1 /* 2 * Copyright (C) 2017 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.inject.internal; 17 18 import static com.google.common.base.Preconditions.checkArgument; 19 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.collect.Lists; 22 import com.google.inject.Guice; 23 import com.google.inject.Key; 24 import com.google.inject.MembersInjector; 25 import com.google.inject.Provides; 26 import com.google.inject.ProvisionException; 27 import com.google.inject.TypeLiteral; 28 import com.google.inject.internal.util.SourceProvider; 29 import com.google.inject.internal.util.StackTraceElements; 30 import com.google.inject.spi.Dependency; 31 import com.google.inject.spi.InjectionListener; 32 import com.google.inject.spi.Message; 33 import java.lang.reflect.Method; 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Set; 39 import java.util.concurrent.ConcurrentHashMap; 40 import java.util.logging.Level; 41 import java.util.logging.Logger; 42 43 /** 44 * A checked exception for provisioning errors. 45 * 46 * <p>This is the internal dual of {@link ProvisionException}, similar to the relationship between 47 * {@link com.google.inject.ConfigurationException} and {@link ErrorsException}. This is useful for 48 * several reasons: 49 * 50 * <ul> 51 * <li>Since it is a checked exception, we get some assistance from the java compiler in ensuring 52 * that we correctly handle it everywhere. ProvisionException is unchecked. 53 * <li>Since this is an internal package, we can add useful construction and mutation APIs that 54 * would be undesirable in a public supported API. 55 * </ul> 56 * 57 * <p>This exception will be thrown when errors are encountered during provisioning, ErrorsException 58 * will continue to be used for errors that are encountered during provisioning and both make use of 59 * the {@link Message} as the core model. 60 * 61 * <p>NOTE: this object stores a list of messages but in the most common case the cardinality will 62 * be 1. The only time that multiple errors might be reported via this mechanism is when {@link 63 * #errorInUserCode} is called with an exception that holds multiple errors (like 64 * ProvisionException). 65 */ 66 public final class InternalProvisionException extends Exception { 67 private static final Logger logger = Logger.getLogger(Guice.class.getName()); 68 private static final Set<Dependency<?>> warnedDependencies = 69 Collections.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>()); 70 71 circularDependenciesDisabled(Class<?> expectedType)72 public static InternalProvisionException circularDependenciesDisabled(Class<?> expectedType) { 73 return create( 74 "Found a circular dependency involving %s, and circular dependencies are disabled.", 75 expectedType); 76 } 77 cannotProxyClass(Class<?> expectedType)78 public static InternalProvisionException cannotProxyClass(Class<?> expectedType) { 79 return create( 80 "Tried proxying %s to support a circular dependency, but it is not an interface.", 81 expectedType); 82 } 83 create(String format, Object... arguments)84 public static InternalProvisionException create(String format, Object... arguments) { 85 return new InternalProvisionException(Messages.create(format, arguments)); 86 } 87 errorInUserCode( Throwable cause, String messageFormat, Object... arguments)88 public static InternalProvisionException errorInUserCode( 89 Throwable cause, String messageFormat, Object... arguments) { 90 Collection<Message> messages = Errors.getMessagesFromThrowable(cause); 91 if (!messages.isEmpty()) { 92 // TODO(lukes): it seems like we are dropping some valuable context here.. 93 // consider eliminating this special case 94 return new InternalProvisionException(messages); 95 } else { 96 return new InternalProvisionException(Messages.create(cause, messageFormat, arguments)); 97 } 98 } 99 subtypeNotProvided( Class<? extends javax.inject.Provider<?>> providerType, Class<?> type)100 public static InternalProvisionException subtypeNotProvided( 101 Class<? extends javax.inject.Provider<?>> providerType, Class<?> type) { 102 return create("%s doesn't provide instances of %s.", providerType, type); 103 } 104 errorInProvider(Throwable cause)105 public static InternalProvisionException errorInProvider(Throwable cause) { 106 return errorInUserCode(cause, "Error in custom provider, %s", cause); 107 } 108 errorInjectingMethod(Throwable cause)109 public static InternalProvisionException errorInjectingMethod(Throwable cause) { 110 return errorInUserCode(cause, "Error injecting method, %s", cause); 111 } 112 errorInjectingConstructor(Throwable cause)113 public static InternalProvisionException errorInjectingConstructor(Throwable cause) { 114 return errorInUserCode(cause, "Error injecting constructor, %s", cause); 115 } 116 errorInUserInjector( MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause)117 public static InternalProvisionException errorInUserInjector( 118 MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) { 119 return errorInUserCode( 120 cause, "Error injecting %s using %s.%n Reason: %s", type, listener, cause); 121 } 122 jitDisabled(Key<?> key)123 public static InternalProvisionException jitDisabled(Key<?> key) { 124 return create("Explicit bindings are required and %s is not explicitly bound.", key); 125 } 126 errorNotifyingInjectionListener( InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause)127 public static InternalProvisionException errorNotifyingInjectionListener( 128 InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) { 129 return errorInUserCode( 130 cause, "Error notifying InjectionListener %s of %s.%n Reason: %s", listener, type, cause); 131 } 132 133 /** 134 * Returns {@code value} if it is non-null or allowed to be null. Otherwise a message is added and 135 * an {@code InternalProvisionException} is thrown. 136 */ onNullInjectedIntoNonNullableDependency(Object source, Dependency<?> dependency)137 static void onNullInjectedIntoNonNullableDependency(Object source, Dependency<?> dependency) 138 throws InternalProvisionException { 139 // Hack to allow null parameters to @Provides methods, for backwards compatibility. 140 if (dependency.getInjectionPoint().getMember() instanceof Method) { 141 Method annotated = (Method) dependency.getInjectionPoint().getMember(); 142 if (annotated.isAnnotationPresent(Provides.class)) { 143 switch (InternalFlags.getNullableProvidesOption()) { 144 case ERROR: 145 break; // break out & let the below exception happen 146 case IGNORE: 147 return; // user doesn't care about injecting nulls to non-@Nullables. 148 case WARN: 149 // Warn only once, otherwise we spam logs too much. 150 if (warnedDependencies.add(dependency)) { 151 logger.log( 152 Level.WARNING, 153 "Guice injected null into {0} (a {1}), please mark it @Nullable." 154 + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" 155 + " error.", 156 new Object[] { 157 Messages.formatParameter(dependency), Messages.convert(dependency.getKey()) 158 }); 159 } 160 return; 161 } 162 } 163 } 164 165 Object formattedDependency = 166 (dependency.getParameterIndex() != -1) 167 ? Messages.formatParameter(dependency) 168 : StackTraceElements.forMember(dependency.getInjectionPoint().getMember()); 169 170 throw InternalProvisionException.create( 171 "null returned by binding at %s%n but %s is not @Nullable", source, formattedDependency) 172 .addSource(source); 173 } 174 175 private final List<Object> sourcesToPrepend = new ArrayList<>(); 176 private final ImmutableList<Message> errors; 177 InternalProvisionException(Message error)178 private InternalProvisionException(Message error) { 179 this(ImmutableList.of(error)); 180 } 181 InternalProvisionException(Iterable<Message> errors)182 private InternalProvisionException(Iterable<Message> errors) { 183 this.errors = ImmutableList.copyOf(errors); 184 checkArgument(!this.errors.isEmpty(), "Can't create a provision exception with no errors"); 185 } 186 187 /** 188 * Prepends the given {@code source} to the stack of binding sources for the errors reported in 189 * this exception. 190 * 191 * <p>See {@link Errors#withSource(Object)} 192 * 193 * <p>It is expected that this method is called as the exception propagates up the stack. 194 * 195 * @param source 196 * @return {@code this} 197 */ addSource(Object source)198 InternalProvisionException addSource(Object source) { 199 if (source == SourceProvider.UNKNOWN_SOURCE) { 200 return this; 201 } 202 int sz = sourcesToPrepend.size(); 203 if (sz > 0 && sourcesToPrepend.get(sz - 1) == source) { 204 // This is for when there are two identical sources added in a row. This behavior is copied 205 // from Errors.withSource where it can happen when an constructor/provider method throws an 206 // exception 207 return this; 208 } 209 sourcesToPrepend.add(source); 210 return this; 211 } 212 getErrors()213 ImmutableList<Message> getErrors() { 214 ImmutableList.Builder<Message> builder = ImmutableList.builder(); 215 // reverse them since sources are added as the exception propagates (so the first source is the 216 // last one added) 217 List<Object> newSources = Lists.reverse(sourcesToPrepend); 218 for (Message error : errors) { 219 builder.add(Messages.mergeSources(newSources, error)); 220 } 221 return builder.build(); 222 } 223 224 /** Returns this exception convered to a ProvisionException. */ toProvisionException()225 public ProvisionException toProvisionException() { 226 return new ProvisionException(getErrors()); 227 } 228 } 229