All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.internal.InternalProvisionException Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
/*
 * 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.
 *
 * 

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: * *

    *
  • 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. *
  • Since this is an internal package, we can add useful construction and mutation APIs that * would be undesirable in a public supported API. *
* *

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. * *

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> warnedDependencies = Collections.newSetFromMap(new ConcurrentHashMap, 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 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> 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 sourcesToPrepend = new ArrayList<>(); private final ImmutableList errors; private InternalProvisionException(Message error) { this(ImmutableList.of(error)); } private InternalProvisionException(Iterable 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. * *

See {@link Errors#withSource(Object)} * *

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 getErrors() { ImmutableList.Builder builder = ImmutableList.builder(); // reverse them since sources are added as the exception propagates (so the first source is the // last one added) List 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()); } }