com.google.inject.internal.InternalProvisionException Maven / Gradle / Ivy
/*
* 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(
ErrorId.CIRCULAR_PROXY_DISABLED,
"Found a circular dependency involving %s, and circular dependencies are disabled.",
expectedType);
}
public static InternalProvisionException cannotProxyClass(Class> expectedType) {
return create(
ErrorId.CAN_NOT_PROXY_CLASS,
"Tried proxying %s to support a circular dependency, but it is not an interface.",
expectedType);
}
public static InternalProvisionException create(
ErrorId errorId, String format, Object... arguments) {
return new InternalProvisionException(Messages.create(errorId, format, arguments));
}
public static InternalProvisionException errorInUserCode(
ErrorId errorId, 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(errorId, cause, messageFormat, arguments));
}
}
public static InternalProvisionException subtypeNotProvided(
Class extends jakarta.inject.Provider>> providerType, Class> type) {
return create(
ErrorId.SUBTYPE_NOT_PROVIDED, "%s doesn't provide instances of %s.", providerType, type);
}
public static InternalProvisionException errorInProvider(Throwable cause) {
return errorInUserCode(ErrorId.ERROR_IN_CUSTOM_PROVIDER, cause, "%s", cause);
}
public static InternalProvisionException errorInjectingMethod(Throwable cause) {
return errorInUserCode(ErrorId.ERROR_INJECTING_METHOD, cause, "%s", cause);
}
public static InternalProvisionException errorInjectingConstructor(Throwable cause) {
return errorInUserCode(ErrorId.ERROR_INJECTING_CONSTRUCTOR, cause, "%s", cause);
}
public static InternalProvisionException errorInUserInjector(
MembersInjector> listener, TypeLiteral> type, RuntimeException cause) {
return errorInUserCode(
ErrorId.ERROR_IN_USER_INJECTOR,
cause,
"Error injecting %s using %s.\n Reason: %s",
type,
listener,
cause);
}
public static InternalProvisionException jitDisabled(Key> key) {
return create(
ErrorId.JIT_DISABLED,
"Explicit bindings are required and %s is not explicitly bound.",
key);
}
public static InternalProvisionException errorNotifyingInjectionListener(
InjectionListener> listener, TypeLiteral> type, RuntimeException cause) {
return errorInUserCode(
ErrorId.OTHER,
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[] {
SourceFormatter.getParameterName(dependency),
Messages.convert(dependency.getKey())
});
}
return;
}
}
}
String parameterName =
(dependency.getParameterIndex() != -1) ? SourceFormatter.getParameterName(dependency) : "";
Object memberStackTraceElement =
StackTraceElements.forMember(dependency.getInjectionPoint().getMember());
Object formattedDependency =
parameterName.isEmpty()
? memberStackTraceElement
: "the " + parameterName + " of " + memberStackTraceElement;
throw InternalProvisionException.create(
ErrorId.NULL_INJECTED_INTO_NON_NULLABLE,
"null returned by binding at %s\n but %s is not @Nullable",
source,
formattedDependency)
.addSource(source);
}
private final List