| /* |
| * Copyright (C) 2017 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.inject.internal; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import com.google.inject.Guice; |
| import com.google.inject.Key; |
| import com.google.inject.MembersInjector; |
| import com.google.inject.Provides; |
| import com.google.inject.ProvisionException; |
| import com.google.inject.TypeLiteral; |
| import com.google.inject.internal.util.SourceProvider; |
| import com.google.inject.internal.util.StackTraceElements; |
| import com.google.inject.spi.Dependency; |
| import com.google.inject.spi.InjectionListener; |
| import com.google.inject.spi.Message; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * A checked exception for provisioning errors. |
| * |
| * <p>This is the internal dual of {@link ProvisionException}, similar to the relationship between |
| * {@link com.google.inject.ConfigurationException} and {@link ErrorsException}. This is useful for |
| * several reasons: |
| * |
| * <ul> |
| * <li>Since it is a checked exception, we get some assistance from the java compiler in ensuring |
| * that we correctly handle it everywhere. ProvisionException is unchecked. |
| * <li>Since this is an internal package, we can add useful construction and mutation APIs that |
| * would be undesirable in a public supported API. |
| * </ul> |
| * |
| * <p>This exception will be thrown when errors are encountered during provisioning, ErrorsException |
| * will continue to be used for errors that are encountered during provisioning and both make use of |
| * the {@link Message} as the core model. |
| * |
| * <p>NOTE: this object stores a list of messages but in the most common case the cardinality will |
| * be 1. The only time that multiple errors might be reported via this mechanism is when {@link |
| * #errorInUserCode} is called with an exception that holds multiple errors (like |
| * ProvisionException). |
| */ |
| public final class InternalProvisionException extends Exception { |
| private static final Logger logger = Logger.getLogger(Guice.class.getName()); |
| private static final Set<Dependency<?>> warnedDependencies = |
| Collections.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>()); |
| |
| |
| public static InternalProvisionException circularDependenciesDisabled(Class<?> expectedType) { |
| return create( |
| "Found a circular dependency involving %s, and circular dependencies are disabled.", |
| expectedType); |
| } |
| |
| public static InternalProvisionException cannotProxyClass(Class<?> expectedType) { |
| return create( |
| "Tried proxying %s to support a circular dependency, but it is not an interface.", |
| expectedType); |
| } |
| |
| public static InternalProvisionException create(String format, Object... arguments) { |
| return new InternalProvisionException(Messages.create(format, arguments)); |
| } |
| |
| public static InternalProvisionException errorInUserCode( |
| Throwable cause, String messageFormat, Object... arguments) { |
| Collection<Message> messages = Errors.getMessagesFromThrowable(cause); |
| if (!messages.isEmpty()) { |
| // TODO(lukes): it seems like we are dropping some valuable context here.. |
| // consider eliminating this special case |
| return new InternalProvisionException(messages); |
| } else { |
| return new InternalProvisionException(Messages.create(cause, messageFormat, arguments)); |
| } |
| } |
| |
| public static InternalProvisionException subtypeNotProvided( |
| Class<? extends javax.inject.Provider<?>> providerType, Class<?> type) { |
| return create("%s doesn't provide instances of %s.", providerType, type); |
| } |
| |
| public static InternalProvisionException errorInProvider(Throwable cause) { |
| return errorInUserCode(cause, "Error in custom provider, %s", cause); |
| } |
| |
| public static InternalProvisionException errorInjectingMethod(Throwable cause) { |
| return errorInUserCode(cause, "Error injecting method, %s", cause); |
| } |
| |
| public static InternalProvisionException errorInjectingConstructor(Throwable cause) { |
| return errorInUserCode(cause, "Error injecting constructor, %s", cause); |
| } |
| |
| public static InternalProvisionException errorInUserInjector( |
| MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) { |
| return errorInUserCode( |
| cause, "Error injecting %s using %s.%n Reason: %s", type, listener, cause); |
| } |
| |
| public static InternalProvisionException jitDisabled(Key<?> key) { |
| return create("Explicit bindings are required and %s is not explicitly bound.", key); |
| } |
| |
| public static InternalProvisionException errorNotifyingInjectionListener( |
| InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) { |
| return errorInUserCode( |
| cause, "Error notifying InjectionListener %s of %s.%n Reason: %s", listener, type, cause); |
| } |
| |
| /** |
| * Returns {@code value} if it is non-null or allowed to be null. Otherwise a message is added and |
| * an {@code InternalProvisionException} is thrown. |
| */ |
| static void onNullInjectedIntoNonNullableDependency(Object source, Dependency<?> dependency) |
| throws InternalProvisionException { |
| // Hack to allow null parameters to @Provides methods, for backwards compatibility. |
| if (dependency.getInjectionPoint().getMember() instanceof Method) { |
| Method annotated = (Method) dependency.getInjectionPoint().getMember(); |
| if (annotated.isAnnotationPresent(Provides.class)) { |
| switch (InternalFlags.getNullableProvidesOption()) { |
| case ERROR: |
| break; // break out & let the below exception happen |
| case IGNORE: |
| return; // user doesn't care about injecting nulls to non-@Nullables. |
| case WARN: |
| // Warn only once, otherwise we spam logs too much. |
| if (warnedDependencies.add(dependency)) { |
| logger.log( |
| Level.WARNING, |
| "Guice injected null into {0} (a {1}), please mark it @Nullable." |
| + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" |
| + " error.", |
| new Object[] { |
| Messages.formatParameter(dependency), Messages.convert(dependency.getKey()) |
| }); |
| } |
| return; |
| } |
| } |
| } |
| |
| Object formattedDependency = |
| (dependency.getParameterIndex() != -1) |
| ? Messages.formatParameter(dependency) |
| : StackTraceElements.forMember(dependency.getInjectionPoint().getMember()); |
| |
| throw InternalProvisionException.create( |
| "null returned by binding at %s%n but %s is not @Nullable", source, formattedDependency) |
| .addSource(source); |
| } |
| |
| private final List<Object> sourcesToPrepend = new ArrayList<>(); |
| private final ImmutableList<Message> errors; |
| |
| private InternalProvisionException(Message error) { |
| this(ImmutableList.of(error)); |
| } |
| |
| private InternalProvisionException(Iterable<Message> errors) { |
| this.errors = ImmutableList.copyOf(errors); |
| checkArgument(!this.errors.isEmpty(), "Can't create a provision exception with no errors"); |
| } |
| |
| /** |
| * Prepends the given {@code source} to the stack of binding sources for the errors reported in |
| * this exception. |
| * |
| * <p>See {@link Errors#withSource(Object)} |
| * |
| * <p>It is expected that this method is called as the exception propagates up the stack. |
| * |
| * @param source |
| * @return {@code this} |
| */ |
| InternalProvisionException addSource(Object source) { |
| if (source == SourceProvider.UNKNOWN_SOURCE) { |
| return this; |
| } |
| int sz = sourcesToPrepend.size(); |
| if (sz > 0 && sourcesToPrepend.get(sz - 1) == source) { |
| // This is for when there are two identical sources added in a row. This behavior is copied |
| // from Errors.withSource where it can happen when an constructor/provider method throws an |
| // exception |
| return this; |
| } |
| sourcesToPrepend.add(source); |
| return this; |
| } |
| |
| ImmutableList<Message> getErrors() { |
| ImmutableList.Builder<Message> builder = ImmutableList.builder(); |
| // reverse them since sources are added as the exception propagates (so the first source is the |
| // last one added) |
| List<Object> newSources = Lists.reverse(sourcesToPrepend); |
| for (Message error : errors) { |
| builder.add(Messages.mergeSources(newSources, error)); |
| } |
| return builder.build(); |
| } |
| |
| /** Returns this exception convered to a ProvisionException. */ |
| public ProvisionException toProvisionException() { |
| return new ProvisionException(getErrors()); |
| } |
| } |