com.google.inject.internal.Errors Maven / Gradle / Ivy
package com.google.inject.internal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Primitives;
import com.google.inject.Binding;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.SourceProvider;
import com.google.inject.spi.Message;
import com.google.inject.spi.ScopeBinding;
import com.google.inject.spi.TypeConverterBinding;
import com.google.inject.spi.TypeListenerBinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A collection of error messages. If this type is passed as a method parameter, the method is
* considered to have executed successfully only if new errors were not added to this collection.
*
* Errors can be chained to provide additional context. To add context, call {@link #withSource}
* to create a new Errors instance that contains additional context. All messages added to the
* returned instance will contain full context.
*
*
To avoid messages with redundant context, {@link #withSource} should be added sparingly. A
* good rule of thumb is to assume a method's caller has already specified enough context to
* identify that method. When calling a method that's defined in a different context, call that
* method with an errors object that includes its context.
*
*/
public final class Errors {
/** When a binding is not found, show at most this many bindings with the same type */
private static final int MAX_MATCHING_TYPES_REPORTED = 3;
/** When a binding is not found, show at most this many bindings that have some similarities */
private static final int MAX_RELATED_TYPES_REPORTED = 3;
static T checkNotNull(T reference, String name) {
if (reference != null) {
return reference;
}
NullPointerException npe = new NullPointerException(name);
throw new ConfigurationException(ImmutableSet.of(new Message(npe.toString(), npe)));
}
static void checkConfiguration(boolean condition, String format, Object... args) {
if (condition) {
return;
}
throw new ConfigurationException(ImmutableSet.of(new Message(Messages.format(format, args))));
}
private static final ImmutableSet> COMMON_AMBIGUOUS_TYPES =
ImmutableSet.>builder()
.add(Object.class)
.add(String.class)
.addAll(Primitives.allWrapperTypes())
.build();
/** The root errors object. Used to access the list of error messages. */
private final Errors root;
/** The parent errors object. Used to obtain the chain of source objects. */
private final Errors parent;
/**
* The leaf source for errors added here.
*/
private final Object source;
/**
* null unless (root == this) and error messages exist. Never an empty list.
*/
private List errors; // lazy, use getErrorsForAdd()
public Errors() {
this.root = this;
this.parent = null;
this.source = SourceProvider.UNKNOWN_SOURCE;
}
public Errors(Object source) {
this.root = this;
this.parent = null;
this.source = source;
}
private Errors(Errors parent, Object source) {
this.root = parent.root;
this.parent = parent;
this.source = source;
}
/** Returns an instance that uses {@code source} as a reference point for newly added errors. */
public Errors withSource(Object source) {
return source == this.source || source == SourceProvider.UNKNOWN_SOURCE
? this
: new Errors(this, source);
}
public Errors missingImplementation(Key> key) {
return addMessage("No implementation for %s was bound.", key);
}
/** Within guice's core, allow for better missing binding messages */
Errors missingImplementationWithHint(Key key, Injector injector) {
StringBuilder sb = new StringBuilder();
sb.append(Messages.format("No implementation for %s was bound.", key));
// Keys which have similar strings as the desired key
List possibleMatches = new ArrayList<>();
// Check for other keys that may have the same type,
// but not the same annotation
TypeLiteral type = key.getTypeLiteral();
List> sameTypes = injector.findBindingsByType(type);
if (!sameTypes.isEmpty()) {
sb.append(Messages.format("%n Did you mean?"));
int howMany = Math.min(sameTypes.size(), MAX_MATCHING_TYPES_REPORTED);
for (int i = 0; i < howMany; ++i) {
// TODO: Look into a better way to prioritize suggestions. For example, possbily
// use levenshtein distance of the given annotation vs actual annotation.
sb.append(Messages.format("%n * %s", sameTypes.get(i).getKey()));
}
int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED;
if (remaining > 0) {
String plural = (remaining == 1) ? "" : "s";
sb.append(Messages.format("%n %d more binding%s with other annotations.", remaining, plural));
}
} else {
// For now, do a simple substring search for possibilities. This can help spot
// issues when there are generics being used (such as a wrapper class) and the
// user has forgotten they need to bind based on the wrapper, not the underlying
// class. In the future, consider doing a strict in-depth type search.
// TODO: Look into a better way to prioritize suggestions. For example, possbily
// use levenshtein distance of the type literal strings.
String want = type.toString();
Map, Binding>> bindingMap = injector.getAllBindings();
for (Key> bindingKey : bindingMap.keySet()) {
String have = bindingKey.getTypeLiteral().toString();
if (have.contains(want) || want.contains(have)) {
Formatter fmt = new Formatter();
Messages.formatSource(fmt, bindingMap.get(bindingKey).getSource());
String match = String.format("%s bound%s", Messages.convert(bindingKey), fmt.toString());
possibleMatches.add(match);
// TODO: Consider a check that if there are more than some number of results,
// don't suggest any.
if (possibleMatches.size() > MAX_RELATED_TYPES_REPORTED) {
// Early exit if we have found more than we need.
break;
}
}
}
if ((possibleMatches.size() > 0) && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) {
sb.append(Messages.format("%n Did you mean?"));
for (String possibleMatch : possibleMatches) {
sb.append(Messages.format("%n %s", possibleMatch));
}
}
}
// If where are no possibilities to suggest, then handle the case of missing
// annotations on simple types. This is usually a bad idea.
if (sameTypes.isEmpty()
&& possibleMatches.isEmpty()
&& key.getAnnotationType() == null
&& COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) {
// We don't recommend using such simple types without annotations.
sb.append(Messages.format("%nThe key seems very generic, did you forget an annotation?"));
}
return addMessage(sb.toString());
}
public Errors jitDisabled(Key> key) {
return addMessage("Explicit bindings are required and %s is not explicitly bound.", key);
}
public Errors jitDisabledInParent(Key> key) {
return addMessage("Explicit bindings are required and %s would be bound in a parent injector.%n"
+ "Please add an explicit binding for it, either in the child or the parent.",
key);
}
public Errors atInjectRequired(Class> clazz) {
return addMessage("Explicit @Inject annotations are required on constructors,"
+ " but %s has no constructors annotated with @Inject.",
clazz);
}
public Errors converterReturnedNull(String stringValue, Object source,
TypeLiteral> type, TypeConverterBinding typeConverterBinding) {
return addMessage("Received null converting '%s' (bound at %s) to %s%n"
+ " using %s.",
stringValue, Messages.convert(source), type, typeConverterBinding);
}
public Errors conversionTypeError(String stringValue, Object source, TypeLiteral> type,
TypeConverterBinding typeConverterBinding, Object converted) {
return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n"
+ " using %s.%n"
+ " Converter returned %s.",
stringValue, Messages.convert(source), type, typeConverterBinding, converted);
}
public Errors conversionError(String stringValue, Object source,
TypeLiteral> type, TypeConverterBinding typeConverterBinding, RuntimeException cause) {
return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n"
+ " using %s.%n"
+ " Reason: %s",
stringValue, Messages.convert(source), type, typeConverterBinding, cause);
}
public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral> type,
TypeConverterBinding a, TypeConverterBinding b) {
return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n"
+ " %s and%n"
+ " %s.%n"
+ " Please adjust your type converter configuration to avoid overlapping matches.",
stringValue, Messages.convert(source), type, a, b);
}
public Errors bindingToProvider() {
return addMessage("Binding to Provider is not allowed.");
}
public Errors notASubtype(Class> implementationType, Class> type) {
return addMessage("%s doesn't extend %s.", implementationType, type);
}
public Errors recursiveImplementationType() {
return addMessage("@ImplementedBy points to the same class it annotates.");
}
public Errors recursiveProviderType() {
return addMessage("@ProvidedBy points to the same class it annotates.");
}
public Errors missingRuntimeRetention(Class extends Annotation> annotation) {
return addMessage(Messages.format("Please annotate %s with @Retention(RUNTIME).", annotation));
}
public Errors missingScopeAnnotation(Class extends Annotation> annotation) {
return addMessage(Messages.format("Please annotate %s with @ScopeAnnotation.", annotation));
}
public Errors optionalConstructor(Constructor> constructor) {
return addMessage("%s is annotated @Inject(optional=true), "
+ "but constructors cannot be optional.", constructor);
}
public Errors cannotBindToGuiceType(String simpleName) {
return addMessage("Binding to core guice framework type is not allowed: %s.", simpleName);
}
public Errors scopeNotFound(Class extends Annotation> scopeAnnotation) {
return addMessage("No scope is bound to %s.", scopeAnnotation);
}
public Errors scopeAnnotationOnAbstractType(
Class extends Annotation> scopeAnnotation, Class> type, Object source) {
return addMessage("%s is annotated with %s, but scope annotations are not supported "
+ "for abstract types.%n Bound at %s.", type, scopeAnnotation, Messages.convert(source));
}
public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
return addMessage("%s is annotated with %s, but binding annotations should be applied "
+ "to its parameters instead.", member, bindingAnnotation);
}
private static final String CONSTRUCTOR_RULES =
"Injectable classes must have either one (and only one) constructor annotated with @Inject"
+ " or a zero-argument constructor that is not private.";
public Errors missingConstructor(TypeLiteral> type) {
// Don't bother including the type in the message twice, unless the type is generic (i.e. the
// type has generics that the raw class loses)
String typeString = type.toString();
String rawTypeString = MoreTypes.getRawType(type.getType()).getName();
return addMessage(
"No implementation for %s (with no qualifier annotation) was bound, and could not find an"
+ " injectable constructor%s. %s",
typeString,
typeString.equals(rawTypeString) ? "" : " in " + rawTypeString,
CONSTRUCTOR_RULES);
}
public Errors tooManyConstructors(Class> implementation) {
return addMessage(
"%s has more than one constructor annotated with @Inject. %s",
implementation, CONSTRUCTOR_RULES);
}
public Errors constructorNotDefinedByType(Constructor> constructor, TypeLiteral> type) {
return addMessage("%s does not define %s", type, constructor);
}
public Errors duplicateScopes(ScopeBinding existing,
Class extends Annotation> annotationType, Scope scope) {
return addMessage("Scope %s is already bound to %s at %s.%n Cannot bind %s.",
existing.getScope(), annotationType, existing.getSource(), scope);
}
public Errors voidProviderMethod() {
return addMessage("Provider methods must return a value. Do not return void.");
}
public Errors missingConstantValues() {
return addMessage("Missing constant value. Please call to(...).");
}
public Errors cannotInjectInnerClass(Class> type) {
return addMessage("Injecting into inner classes is not supported. "
+ "Please use a 'static' class (top-level or nested) instead of %s.", type);
}
public Errors duplicateBindingAnnotations(Member member,
Class extends Annotation> a, Class extends Annotation> b) {
return addMessage("%s has more than one annotation annotated with @BindingAnnotation: "
+ "%s and %s", member, a, b);
}
public Errors staticInjectionOnInterface(Class> clazz) {
return addMessage("%s is an interface, but interfaces have no static injection points.", clazz);
}
public Errors cannotInjectFinalField(Field field) {
return addMessage("Injected field %s cannot be final.", field);
}
public Errors cannotInjectAbstractMethod(Method method) {
return addMessage("Injected method %s cannot be abstract.", method);
}
public Errors cannotInjectNonVoidMethod(Method method) {
return addMessage("Injected method %s must return void.", method);
}
public Errors cannotInjectMethodWithTypeParameters(Method method) {
return addMessage("Injected method %s cannot declare type parameters of its own.", method);
}
public Errors duplicateScopeAnnotations(
Class extends Annotation> a, Class extends Annotation> b) {
return addMessage("More than one scope annotation was found: %s and %s.", a, b);
}
public Errors recursiveBinding() {
return addMessage("Binding points to itself.");
}
public Errors bindingAlreadySet(Key> key, Object source) {
return addMessage("A binding to %s was already configured at %s.", key, Messages.convert(source));
}
public Errors jitBindingAlreadySet(Key> key) {
return addMessage("A just-in-time binding to %s was already configured on a parent injector.", key);
}
public Errors childBindingAlreadySet(Key> key, Set