Merge pull request #902 from google/merge-moe-changes
Merge moe changes
diff --git a/core/src/com/google/inject/Binder.java b/core/src/com/google/inject/Binder.java
index 91f436f..e895759 100644
--- a/core/src/com/google/inject/Binder.java
+++ b/core/src/com/google/inject/Binder.java
@@ -20,6 +20,7 @@
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProvisionListener;
import com.google.inject.spi.TypeConverter;
@@ -323,6 +324,18 @@
<T> Provider<T> getProvider(Key<T> key);
/**
+ * Returns the provider used to obtain instances for the given injection key.
+ * The returned provider will be attached to the injection point and will
+ * follow the nullability specified in the dependency.
+ * Additionally, the returned provider will not be valid until the {@link Injector}
+ * has been created. The provider will throw an {@code IllegalStateException} if you
+ * try to use it beforehand.
+ *
+ * @since 4.0
+ */
+ <T> Provider<T> getProvider(Dependency<T> dependency);
+
+ /**
* Returns the provider used to obtain instances for the given injection type.
* The returned provider will not be valid until the {@link Injector} has been
* created. The provider will throw an {@code IllegalStateException} if you
diff --git a/core/src/com/google/inject/internal/Annotations.java b/core/src/com/google/inject/internal/Annotations.java
index dfc6936..4c994a9 100644
--- a/core/src/com/google/inject/internal/Annotations.java
+++ b/core/src/com/google/inject/internal/Annotations.java
@@ -201,6 +201,17 @@
return found;
}
+ static boolean containsComponentAnnotation(Annotation[] annotations) {
+ for (Annotation annotation : annotations) {
+ // TODO(user): Should we scope this down to dagger.Component?
+ if (annotation.annotationType().getSimpleName().equals("Component")) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Checks for the presence of annotations. Caches results because Android doesn't.
*/
@@ -256,7 +267,9 @@
}
Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, type);
- if (scopeAnnotation != null) {
+ if (scopeAnnotation != null
+ // We let Dagger Components through to aid migrations.
+ && !containsComponentAnnotation(type.getAnnotations())) {
errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source);
}
}
diff --git a/core/src/com/google/inject/internal/Errors.java b/core/src/com/google/inject/internal/Errors.java
index 385d4b8..5ce3b42 100644
--- a/core/src/com/google/inject/internal/Errors.java
+++ b/core/src/com/google/inject/internal/Errors.java
@@ -20,11 +20,14 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
+import com.google.common.collect.Sets;
import com.google.inject.ConfigurationException;
import com.google.inject.CreationException;
+import com.google.inject.Guice;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Provider;
+import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
@@ -53,6 +56,9 @@
import java.util.Formatter;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* A collection of error messages. If this type is passed as a method parameter, the method is
@@ -71,6 +77,11 @@
*/
public final class Errors implements Serializable {
+ private static final Logger logger = Logger.getLogger(Guice.class.getName());
+
+ private static final Set<Dependency<?>> warnedDependencies =
+ Sets.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>());
+
/**
* The root errors object. Used to access the list of error messages.
@@ -602,6 +613,33 @@
return value;
}
+ // 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 value; // 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)) {
+ return value;
+ }
+ logger.log(Level.WARNING,
+ "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+ + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ + " error.",
+ new Object[] {
+ dependency.getParameterIndex(),
+ convert(dependency.getInjectionPoint().getMember()),
+ convert(dependency.getKey())});
+ return null; // log & exit.
+ }
+ }
+ }
+
int parameterIndex = dependency.getParameterIndex();
String parameterName = (parameterIndex != -1)
? "parameter " + parameterIndex + " of "
diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java
index eef7d98..d1d028f 100644
--- a/core/src/com/google/inject/internal/InjectorImpl.java
+++ b/core/src/com/google/inject/internal/InjectorImpl.java
@@ -1000,9 +1000,9 @@
return getProvider(Key.get(type));
}
- <T> Provider<T> getProviderOrThrow(final Key<T> key, Errors errors) throws ErrorsException {
+ <T> Provider<T> getProviderOrThrow(final Dependency<T> dependency, Errors errors) throws ErrorsException {
+ final Key<T> key = dependency.getKey();
final BindingImpl<? extends T> binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT);
- final Dependency<T> dependency = Dependency.get(key);
return new Provider<T>() {
public T get() {
@@ -1034,7 +1034,7 @@
public <T> Provider<T> getProvider(final Key<T> key) {
Errors errors = new Errors(key);
try {
- Provider<T> result = getProviderOrThrow(key, errors);
+ Provider<T> result = getProviderOrThrow(Dependency.get(key), errors);
errors.throwIfNewErrors(0);
return result;
} catch (ErrorsException e) {
diff --git a/core/src/com/google/inject/internal/InternalFlags.java b/core/src/com/google/inject/internal/InternalFlags.java
index 4e0b227..85c07ac 100644
--- a/core/src/com/google/inject/internal/InternalFlags.java
+++ b/core/src/com/google/inject/internal/InternalFlags.java
@@ -33,6 +33,9 @@
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING
= parseCustomClassLoadingOption();
+ private static final NullableProvidesOption NULLABLE_PROVIDES
+ = parseNullableProvidesOption(NullableProvidesOption.ERROR);
+
/**
* The options for Guice stack trace collection.
@@ -56,6 +59,15 @@
BRIDGE
}
+ public enum NullableProvidesOption {
+ /** Ignore null parameters to @Provides methods. */
+ IGNORE,
+ /** Warn if null parameters are passed to non-@Nullable parameters of provides methods. */
+ WARN,
+ /** Error if null parameters are passed to non-@Nullable parameters of provides parameters */
+ ERROR
+ }
+
public static IncludeStackTraceOption getIncludeStackTraceOption() {
return INCLUDE_STACK_TRACES;
}
@@ -64,6 +76,10 @@
return CUSTOM_CLASS_LOADING;
}
+ public static NullableProvidesOption getNullableProvidesOption() {
+ return NULLABLE_PROVIDES;
+ }
+
private static IncludeStackTraceOption parseIncludeStackTraceOption() {
return getSystemOption("guice_include_stack_traces",
IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE);
@@ -74,6 +90,11 @@
CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
}
+ private static NullableProvidesOption parseNullableProvidesOption(
+ NullableProvidesOption defaultValue) {
+ return getSystemOption("guice_check_nullable_provides_params", defaultValue);
+ }
+
/**
* Gets the system option indicated by the specified key; runs as a privileged action.
*
diff --git a/core/src/com/google/inject/internal/LookupProcessor.java b/core/src/com/google/inject/internal/LookupProcessor.java
index 3971818..bf11b83 100644
--- a/core/src/com/google/inject/internal/LookupProcessor.java
+++ b/core/src/com/google/inject/internal/LookupProcessor.java
@@ -48,7 +48,7 @@
@Override public <T> Boolean visit(ProviderLookup<T> lookup) {
// ensure the provider can be created
try {
- Provider<T> provider = injector.getProviderOrThrow(lookup.getKey(), errors);
+ Provider<T> provider = injector.getProviderOrThrow(lookup.getDependency(), errors);
lookup.initializeDelegate(provider);
} catch (ErrorsException e) {
errors.merge(e.getErrors()); // TODO: source
diff --git a/core/src/com/google/inject/internal/ProviderMethod.java b/core/src/com/google/inject/internal/ProviderMethod.java
index 1a952ed..beaf406 100644
--- a/core/src/com/google/inject/internal/ProviderMethod.java
+++ b/core/src/com/google/inject/internal/ProviderMethod.java
@@ -24,6 +24,7 @@
import com.google.inject.Key;
import com.google.inject.PrivateBinder;
import com.google.inject.Provider;
+import com.google.inject.Provides;
import com.google.inject.internal.BytecodeGen.Visibility;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.BindingTargetVisitor;
@@ -52,21 +53,28 @@
/**
* Creates a {@link ProviderMethod}.
*
- * <p>Unless {@code skipFastClassGeneration} is set, this will use {@link FastClass} to invoke
- * the actual method, since it is significantly faster. However, this will fail if the method is
- * {@code private} or {@code protected}, since fastclass is subject to java access policies.
+ * <p>Unless {@code skipFastClassGeneration} is set, this will use
+ * {@link net.sf.cglib.reflect.FastClass} to invoke the actual method, since it is significantly
+ * faster. However, this will fail if the method is {@code private} or {@code protected}, since
+ * fastclass is subject to java access policies.
*/
static <T> ProviderMethod<T> create(Key<T> key, Method method, Object instance,
ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
- Class<? extends Annotation> scopeAnnotation, boolean skipFastClassGeneration) {
+ Class<? extends Annotation> scopeAnnotation, boolean skipFastClassGeneration,
+ Annotation annotation) {
int modifiers = method.getModifiers();
/*if[AOP]*/
if (!skipFastClassGeneration && !Modifier.isPrivate(modifiers)
&& !Modifier.isProtected(modifiers)) {
try {
// We use an index instead of FastMethod to save a stack frame.
- return new FastClassProviderMethod<T>(
- key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+ return new FastClassProviderMethod<T>(key,
+ method,
+ instance,
+ dependencies,
+ parameterProviders,
+ scopeAnnotation,
+ annotation);
} catch (net.sf.cglib.core.CodeGenerationException e) {/* fall-through */}
}
/*end[AOP]*/
@@ -76,8 +84,13 @@
method.setAccessible(true);
}
- return new ReflectionProviderMethod<T>(
- key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+ return new ReflectionProviderMethod<T>(key,
+ method,
+ instance,
+ dependencies,
+ parameterProviders,
+ scopeAnnotation,
+ annotation);
}
protected final Object instance;
@@ -88,13 +101,14 @@
private final ImmutableSet<Dependency<?>> dependencies;
private final List<Provider<?>> parameterProviders;
private final boolean exposed;
+ private final Annotation annotation;
/**
* @param method the method to invoke. It's return type must be the same type as {@code key}.
*/
private ProviderMethod(Key<T> key, Method method, Object instance,
ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
- Class<? extends Annotation> scopeAnnotation) {
+ Class<? extends Annotation> scopeAnnotation, Annotation annotation) {
this.key = key;
this.scopeAnnotation = scopeAnnotation;
this.instance = instance;
@@ -102,12 +116,15 @@
this.method = method;
this.parameterProviders = parameterProviders;
this.exposed = method.isAnnotationPresent(Exposed.class);
+ this.annotation = annotation;
}
+ @Override
public Key<T> getKey() {
return key;
}
+ @Override
public Method getMethod() {
return method;
}
@@ -117,9 +134,15 @@
return instance;
}
+ @Override
public Object getEnclosingInstance() {
return instance;
}
+
+ @Override
+ public Annotation getAnnotation() {
+ return annotation;
+ }
public void configure(Binder binder) {
binder = binder.withSource(method);
@@ -137,6 +160,7 @@
}
}
+ @Override
public T get() {
Object[] parameters = new Object[parameterProviders.size()];
for (int i = 0; i < parameters.length; i++) {
@@ -158,10 +182,12 @@
abstract Object doProvision(Object[] parameters)
throws IllegalAccessException, InvocationTargetException;
+ @Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
+ @Override
@SuppressWarnings("unchecked")
public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
@@ -172,15 +198,24 @@
}
@Override public String toString() {
- return "@Provides " + StackTraceElements.forMember(method);
+ String annotationString = annotation.toString();
+ // Show @Provides w/o the com.google.inject prefix.
+ if (annotation.annotationType() == Provides.class) {
+ annotationString = "@Provides";
+ } else if (annotationString.endsWith("()")) {
+ // Remove the common "()" suffix if there are no values.
+ annotationString = annotationString.substring(0, annotationString.length() - 2);
+ }
+ return annotationString + " " + StackTraceElements.forMember(method);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ProviderMethod) {
- ProviderMethod o = (ProviderMethod)obj;
+ ProviderMethod<?> o = (ProviderMethod<?>) obj;
return method.equals(o.method)
- && instance.equals(o.instance);
+ && instance.equals(o.instance)
+ && annotation.equals(o.annotation);
} else {
return false;
}
@@ -191,7 +226,7 @@
// Avoid calling hashCode on 'instance', which is a user-object
// that might not be expecting it.
// (We need to call equals, so we do. But we can avoid hashCode.)
- return Objects.hashCode(method);
+ return Objects.hashCode(method, annotation);
}
/*if[AOP]*/
@@ -208,8 +243,15 @@
Object instance,
ImmutableSet<Dependency<?>> dependencies,
List<Provider<?>> parameterProviders,
- Class<? extends Annotation> scopeAnnotation) {
- super(key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+ Class<? extends Annotation> scopeAnnotation,
+ Annotation annotation) {
+ super(key,
+ method,
+ instance,
+ dependencies,
+ parameterProviders,
+ scopeAnnotation,
+ annotation);
// We need to generate a FastClass for the method's class, not the object's class.
this.fastClass =
BytecodeGen.newFastClass(method.getDeclaringClass(), Visibility.forMember(method));
@@ -241,8 +283,15 @@
Object instance,
ImmutableSet<Dependency<?>> dependencies,
List<Provider<?>> parameterProviders,
- Class<? extends Annotation> scopeAnnotation) {
- super(key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+ Class<? extends Annotation> scopeAnnotation,
+ Annotation annotation) {
+ super(key,
+ method,
+ instance,
+ dependencies,
+ parameterProviders,
+ scopeAnnotation,
+ annotation);
}
@Override Object doProvision(Object[] parameters) throws IllegalAccessException,
diff --git a/core/src/com/google/inject/internal/ProviderMethodsModule.java b/core/src/com/google/inject/internal/ProviderMethodsModule.java
index 214039d..98eb45d 100644
--- a/core/src/com/google/inject/internal/ProviderMethodsModule.java
+++ b/core/src/com/google/inject/internal/ProviderMethodsModule.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.base.Optional;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -28,7 +29,9 @@
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message;
import com.google.inject.util.Modules;
@@ -38,7 +41,7 @@
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
-import java.util.logging.Logger;
+import java.util.Set;
/**
* Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and
@@ -48,23 +51,46 @@
* @author jessewilson@google.com (Jesse Wilson)
*/
public final class ProviderMethodsModule implements Module {
- private static final Key<Logger> LOGGER_KEY = Key.get(Logger.class);
+
+ private static ModuleAnnotatedMethodScanner PROVIDES_BUILDER =
+ new ModuleAnnotatedMethodScanner() {
+ @Override
+ public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+ InjectionPoint injectionPoint) {
+ return key;
+ }
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotationClasses() {
+ return ImmutableSet.of(Provides.class);
+ }
+ };
private final Object delegate;
private final TypeLiteral<?> typeLiteral;
private final boolean skipFastClassGeneration;
+ private final ModuleAnnotatedMethodScanner scanner;
- private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration) {
+ private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration,
+ ModuleAnnotatedMethodScanner scanner) {
this.delegate = checkNotNull(delegate, "delegate");
this.typeLiteral = TypeLiteral.get(this.delegate.getClass());
this.skipFastClassGeneration = skipFastClassGeneration;
+ this.scanner = scanner;
}
/**
* Returns a module which creates bindings for provider methods from the given module.
*/
public static Module forModule(Module module) {
- return forObject(module, false);
+ return forObject(module, false, PROVIDES_BUILDER);
+ }
+
+ /**
+ * Returns a module which creates bindings methods in the module that match the scanner.
+ */
+ public static Module forModule(Module module, ModuleAnnotatedMethodScanner scanner) {
+ return forObject(module, false, scanner);
}
/**
@@ -75,18 +101,20 @@
* are only interested in Module metadata.
*/
public static Module forObject(Object object) {
- return forObject(object, true);
+ return forObject(object, true, PROVIDES_BUILDER);
}
- private static Module forObject(Object object, boolean skipFastClassGeneration) {
+ private static Module forObject(Object object, boolean skipFastClassGeneration,
+ ModuleAnnotatedMethodScanner scanner) {
// avoid infinite recursion, since installing a module always installs itself
if (object instanceof ProviderMethodsModule) {
return Modules.EMPTY_MODULE;
}
- return new ProviderMethodsModule(object, skipFastClassGeneration);
+ return new ProviderMethodsModule(object, skipFastClassGeneration, scanner);
}
+ @Override
public synchronized void configure(Binder binder) {
for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) {
providerMethod.configure(binder);
@@ -107,8 +135,9 @@
&& !method.isBridge() && !method.isSynthetic()) {
methodsBySignature.put(new Signature(method), method);
}
- if (isProvider(method)) {
- result.add(createProviderMethod(binder, method));
+ Optional<Annotation> annotation = isProvider(binder, method);
+ if (annotation.isPresent()) {
+ result.add(createProviderMethod(binder, method, annotation.get()));
}
}
}
@@ -125,9 +154,11 @@
}
// now we know matching signature is in a subtype of method.getDeclaringClass()
if (overrides(matchingSignature, method)) {
+ String annotationString = provider.getAnnotation().annotationType() == Provides.class
+ ? "@Provides" : "@" + provider.getAnnotation().annotationType().getCanonicalName();
binder.addError(
- "Overriding @Provides methods is not allowed."
- + "\n\t@Provides method: %s\n\toverridden by: %s",
+ "Overriding " + annotationString + " methods is not allowed."
+ + "\n\t" + annotationString + " method: %s\n\toverridden by: %s",
method,
matchingSignature);
break;
@@ -143,10 +174,24 @@
* Synthetic bridge methods are excluded. Starting with JDK 8, javac copies annotations onto
* bridge methods (which always have erased signatures).
*/
- private static boolean isProvider(Method method) {
- return !method.isBridge()
- && !method.isSynthetic()
- && method.isAnnotationPresent(Provides.class);
+ private Optional<Annotation> isProvider(Binder binder, Method method) {
+ if (method.isBridge() || method.isSynthetic()) {
+ return Optional.absent();
+ }
+ Annotation annotation = null;
+ for (Class<? extends Annotation> annotationClass : scanner.annotationClasses()) {
+ Annotation foundAnnotation = method.getAnnotation(annotationClass);
+ if (foundAnnotation != null) {
+ if (annotation != null) {
+ binder.addError("More than one annotation claimed by %s on method %s."
+ + " Methods can only have one annotation claimed per scanner.",
+ scanner, method);
+ return Optional.absent();
+ }
+ annotation = foundAnnotation;
+ }
+ }
+ return Optional.fromNullable(annotation);
}
private final class Signature {
@@ -197,42 +242,30 @@
return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage());
}
- private <T> ProviderMethod<T> createProviderMethod(Binder binder, Method method) {
+ private <T> ProviderMethod<T> createProviderMethod(Binder binder, Method method,
+ Annotation annotation) {
binder = binder.withSource(method);
Errors errors = new Errors(method);
// prepare the parameter providers
- List<Dependency<?>> dependencies = Lists.newArrayList();
+ InjectionPoint point = InjectionPoint.forMethod(method, typeLiteral);
+ List<Dependency<?>> dependencies = point.getDependencies();
List<Provider<?>> parameterProviders = Lists.newArrayList();
- List<TypeLiteral<?>> parameterTypes = typeLiteral.getParameterTypes(method);
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- for (int i = 0; i < parameterTypes.size(); i++) {
- Key<?> key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]);
- if (key.equals(LOGGER_KEY)) {
- // If it was a Logger, change the key to be unique & bind it to a
- // provider that provides a logger with a proper name.
- // This solves issue 482 (returning a new anonymous logger on every call exhausts memory)
- Key<Logger> loggerKey = Key.get(Logger.class, UniqueAnnotations.create());
- binder.bind(loggerKey).toProvider(new LogProvider(method));
- key = loggerKey;
- }
- dependencies.add(Dependency.get(key));
- parameterProviders.add(binder.getProvider(key));
+ for (Dependency<?> dependency : point.getDependencies()) {
+ parameterProviders.add(binder.getProvider(dependency));
}
@SuppressWarnings("unchecked") // Define T as the method's return type.
TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
-
Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
+ key = scanner.prepareMethod(binder, annotation, key, point);
Class<? extends Annotation> scopeAnnotation
= Annotations.findScopeAnnotation(errors, method.getAnnotations());
-
for (Message message : errors.getMessages()) {
binder.addError(message);
}
-
return ProviderMethod.create(key, method, delegate, ImmutableSet.copyOf(dependencies),
- parameterProviders, scopeAnnotation, skipFastClassGeneration);
+ parameterProviders, scopeAnnotation, skipFastClassGeneration, annotation);
}
<T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) {
@@ -248,17 +281,4 @@
@Override public int hashCode() {
return delegate.hashCode();
}
-
- /** A provider that returns a logger based on the method name. */
- private static final class LogProvider implements Provider<Logger> {
- private final String name;
-
- public LogProvider(Method method) {
- this.name = method.getDeclaringClass().getName() + "." + method.getName();
- }
-
- public Logger get() {
- return Logger.getLogger(name);
- }
- }
}
diff --git a/core/src/com/google/inject/spi/Elements.java b/core/src/com/google/inject/spi/Elements.java
index f507fe0..986582e 100644
--- a/core/src/com/google/inject/spi/Elements.java
+++ b/core/src/com/google/inject/spi/Elements.java
@@ -302,7 +302,11 @@
}
public <T> Provider<T> getProvider(final Key<T> key) {
- final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), key);
+ return getProvider(Dependency.get(key));
+ }
+
+ public <T> Provider<T> getProvider(final Dependency<T> dependency) {
+ final ProviderLookup<T> element = new ProviderLookup<T>(getElementSource(), dependency);
elements.add(element);
return element.getProvider();
}
diff --git a/core/src/com/google/inject/spi/InjectionPoint.java b/core/src/com/google/inject/spi/InjectionPoint.java
index 653f87c..267e0ab 100644
--- a/core/src/com/google/inject/spi/InjectionPoint.java
+++ b/core/src/com/google/inject/spi/InjectionPoint.java
@@ -307,6 +307,20 @@
}
/**
+ * Returns a new injection point for the specified method of {@code type}.
+ * This is useful for extensions that need to build dependency graphs from
+ * arbitrary methods.
+ *
+ * @param method any single method present on {@code type}.
+ * @param type the concrete type that defines {@code method}.
+ *
+ * @since 4.0
+ */
+ public static <T> InjectionPoint forMethod(Method method, TypeLiteral<T> type) {
+ return new InjectionPoint(type, method, false);
+ }
+
+ /**
* Returns all static method and field injection points on {@code type}.
*
* @return a possibly empty set of injection points. The set has a specified iteration order. All
diff --git a/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
new file mode 100644
index 0000000..99b6d91
--- /dev/null
+++ b/core/src/com/google/inject/spi/ModuleAnnotatedMethodScanner.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2015 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.spi;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.internal.ProviderMethodsModule;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+/**
+ * Allows extensions to scan modules for annotated methods and bind those methods
+ * as providers, similar to {@code @Provides} methods.
+ */
+public abstract class ModuleAnnotatedMethodScanner {
+
+ /**
+ * Scans the module for methods and returns a module that will bind the methods
+ * that match this scanner.
+ */
+ public final Module forModule(Module module) {
+ return ProviderMethodsModule.forModule(module, this);
+ }
+
+ /**
+ * Returns the annotations this should scan for. Every method in the module that has one of these
+ * annotations will create a Provider binding, with the return value of the binding being what's
+ * provided and the parameters of the method being dependencies of the provider.
+ */
+ public abstract Set<? extends Class<? extends Annotation>> annotationClasses();
+
+ /**
+ * Prepares a method for binding. This {@code key} parameter is the key discovered from looking at
+ * the binding annotation and return value of the method. Implementations can modify the key to
+ * instead bind to another key. For example, Multibinder may want to change
+ * {@code @SetProvides String provideFoo()} to bind into a unique Key within the multibinder
+ * instead of binding {@code String}.
+ *
+ * <p>The injection point and annotation are provided in case the implementation wants to set the
+ * key based on the property of the annotation or if any additional preparation is needed for any
+ * of the dependencies. The annotation is guaranteed to be an instance of one the classes returned
+ * by {@link #annotationClasses}.
+ */
+ public abstract <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+ InjectionPoint injectionPoint);
+
+}
diff --git a/core/src/com/google/inject/spi/ProviderLookup.java b/core/src/com/google/inject/spi/ProviderLookup.java
index e4b5da6..a232431 100644
--- a/core/src/com/google/inject/spi/ProviderLookup.java
+++ b/core/src/com/google/inject/spi/ProviderLookup.java
@@ -39,12 +39,17 @@
*/
public final class ProviderLookup<T> implements Element {
private final Object source;
- private final Key<T> key;
+ private final Dependency<T> dependency;
private Provider<T> delegate;
public ProviderLookup(Object source, Key<T> key) {
+ this(source, Dependency.get(checkNotNull(key, "key")));
+ }
+
+ /** @since 4.0 */
+ public ProviderLookup(Object source, Dependency<T> dependency) {
this.source = checkNotNull(source, "source");
- this.key = checkNotNull(key, "key");
+ this.dependency = checkNotNull(dependency, "dependency");
}
public Object getSource() {
@@ -52,7 +57,11 @@
}
public Key<T> getKey() {
- return key;
+ return dependency.getKey();
+ }
+
+ public Dependency<T> getDependency() {
+ return dependency;
}
public <T> T acceptVisitor(ElementVisitor<T> visitor) {
@@ -70,7 +79,7 @@
}
public void applyTo(Binder binder) {
- initializeDelegate(binder.withSource(getSource()).getProvider(key));
+ initializeDelegate(binder.withSource(getSource()).getProvider(dependency));
}
/**
@@ -93,16 +102,16 @@
"This Provider cannot be used until the Injector has been created.");
return delegate.get();
}
-
+
public Set<Dependency<?>> getDependencies() {
// We depend on Provider<T>, not T directly. This is an important distinction
// for dependency analysis tools that short-circuit on providers.
- Key<?> providerKey = key.ofType(Types.providerOf(key.getTypeLiteral().getType()));
+ Key<?> providerKey = getKey().ofType(Types.providerOf(getKey().getTypeLiteral().getType()));
return ImmutableSet.<Dependency<?>>of(Dependency.get(providerKey));
}
@Override public String toString() {
- return "Provider<" + key.getTypeLiteral() + ">";
+ return "Provider<" + getKey().getTypeLiteral() + ">";
}
};
}
diff --git a/core/src/com/google/inject/spi/ProvidesMethodBinding.java b/core/src/com/google/inject/spi/ProvidesMethodBinding.java
index 010e936..a862fcc 100644
--- a/core/src/com/google/inject/spi/ProvidesMethodBinding.java
+++ b/core/src/com/google/inject/spi/ProvidesMethodBinding.java
@@ -19,11 +19,13 @@
import com.google.inject.Key;
import com.google.inject.Provides;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
- * An {@literal @}{@link Provides} binding.
- *
+ * An {@literal @}{@link Provides} binding or binding produced by a
+ * {@link ModuleAnnotatedMethodScanner}.
+ *
* @since 4.0
* @author sameb@google.com (Sam Berlin)
*/
@@ -37,4 +39,12 @@
/** Returns the key of the binding. */
Key<T> getKey();
+
+ /**
+ * Returns the annotation that caused this binding to be created. For {@code @Provides} methods,
+ * this is an instance of the {@code @Provides} annotation. For bindings from
+ * {@link ModuleAnnotatedMethodScanner}, this is the annotation that caused the scanner to produce
+ * the binding.
+ */
+ Annotation getAnnotation();
}
diff --git a/core/test/com/google/inject/AllTests.java b/core/test/com/google/inject/AllTests.java
index e7ca9da..f700608 100644
--- a/core/test/com/google/inject/AllTests.java
+++ b/core/test/com/google/inject/AllTests.java
@@ -31,6 +31,7 @@
import com.google.inject.spi.HasDependenciesTest;
import com.google.inject.spi.InjectionPointTest;
import com.google.inject.spi.InjectorSpiTest;
+import com.google.inject.spi.ModuleAnnotatedMethodScannerTest;
import com.google.inject.spi.ModuleRewriterTest;
import com.google.inject.spi.ModuleSourceTest;
import com.google.inject.spi.ProviderMethodsTest;
@@ -86,6 +87,7 @@
suite.addTestSuite(MembersInjectorTest.class);
suite.addTestSuite(ModulesTest.class);
suite.addTestSuite(ModuleTest.class);
+ suite.addTestSuite(ModuleAnnotatedMethodScannerTest.class);
suite.addTestSuite(NullableInjectionPointTest.class);
suite.addTestSuite(OptionalBindingTest.class);
suite.addTestSuite(OverrideModuleTest.class);
diff --git a/core/test/com/google/inject/ScopesTest.java b/core/test/com/google/inject/ScopesTest.java
index fd7a4eb..59aa596 100644
--- a/core/test/com/google/inject/ScopesTest.java
+++ b/core/test/com/google/inject/ScopesTest.java
@@ -162,6 +162,22 @@
interface A {}
static class AImpl implements A {}
+ @Retention(RUNTIME)
+ @interface Component {}
+
+ @Component
+ @Singleton
+ interface ComponentAnnotationTest {}
+ static class ComponentAnnotationTestImpl implements ComponentAnnotationTest {}
+
+ public void testScopingAnnotationsOnAbstractTypeIsValidForComponent() {
+ Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bind(ComponentAnnotationTest.class).to(ComponentAnnotationTestImpl.class);
+ }
+ });
+ }
+
public void testScopingAnnotationsOnAbstractTypeViaImplementedBy() {
try {
Guice.createInjector().getInstance(D.class);
diff --git a/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java
new file mode 100644
index 0000000..62f8220
--- /dev/null
+++ b/core/test/com/google/inject/spi/ModuleAnnotatedMethodScannerTest.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2015 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.spi;
+
+import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Binding;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.internal.util.StackTraceElements;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Set;
+
+/** Tests for {@link ModuleAnnotatedMethodScanner} usage. */
+public class ModuleAnnotatedMethodScannerTest extends TestCase {
+
+ public void testScanning() throws Exception {
+ Module module = new AbstractModule() {
+ @Override protected void configure() {
+ install(new NamedMunger().forModule(this));
+ }
+
+ @TestProvides @Named("foo") String foo() {
+ return "foo";
+ }
+
+ @TestProvides @Named("foo2") String foo2() {
+ return "foo2";
+ }
+ };
+ Injector injector = Guice.createInjector(module);
+
+ // assert no bindings named "foo" or "foo2" exist -- they were munged.
+ assertNull(injector.getExistingBinding(Key.get(String.class, named("foo"))));
+ assertNull(injector.getExistingBinding(Key.get(String.class, named("foo2"))));
+
+ Binding<String> fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged")));
+ Binding<String> foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged")));
+ assertEquals("foo", fooBinding.getProvider().get());
+ assertEquals("foo2", foo2Binding.getProvider().get());
+
+ // Validate the provider has a sane toString
+ assertEquals(methodName(TestProvides.class, "foo", module),
+ fooBinding.getProvider().toString());
+ assertEquals(methodName(TestProvides.class, "foo2", module),
+ foo2Binding.getProvider().toString());
+ }
+
+ public void testMoreThanOneClaimedAnnotationFails() throws Exception {
+ final NamedMunger scanner = new NamedMunger();
+ Module module = new AbstractModule() {
+ @Override protected void configure() {
+ install(scanner.forModule(this));
+ }
+
+ @TestProvides @TestProvides2 String foo() {
+ return "foo";
+ }
+ };
+ try {
+ Guice.createInjector(module);
+ fail();
+ } catch(CreationException expected) {
+ assertEquals(1, expected.getErrorMessages().size());
+ assertContains(expected.getMessage(),
+ "More than one annotation claimed by " + scanner + " on method "
+ + module.getClass().getName() + ".foo(). Methods can only have "
+ + "one annotation claimed per scanner.");
+ }
+ }
+
+ private String methodName(Class<? extends Annotation> annotation, String method, Object container)
+ throws Exception {
+ return "@" + annotation.getName() + " "
+ + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method));
+ }
+
+ @Documented @Target(METHOD) @Retention(RUNTIME)
+ private @interface TestProvides {}
+
+ @Documented @Target(METHOD) @Retention(RUNTIME)
+ private @interface TestProvides2 {}
+
+ private static class NamedMunger extends ModuleAnnotatedMethodScanner {
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotationClasses() {
+ return ImmutableSet.of(TestProvides.class, TestProvides2.class);
+ }
+
+ @Override
+ public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+ InjectionPoint injectionPoint) {
+ return Key.get(key.getTypeLiteral(),
+ Names.named(((Named) key.getAnnotation()).value() + "-munged"));
+ }
+ }
+}
diff --git a/core/test/com/google/inject/spi/ProviderMethodsTest.java b/core/test/com/google/inject/spi/ProviderMethodsTest.java
index cf08e46..5814967 100644
--- a/core/test/com/google/inject/spi/ProviderMethodsTest.java
+++ b/core/test/com/google/inject/spi/ProviderMethodsTest.java
@@ -21,6 +21,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
@@ -32,24 +34,33 @@
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
+import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.inject.Stage;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.InternalFlags;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import junit.framework.TestCase;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
import java.util.logging.Logger;
/**
@@ -211,7 +222,7 @@
public void testGenericProviderMethods() {
Injector injector = Guice.createInjector(
new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {});
-
+
assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First"))));
assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second"))));
assertEquals(ImmutableSet.of("A", "B"),
@@ -246,7 +257,7 @@
return ImmutableSet.of(first, second);
}
}
-
+
public void testAutomaticProviderMethods() {
Injector injector = Guice.createInjector((Module) new AbstractModule() {
@Override protected void configure() { }
@@ -281,7 +292,7 @@
Injector injector = Guice.createInjector(installsSelf);
assertEquals("A5", injector.getInstance(String.class));
}
-
+
public void testWildcardProviderMethods() {
final List<String> strings = ImmutableList.of("A", "B", "C");
final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3);
@@ -312,8 +323,8 @@
@Inject Class<?> type;
}
- public void testProviderMethodDependenciesAreExposed() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ public void testProviderMethodDependenciesAreExposed() throws Exception {
+ Module module = new AbstractModule() {
@Override protected void configure() {
bind(Integer.class).toInstance(50);
bindConstant().annotatedWith(Names.named("units")).to("Kg");
@@ -321,13 +332,18 @@
@Provides @Named("weight") String provideWeight(Integer count, @Named("units") String units) {
return count + units;
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
ProviderInstanceBinding<?> binding = (ProviderInstanceBinding<?>) injector.getBinding(
Key.get(String.class, Names.named("weight")));
- assertEquals(ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Integer.class)),
- Dependency.get(Key.get(String.class, Names.named("units")))),
- binding.getDependencies());
+ Method method =
+ module.getClass().getDeclaredMethod("provideWeight", Integer.class, String.class);
+ InjectionPoint point = new InjectionPoint(TypeLiteral.get(module.getClass()), method, false);
+ assertEquals(ImmutableSet.<Dependency<?>>of(
+ new Dependency<Integer>(point, Key.get(Integer.class), false, 0),
+ new Dependency<String>(point, Key.get(String.class, Names.named("units")), false, 1)),
+ binding.getDependencies());
}
public void testNonModuleProviderMethods() {
@@ -357,9 +373,9 @@
element instanceof ProviderInstanceBinding);
ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
- javax.inject.Provider provider = binding.getUserSuppliedProvider();
+ javax.inject.Provider provider = binding.getUserSuppliedProvider();
assertTrue(provider instanceof ProviderMethod);
- assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
+ assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
assertSame(provider, binding.getProviderInstance());
}
@@ -372,29 +388,29 @@
});
fail();
} catch (CreationException expected) {
- assertContains(expected.getMessage(),
+ assertContains(expected.getMessage(),
"1) Provider methods must return a value. Do not return void.",
getClass().getName(), ".provideFoo(ProviderMethodsTest.java:");
}
}
-
+
public void testInjectsJustOneLogger() {
AtomicReference<Logger> loggerRef = new AtomicReference<Logger>();
Injector injector = Guice.createInjector(new FooModule(loggerRef));
-
+
assertNull(loggerRef.get());
injector.getInstance(Integer.class);
Logger lastLogger = loggerRef.getAndSet(null);
assertNotNull(lastLogger);
injector.getInstance(Integer.class);
assertSame(lastLogger, loggerRef.get());
-
- assertEquals(FooModule.class.getName() + ".foo", lastLogger.getName());
+
+ assertEquals(FooModule.class.getName(), lastLogger.getName());
}
-
+
private static class FooModule extends AbstractModule {
private final AtomicReference<Logger> loggerRef;
-
+
public FooModule(AtomicReference<Logger> loggerRef) {
this.loggerRef = loggerRef;
}
@@ -407,7 +423,7 @@
return 42;
}
}
-
+
public void testSpi() throws Exception {
Module m1 = new AbstractModule() {
@Override protected void configure() {}
@@ -418,7 +434,7 @@
@Provides Integer provideInt(@Named("foo") String dep) { return 42; }
};
Injector injector = Guice.createInjector(m1, m2);
-
+
Binding<String> stringBinding =
injector.getBinding(Key.get(String.class, Names.named("foo")));
ProvidesMethodBinding<String> stringMethod =
@@ -429,7 +445,7 @@
assertEquals(((HasDependencies) stringBinding).getDependencies(),
stringMethod.getDependencies());
assertEquals(Key.get(String.class, Names.named("foo")), stringMethod.getKey());
-
+
Binding<Integer> intBinding = injector.getBinding(Integer.class);
ProvidesMethodBinding<Integer> intMethod =
intBinding.acceptTargetVisitor(new BindingCapturer<Integer>());
@@ -439,21 +455,21 @@
assertEquals(((HasDependencies) intBinding).getDependencies(),
intMethod.getDependencies());
assertEquals(Key.get(Integer.class), intMethod.getKey());
-
+
}
-
+
private static class BindingCapturer<T> extends DefaultBindingTargetVisitor<T, ProvidesMethodBinding<T>>
implements ProvidesMethodTargetVisitor<T, ProvidesMethodBinding<T>> {
-
+
@SuppressWarnings("unchecked")
public ProvidesMethodBinding<T> visit(
ProvidesMethodBinding<? extends T> providesMethodBinding) {
return (ProvidesMethodBinding<T>)providesMethodBinding;
}
-
+
@Override protected ProvidesMethodBinding<T> visitOther(Binding<? extends T> binding) {
throw new IllegalStateException("unexpected visit of: " + binding);
- }
+ }
}
public void testProvidesMethodVisibility() {
@@ -495,7 +511,7 @@
fail("Expected injector creation failure");
} catch (CreationException expected) {
// both of our super class bindings cause errors
- assertContains(expected.getMessage(),
+ assertContains(expected.getMessage(),
"A binding to java.lang.Long was already configured",
"A binding to java.lang.Integer was already configured");
}
@@ -561,14 +577,14 @@
public void testShareFastClassWithSuperClass() {
CallerInspecterSubClassModule module = new CallerInspecterSubClassModule();
Guice.createInjector(Stage.PRODUCTION, module);
- assertEquals("Expected provider methods in the same class to share fastclass classes",
+ assertEquals("Expected provider methods in the same class to share fastclass classes",
module.fooCallerClass, module.barCallerClass);
assertFalse(
"Did not expect provider methods in the subclasses to share fastclass classes "
+ "with their parent classes",
module.bazCallerClass.equals(module.barCallerClass));
}
-
+
private static class CallerInspecterSubClassModule extends CallerInspecterModule {
String bazCallerClass;
@@ -594,7 +610,7 @@
@Provides @Named("unrawlist") List<String> rawParameterProvider(@Named("rawlist") List f) {
return f;
}
-
+
@Provides @Named("list") List<String> annotatedGenericProviderMethod() {
return new ArrayList<String>();
}
@@ -617,7 +633,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -634,7 +650,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -651,7 +667,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -667,13 +683,13 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
-
+
public void testOverrideProviderMethod_covariantOverrideDoesntHaveProvides() {
class SubClassModule extends SuperClassModule {
@@ -685,7 +701,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -702,7 +718,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
@@ -747,11 +763,11 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
- "@Provides method: " + SuperClassModule.class.getName()
+ "@Provides method: " + SuperClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()",
- "overridden by: " + SubClassModule.class.getName()
+ "overridden by: " + SubClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()");
}
}
@@ -767,7 +783,7 @@
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
- assertContains(e.getMessage(),
+ assertContains(e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".rawProvider()",
"overridden by: " + SubClassModule.class.getName() + ".rawProvider()");
@@ -836,4 +852,101 @@
public void testIgnoreSyntheticBridgeMethods() {
Guice.createInjector(new ModuleImpl());
}
+
+ public void testNullability() throws Exception {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(String.class).toProvider(Providers.<String>of(null));
+ }
+
+ @SuppressWarnings("unused")
+ @Provides
+ Integer fail(String foo) {
+ return 1;
+ }
+
+ @SuppressWarnings("unused")
+ @Provides
+ Long succeed(@Nullable String foo) {
+ return 2L;
+ }
+ };
+ Injector injector = Guice.createInjector(module);
+ InjectionPoint fooPoint = InjectionPoint.forMethod(
+ module.getClass().getDeclaredMethod("fail", String.class),
+ TypeLiteral.get(module.getClass()));
+ Dependency<?> fooDependency = Iterables.getOnlyElement(fooPoint.getDependencies());
+
+ runNullableTest(injector, fooDependency, module);
+
+ injector.getInstance(Long.class);
+ }
+
+ private void runNullableTest(Injector injector, Dependency<?> dependency, Module module) {
+ switch (InternalFlags.getNullableProvidesOption()) {
+ case ERROR:
+ validateNullableFails(injector, module);
+ break;
+ case IGNORE:
+ validateNullableIgnored(injector);
+ break;
+ case WARN:
+ validateNullableWarns(injector, dependency);
+ break;
+ }
+ }
+
+ private void validateNullableFails(Injector injector, Module module) {
+ try {
+ injector.getInstance(Integer.class);
+ fail();
+ } catch (ProvisionException expected) {
+ assertContains(expected.getMessage(),
+ "1) null returned by binding at " + module.getClass().getName() + ".configure(",
+ "but parameter 0 of " + module.getClass().getName() + ".fail() is not @Nullable",
+ "while locating java.lang.String",
+ "for parameter 0 at " + module.getClass().getName() + ".fail(",
+ "while locating java.lang.Integer");
+
+ assertEquals(1, expected.getErrorMessages().size());
+ }
+ }
+
+ private void validateNullableIgnored(Injector injector) {
+ injector.getInstance(Integer.class); // no exception
+ }
+
+ private void validateNullableWarns(Injector injector, Dependency<?> dependency) {
+ final List<LogRecord> logRecords = Lists.newArrayList();
+ final Handler fakeHandler = new Handler() {
+ @Override
+ public void publish(LogRecord logRecord) {
+ logRecords.add(logRecord);
+ }
+ @Override
+ public void flush() {}
+ @Override
+ public void close() throws SecurityException {}
+ };
+ Logger.getLogger(Guice.class.getName()).addHandler(fakeHandler);
+ try {
+ injector.getInstance(Integer.class); // no exception, but assert it does log.
+ LogRecord record = Iterables.getOnlyElement(logRecords);
+ assertEquals(
+ "Guice injected null into parameter {0} of {1} (a {2}), please mark it @Nullable."
+ + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ + " error.",
+ record.getMessage());
+ assertEquals(dependency.getParameterIndex(), record.getParameters()[0]);
+ assertEquals(Errors.convert(dependency.getInjectionPoint().getMember()),
+ record.getParameters()[1]);
+ assertEquals(Errors.convert(dependency.getKey()), record.getParameters()[2]);
+ } finally {
+ Logger.getLogger(Guice.class.getName()).removeHandler(fakeHandler);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Nullable {}
}