• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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