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

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

The newest version!
/*
 * Copyright (C) 2006 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 com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
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.ElementSource;
import com.google.inject.spi.InterceptorBinding;
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.io.Serializable;
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.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. * * @author [email protected] (Jesse Wilson) */ public final class Errors implements Serializable { /** * Throws a ConfigurationException with an NullPointerExceptions as the cause if the given * reference is {@code null}. */ 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))); } /** * Throws a ConfigurationException with a formatted {@link Message} if this condition is {@code * false}. */ static void checkConfiguration(boolean condition, String format, Object... args) { if (condition) { return; } throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); } /** 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 aopDisabled(InterceptorBinding binding) { return addMessage( ErrorId.AOP_DISABLED, "Binding interceptor is not supported when bytecode generation is disabled. \nInterceptor" + " bound at: %s", binding.getSource()); } /** * We use a fairly generic error message here. The motivation is to share the same message for * both bind time errors: * *

Guice.createInjector(new AbstractModule() {
   *   public void configure() {
   *     bind(Runnable.class);
   *   }
   * }
* * ...and at provide-time errors: * *
Guice.createInjector().getInstance(Runnable.class);
* * Otherwise we need to know who's calling when resolving a just-in-time binding, which makes * things unnecessarily complex. */ public Errors missingImplementation(Key key) { return addMessage(ErrorId.MISSING_IMPLEMENTATION, "No implementation for %s was bound.", key); } /** Within guice's core, allow for better missing binding messages */ Errors missingImplementationWithHint(Key key, Injector injector) { MissingImplementationError error = new MissingImplementationError(key, injector, getSources()); return addMessage( new Message(GuiceInternal.GUICE_INTERNAL, ErrorId.MISSING_IMPLEMENTATION, error)); } public Errors jitDisabled(Key key) { return addMessage( ErrorId.JIT_DISABLED, "Explicit bindings are required and %s is not explicitly bound.", key); } public Errors jitDisabledInParent(Key key) { return addMessage( ErrorId.JIT_DISABLED_IN_PARENT, "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(TypeLiteral type) { return addMessage( new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.MISSING_CONSTRUCTOR, new MissingConstructorError(type, /* atInjectRequired= */ true, getSources()))); } public Errors converterReturnedNull( String stringValue, Object source, TypeLiteral type, TypeConverterBinding typeConverterBinding) { return addMessage( ErrorId.CONVERTER_RETURNED_NULL, "Received null converting '%s' (bound at %s) to %s\n using %s.", stringValue, convert(source), type, typeConverterBinding); } public Errors conversionTypeError( String stringValue, Object source, TypeLiteral type, TypeConverterBinding typeConverterBinding, Object converted) { return addMessage( ErrorId.CONVERSION_TYPE_ERROR, "Type mismatch converting '%s' (bound at %s) to %s\n" + " using %s.\n" + " Converter returned %s.", stringValue, 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, convert(source), type, typeConverterBinding, cause); } public Errors ambiguousTypeConversion( String stringValue, Object source, TypeLiteral type, TypeConverterBinding a, TypeConverterBinding b) { return addMessage( ErrorId.AMBIGUOUS_TYPE_CONVERSION, "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, convert(source), type, a, b); } public Errors bindingToProvider() { return addMessage(ErrorId.BINDING_TO_PROVIDER, "Binding to Provider is not allowed."); } public Errors notASubtype(Class implementationType, Class type) { return addMessage(ErrorId.NOT_A_SUBTYPE, "%s doesn't extend %s.", implementationType, type); } public Errors recursiveImplementationType() { return addMessage( ErrorId.RECURSIVE_IMPLEMENTATION_TYPE, "@ImplementedBy points to the same class it annotates."); } public Errors recursiveProviderType() { return addMessage( ErrorId.RECURSIVE_PROVIDER_TYPE, "@ProvidedBy points to the same class it annotates."); } public Errors missingRuntimeRetention(Class annotation) { return addMessage( ErrorId.MISSING_RUNTIME_RETENTION, format("Please annotate %s with @Retention(RUNTIME).", annotation)); } public Errors missingScopeAnnotation(Class annotation) { return addMessage( ErrorId.MISSING_SCOPE_ANNOTATION, format("Please annotate %s with @ScopeAnnotation.", annotation)); } public Errors optionalConstructor(Constructor constructor) { return addMessage( ErrorId.OPTIONAL_CONSTRUCTOR, "%s is annotated @Inject(optional=true), but constructors cannot be optional.", constructor); } public Errors cannotBindToGuiceType(String simpleName) { return addMessage( ErrorId.BINDING_TO_GUICE_TYPE, "Binding to core guice framework type is not allowed: %s.", simpleName); } public Errors scopeNotFound(Class scopeAnnotation) { return addMessage( new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.SCOPE_NOT_FOUND, new ScopeNotFoundError(scopeAnnotation, getSources()))); } public Errors scopeAnnotationOnAbstractType( Class scopeAnnotation, Class type, Object source) { return addMessage( ErrorId.SCOPE_ANNOTATION_ON_ABSTRACT_TYPE, "%s is annotated with %s, but scope annotations are not supported " + "for abstract types.\n Bound at %s.", type, scopeAnnotation, convert(source)); } public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) { return addMessage( ErrorId.MISPLACED_BINDING_ANNOTATION, "%s is annotated with %s, but binding annotations should be applied " + "to its parameters instead.", member, bindingAnnotation); } // TODO(diamondm) don't mention zero-arg constructors if requireAtInjectOnConstructors is true 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) { return addMessage( new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.MISSING_CONSTRUCTOR, new MissingConstructorError(type, /* atInjectRequired= */ false, getSources()))); } public Errors tooManyConstructors(Class implementation) { return addMessage( ErrorId.TOO_MANY_CONSTRUCTORS, "%s has more than one constructor annotated with @Inject. %s", implementation, CONSTRUCTOR_RULES); } public Errors constructorNotDefinedByType(Constructor constructor, TypeLiteral type) { return addMessage( ErrorId.CONSTRUCTOR_NOT_DEFINED_BY_TYPE, "%s does not define %s", type, constructor); } public Errors duplicateMapKey(Key> mapKey, Multimap> duplicates) { return addMessage( new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.DUPLICATE_MAP_KEY, new DuplicateMapKeyError(mapKey, duplicates, getSources()))); } public Errors duplicateScopes( ScopeBinding existing, Class annotationType, Scope scope) { return addMessage( ErrorId.DUPLICATE_SCOPES, "Scope %s is already bound to %s at %s.\n Cannot bind %s.", existing.getScope(), annotationType, existing.getSource(), scope); } public Errors voidProviderMethod() { return addMessage( ErrorId.VOID_PROVIDER_METHOD, "Provider methods must return a value. Do not return void."); } public Errors missingConstantValues() { return addMessage( ErrorId.MISSING_CONSTANT_VALUES, "Missing constant value. Please call to(...)."); } public Errors cannotInjectInnerClass(Class type) { return addMessage( ErrorId.INJECT_INNER_CLASS, "Injecting into inner classes is not supported. " + "Please use a 'static' class (top-level or nested) instead of %s.", type); } public Errors cannotInjectLocalClass(Class type) { return addMessage( ErrorId.INJECT_LOCAL_CLASS, "Injecting into local classes is not supported. " + "Please use a non-local class instead of %s.", type); } public Errors duplicateBindingAnnotations( Member member, Class a, Class b) { return addMessage( ErrorId.DUPLICATE_BINDING_ANNOTATIONS, "%s has more than one annotation annotated with @BindingAnnotation: %s and %s", member, a, b); } public Errors staticInjectionOnInterface(Class clazz) { return addMessage( ErrorId.STATIC_INJECTION_ON_INTERFACE, "%s is an interface, but interfaces have no static injection points.", clazz); } public Errors cannotInjectFinalField(Field field) { return addMessage(ErrorId.INJECT_FINAL_FIELD, "Injected field %s cannot be final.", field); } public Errors atTargetIsMissingParameter( Annotation bindingAnnotation, String parameterName, Class clazz) { return addMessage( ErrorId.AT_TARGET_IS_MISSING_PARAMETER, "Binding annotation %s must have PARAMETER listed in its @Targets. It was used on" + " constructor parameter %s in %s.", bindingAnnotation, parameterName, clazz); } public Errors cannotInjectAbstractMethod(Method method) { return addMessage( ErrorId.INJECT_ABSTRACT_METHOD, "Injected method %s cannot be abstract.", method); } public Errors cannotInjectMethodWithTypeParameters(Method method) { return addMessage( ErrorId.INJECT_METHOD_WITH_TYPE_PARAMETER, "Injected method %s cannot declare type parameters of its own.", method); } public Errors duplicateScopeAnnotations( Class a, Class b) { return addMessage( ErrorId.DUPLICATE_SCOPE_ANNOTATIONS, "More than one scope annotation was found: %s and %s.", a, b); } public Errors recursiveBinding(Key key, Key linkedKey) { return addMessage( ErrorId.RECURSIVE_BINDING, "Binding points to itself. Key: %s", Messages.convert(key)); } Errors bindingAlreadySet(Binding binding, Binding original) { BindingAlreadySetError error = new BindingAlreadySetError(binding, original, getSources()); return addMessage( new Message(GuiceInternal.GUICE_INTERNAL, ErrorId.BINDING_ALREADY_SET, error)); } public Errors bindingAlreadySet(Key key, Object source) { return addMessage( ErrorId.BINDING_ALREADY_SET, "A binding to %s was already configured at %s.", key, convert(source)); } public Errors jitBindingAlreadySet(Key key) { return addMessage( ErrorId.JIT_BINDING_ALREADY_SET, "A just-in-time binding to %s was already configured on a parent injector.", key); } public Errors childBindingAlreadySet(Key key, Set sources) { Message message = new Message( GuiceInternal.GUICE_INTERNAL, ErrorId.CHILD_BINDING_ALREADY_SET, new ChildBindingAlreadySetError(key, sources, getSources())); return addMessage(message); } public Errors errorCheckingDuplicateBinding(Key key, Object source, Throwable t) { return addMessage( ErrorId.OTHER, "A binding to %s was already configured at %s and an error was thrown " + "while checking duplicate bindings. Error: %s", key, convert(source), t); } public Errors requestInjectionWithDifferentTypes( Object instance, TypeLiteral type1, Object type1Source, TypeLiteral type2) { return addMessage( ErrorId.REQUEST_INJECTION_WITH_DIFFERENT_TYPES, "Cannot request injection on one instance with two different types. requestInjection was" + " already called for instance %s at %s (with type %s), which is different than type" + " %s.", instance.getClass().getName() + "@" + System.identityHashCode(instance), type1Source, type1, type2); } public Errors errorNotifyingTypeListener( TypeListenerBinding listener, TypeLiteral type, Throwable cause) { return errorInUserCode( cause, "Error notifying TypeListener %s (bound at %s) of %s.\n Reason: %s", listener.getListener(), convert(listener.getSource()), type, cause); } public Errors exposedButNotBound(Key key) { return addMessage( ErrorId.EXPOSED_BUT_NOT_BOUND, "Could not expose() %s, it must be explicitly bound.", key); } public Errors keyNotFullySpecified(TypeLiteral typeLiteral) { return addMessage( ErrorId.KEY_NOT_FULLY_SPECIFIED, "%s cannot be used as a key; It is not fully specified.", typeLiteral); } public Errors errorEnhancingClass(Class clazz, Throwable cause) { return errorInUserCode(cause, "Unable to method intercept: %s", clazz); } public static Collection getMessagesFromThrowable(Throwable throwable) { if (throwable instanceof ProvisionException) { return ((ProvisionException) throwable).getErrorMessages(); } else if (throwable instanceof ConfigurationException) { return ((ConfigurationException) throwable).getErrorMessages(); } else if (throwable instanceof CreationException) { return ((CreationException) throwable).getErrorMessages(); } else { return ImmutableSet.of(); } } public Errors errorInUserCode(Throwable cause, String messageFormat, Object... arguments) { Collection messages = getMessagesFromThrowable(cause); if (!messages.isEmpty()) { return merge(messages); } else { return addMessage(ErrorId.ERROR_IN_USER_CODE, cause, messageFormat, arguments); } } public Errors cannotInjectRawProvider() { return addMessage( ErrorId.INJECT_RAW_PROVIDER, "Cannot inject a Provider that has no type parameter"); } public Errors cannotInjectRawMembersInjector() { return addMessage( ErrorId.INJECT_RAW_MEMBERS_INJECTOR, "Cannot inject a MembersInjector that has no type parameter"); } public Errors cannotInjectTypeLiteralOf(Type unsupportedType) { return addMessage(ErrorId.OTHER, "Cannot inject a TypeLiteral of %s", unsupportedType); } public Errors cannotInjectRawTypeLiteral() { return addMessage( ErrorId.INJECT_RAW_TYPE_LITERAL, "Cannot inject a TypeLiteral that has no type parameter"); } public void throwCreationExceptionIfErrorsExist() { if (!hasErrors()) { return; } CreationException exception = new CreationException(getMessages()); throw exception; } public void throwConfigurationExceptionIfErrorsExist() { if (!hasErrors()) { return; } ConfigurationException exception = new ConfigurationException(getMessages()); throw exception; } // Guice no longer calls this, but external callers do public void throwProvisionExceptionIfErrorsExist() { if (!hasErrors()) { return; } ProvisionException exception = new ProvisionException(getMessages()); throw exception; } public Errors merge(Collection messages) { List sources = getSources(); for (Message message : messages) { addMessage(Messages.mergeSources(sources, message)); } return this; } public Errors merge(Errors moreErrors) { if (moreErrors.root == root || moreErrors.root.errors == null) { return this; } merge(moreErrors.root.errors); return this; } public Errors merge(InternalProvisionException ipe) { merge(ipe.getErrors()); return this; } private List getSources() { List sources = Lists.newArrayList(); for (Errors e = this; e != null; e = e.parent) { if (e.source != SourceProvider.UNKNOWN_SOURCE) { sources.add(0, e.source); } } return sources; } public void throwIfNewErrors(int expectedSize) throws ErrorsException { if (size() == expectedSize) { return; } throw toException(); } public ErrorsException toException() { return new ErrorsException(this); } public boolean hasErrors() { return root.errors != null; } public Errors addMessage(String messageFormat, Object... arguments) { return addMessage(ErrorId.OTHER, null, messageFormat, arguments); } public Errors addMessage(ErrorId errorId, String messageFormat, Object... arguments) { return addMessage(errorId, null, messageFormat, arguments); } private Errors addMessage( ErrorId errorId, Throwable cause, String messageFormat, Object... arguments) { addMessage(Messages.create(errorId, cause, getSources(), messageFormat, arguments)); return this; } public Errors addMessage(Message message) { if (root.errors == null) { root.errors = Lists.newArrayList(); } root.errors.add(message); return this; } // TODO(lukes): inline into callers public static String format(String messageFormat, Object... arguments) { return Messages.format(messageFormat, arguments); } public List getMessages() { if (root.errors == null) { return ImmutableList.of(); } return new Ordering() { @Override public int compare(Message a, Message b) { return a.getSource().compareTo(b.getSource()); } }.sortedCopy(root.errors); } public int size() { return root.errors == null ? 0 : root.errors.size(); } // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult public static Object convert(Object o) { return Messages.convert(o); } // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult public static Object convert(Object o, ElementSource source) { return Messages.convert(o, source); } // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult public static void formatSource(Formatter formatter, Object source) { formatter.format(" "); new SourceFormatter(source, formatter, false).format(); } }