update to 5.0.1, part one (map binder, assisted inject, error messages)

This commit is contained in:
Jörg Prante 2021-07-28 10:01:37 +02:00
parent 9f72318aae
commit 7e942c813e
56 changed files with 6906 additions and 4022 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib group = org.xbib
name = guice name = guice
version = 4.4.2.0 version = 5.0.1.0
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL
gradle.wrapper.version = 6.6.1 gradle.wrapper.version = 6.6.1
@ -8,5 +8,5 @@ javax-inject.version = 1
guava.version = 30.1 guava.version = 30.1
# test # test
junit.version = 5.7.2 junit.version = 5.7.2
junit4.version = 4.13 junit4.version = 4.13.2
log4j.version = 2.14.1 log4j.version = 2.14.1

View file

@ -1,24 +1,20 @@
package com.google.inject; package com.google.inject;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.MoreTypes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.internal.Annotations.generateAnnotation; import static com.google.inject.internal.Annotations.generateAnnotation;
import static com.google.inject.internal.Annotations.isAllDefaultMethods; import static com.google.inject.internal.Annotations.isAllDefaultMethods;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.MoreTypes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
/** /**
* Binding key consisting of an injection type and an optional annotation. * Guice uses Key objects to identify a dependency that can be resolved by the Guice {@link
* Matches the type and annotation at a point of injection. * Injector}. A Guice key consists of an injection type and an optional annotation.
* *
* <p>For example, {@code Key.get(Service.class, Transactional.class)} will * <p>For example, {@code Key.get(Service.class, Transactional.class)} will match:
* match:
* *
* <pre> * <pre>
* {@literal @}Inject * {@literal @}Inject
@ -27,21 +23,20 @@ import static com.google.inject.internal.Annotations.isAllDefaultMethods;
* } * }
* </pre> * </pre>
* *
* <p>{@code Key} supports generic types via subclassing just like {@link * <p>{@code Key} supports generic types via subclassing just like {@link TypeLiteral}.
* TypeLiteral}.
* *
* <p>Keys do not differentiate between primitive types (int, char, etc.) and * <p>Keys do not differentiate between primitive types (int, char, etc.) and their corresponding
* their corresponding wrapper types (Integer, Character, etc.). Primitive * wrapper types (Integer, Character, etc.). Primitive types will be replaced with their wrapper
* types will be replaced with their wrapper types when keys are created. * types when keys are created.
*
* @author crazybob@google.com (Bob Lee)
*/ */
public class Key<T> { public class Key<T> {
private final AnnotationStrategy annotationStrategy; private final AnnotationStrategy annotationStrategy;
private final TypeLiteral<T> typeLiteral; private final TypeLiteral<T> typeLiteral;
private final int hashCode; private final int hashCode;
// This field is updated using the 'Data-Race-Ful' lazy intialization pattern // This field is updated using the 'Data-Race-Ful' lazy intialization pattern
// See http://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html for a detailed // See http://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html for a detailed
// explanation. // explanation.
@ -50,19 +45,18 @@ public class Key<T> {
/** /**
* Constructs a new key. Derives the type from this class's type parameter. * Constructs a new key. Derives the type from this class's type parameter.
* *
* <p>Clients create an empty anonymous subclass. Doing so embeds the type * <p>Clients create an empty anonymous subclass. Doing so embeds the type parameter in the
* parameter in the anonymous class's type hierarchy so we can reconstitute it * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure.
* at runtime despite erasure.
* *
* <p>Example usage for a binding of type {@code Foo} annotated with * <p>Example usage for a binding of type {@code Foo} annotated with {@code @Bar}:
* {@code @Bar}:
* *
* <p>{@code new Key<Foo>(Bar.class) {}}. * <p>{@code new Key<Foo>(Bar.class) {}}.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Key(Class<? extends Annotation> annotationType) { protected Key(Class<? extends Annotation> annotationType) {
this.annotationStrategy = strategyFor(annotationType); this.annotationStrategy = strategyFor(annotationType);
this.typeLiteral = MoreTypes.canonicalizeForKey( this.typeLiteral =
MoreTypes.canonicalizeForKey(
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass())); (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
this.hashCode = computeHashCode(); this.hashCode = computeHashCode();
} }
@ -70,12 +64,10 @@ public class Key<T> {
/** /**
* Constructs a new key. Derives the type from this class's type parameter. * Constructs a new key. Derives the type from this class's type parameter.
* *
* <p>Clients create an empty anonymous subclass. Doing so embeds the type * <p>Clients create an empty anonymous subclass. Doing so embeds the type parameter in the
* parameter in the anonymous class's type hierarchy so we can reconstitute it * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure.
* at runtime despite erasure.
* *
* <p>Example usage for a binding of type {@code Foo} annotated with * <p>Example usage for a binding of type {@code Foo} annotated with {@code @Bar}:
* {@code @Bar}:
* *
* <p>{@code new Key<Foo>(new Bar()) {}}. * <p>{@code new Key<Foo>(new Bar()) {}}.
*/ */
@ -83,7 +75,8 @@ public class Key<T> {
protected Key(Annotation annotation) { protected Key(Annotation annotation) {
// no usages, not test-covered // no usages, not test-covered
this.annotationStrategy = strategyFor(annotation); this.annotationStrategy = strategyFor(annotation);
this.typeLiteral = MoreTypes.canonicalizeForKey( this.typeLiteral =
MoreTypes.canonicalizeForKey(
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass())); (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
this.hashCode = computeHashCode(); this.hashCode = computeHashCode();
} }
@ -91,9 +84,8 @@ public class Key<T> {
/** /**
* Constructs a new key. Derives the type from this class's type parameter. * Constructs a new key. Derives the type from this class's type parameter.
* *
* <p>Clients create an empty anonymous subclass. Doing so embeds the type * <p>Clients create an empty anonymous subclass. Doing so embeds the type parameter in the
* parameter in the anonymous class's type hierarchy so we can reconstitute it * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure.
* at runtime despite erasure.
* *
* <p>Example usage for a binding of type {@code Foo}: * <p>Example usage for a binding of type {@code Foo}:
* *
@ -102,14 +94,13 @@ public class Key<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Key() { protected Key() {
this.annotationStrategy = NullAnnotationStrategy.INSTANCE; this.annotationStrategy = NullAnnotationStrategy.INSTANCE;
this.typeLiteral = MoreTypes.canonicalizeForKey( this.typeLiteral =
MoreTypes.canonicalizeForKey(
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass())); (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
this.hashCode = computeHashCode(); this.hashCode = computeHashCode();
} }
/** /** Unsafe. Constructs a key from a manually specified type. */
* Unsafe. Constructs a key from a manually specified type.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Key(Type type, AnnotationStrategy annotationStrategy) { private Key(Type type, AnnotationStrategy annotationStrategy) {
this.annotationStrategy = annotationStrategy; this.annotationStrategy = annotationStrategy;
@ -117,154 +108,37 @@ public class Key<T> {
this.hashCode = computeHashCode(); this.hashCode = computeHashCode();
} }
/** /** Constructs a key from a manually specified type. */
* Constructs a key from a manually specified type.
*/
private Key(TypeLiteral<T> typeLiteral, AnnotationStrategy annotationStrategy) { private Key(TypeLiteral<T> typeLiteral, AnnotationStrategy annotationStrategy) {
this.annotationStrategy = annotationStrategy; this.annotationStrategy = annotationStrategy;
this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral); this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral);
this.hashCode = computeHashCode(); this.hashCode = computeHashCode();
} }
/** /** Computes the hash code for this key. */
* Gets a key for an injection type and an annotation strategy.
*/
static <T> Key<T> get(Class<T> type, AnnotationStrategy annotationStrategy) {
return new Key<T>(type, annotationStrategy);
}
/**
* Gets a key for an injection type.
*/
public static <T> Key<T> get(Class<T> type) {
return new Key<T>(type, NullAnnotationStrategy.INSTANCE);
}
/**
* Gets a key for an injection type and an annotation type.
*/
public static <T> Key<T> get(Class<T> type, Class<? extends Annotation> annotationType) {
return new Key<T>(type, strategyFor(annotationType));
}
/**
* Gets a key for an injection type and an annotation.
*/
public static <T> Key<T> get(Class<T> type, Annotation annotation) {
return new Key<T>(type, strategyFor(annotation));
}
/**
* Gets a key for an injection type.
*/
public static Key<?> get(Type type) {
return new Key<Object>(type, NullAnnotationStrategy.INSTANCE);
}
/**
* Gets a key for an injection type and an annotation type.
*/
public static Key<?> get(Type type, Class<? extends Annotation> annotationType) {
return new Key<Object>(type, strategyFor(annotationType));
}
/**
* Gets a key for an injection type and an annotation.
*/
public static Key<?> get(Type type, Annotation annotation) {
return new Key<Object>(type, strategyFor(annotation));
}
/**
* Gets a key for an injection type.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
return new Key<T>(typeLiteral, NullAnnotationStrategy.INSTANCE);
}
/**
* Gets a key for an injection type and an annotation type.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Class<? extends Annotation> annotationType) {
return new Key<T>(typeLiteral, strategyFor(annotationType));
}
/**
* Gets a key for an injection type and an annotation.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Annotation annotation) {
return new Key<T>(typeLiteral, strategyFor(annotation));
}
/**
* Gets the strategy for an annotation.
*/
static AnnotationStrategy strategyFor(Annotation annotation) {
checkNotNull(annotation, "annotation");
Class<? extends Annotation> annotationType = annotation.annotationType();
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
if (Annotations.isMarker(annotationType)) {
return new AnnotationTypeStrategy(annotationType, annotation);
}
return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
}
/**
* Gets the strategy for an annotation type.
*/
static AnnotationStrategy strategyFor(Class<? extends Annotation> annotationType) {
annotationType = Annotations.canonicalizeIfNamed(annotationType);
if (isAllDefaultMethods(annotationType)) {
return strategyFor(generateAnnotation(annotationType));
}
checkNotNull(annotationType, "annotation type");
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
return new AnnotationTypeStrategy(annotationType, null);
}
private static void ensureRetainedAtRuntime(
Class<? extends Annotation> annotationType) {
checkArgument(Annotations.isRetainedAtRuntime(annotationType),
"%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
annotationType.getName());
}
private static void ensureIsBindingAnnotation(Class<? extends Annotation> annotationType) {
checkArgument(Annotations.isBindingAnnotation(annotationType),
"%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
annotationType.getName());
}
/**
* Computes the hash code for this key.
*/
private int computeHashCode() { private int computeHashCode() {
return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode();
} }
/** /** Gets the key type. */
* Gets the key type.
*/
public final TypeLiteral<T> getTypeLiteral() { public final TypeLiteral<T> getTypeLiteral() {
return typeLiteral; return typeLiteral;
} }
/** /** Gets the annotation type. Will be {@code null} if this key lacks an annotation. */
* Gets the annotation type.
*/
public final Class<? extends Annotation> getAnnotationType() { public final Class<? extends Annotation> getAnnotationType() {
return annotationStrategy.getAnnotationType(); return annotationStrategy.getAnnotationType();
} }
/** /**
* Gets the annotation. * Gets the annotation instance if available. Will be {@code null} if this key lacks an annotation
* <i>or</i> the key was constructed with a {@code Class<Annotation>}.
*
* <p><b>Warning:</b> this can return null even if this key is annotated. To check whether a
* {@code Key} has an annotation use {@link #hasAnnotationType} instead.
*/ */
// TODO(diamondm) consider deprecating this in favor of a method that ISEs if hasAnnotationType()
// is true but this would return null.
public final Annotation getAnnotation() { public final Annotation getAnnotation() {
return annotationStrategy.getAnnotation(); return annotationStrategy.getAnnotation();
} }
@ -287,9 +161,7 @@ public class Key<T> {
return typeLiteral.getRawType(); return typeLiteral.getRawType();
} }
/** /** Gets the key of this key's provider. */
* Gets the key of this key's provider.
*/
Key<Provider<T>> providerKey() { Key<Provider<T>> providerKey() {
return ofType(typeLiteral.providerType()); return ofType(typeLiteral.providerType());
} }
@ -324,46 +196,178 @@ public class Key<T> {
return local; return local;
} }
/** /** Gets a key for an injection type and an annotation strategy. */
* Returns a new key of the specified type with the same annotation as this static <T> Key<T> get(Class<T> type, AnnotationStrategy annotationStrategy) {
* key.
*/
public <T> Key<T> ofType(Class<T> type) {
return new Key<T>(type, annotationStrategy); return new Key<T>(type, annotationStrategy);
} }
/** Gets a key for an injection type. */
public static <T> Key<T> get(Class<T> type) {
return new Key<T>(type, NullAnnotationStrategy.INSTANCE);
}
/** Gets a key for an injection type and an annotation type. */
public static <T> Key<T> get(Class<T> type, Class<? extends Annotation> annotationType) {
return new Key<T>(type, strategyFor(annotationType));
}
/** Gets a key for an injection type and an annotation. */
public static <T> Key<T> get(Class<T> type, Annotation annotation) {
return new Key<T>(type, strategyFor(annotation));
}
/** Gets a key for an injection type. */
public static Key<?> get(Type type) {
return new Key<>(type, NullAnnotationStrategy.INSTANCE);
}
/** Gets a key for an injection type and an annotation type. */
public static Key<?> get(Type type, Class<? extends Annotation> annotationType) {
return new Key<>(type, strategyFor(annotationType));
}
/** Gets a key for an injection type and an annotation. */
public static Key<?> get(Type type, Annotation annotation) {
return new Key<>(type, strategyFor(annotation));
}
/** Gets a key for an injection type. */
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
return new Key<T>(typeLiteral, NullAnnotationStrategy.INSTANCE);
}
/** Gets a key for an injection type and an annotation type. */
public static <T> Key<T> get(
TypeLiteral<T> typeLiteral, Class<? extends Annotation> annotationType) {
return new Key<T>(typeLiteral, strategyFor(annotationType));
}
/** Gets a key for an injection type and an annotation. */
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Annotation annotation) {
return new Key<T>(typeLiteral, strategyFor(annotation));
}
/** /**
* Returns a new key of the specified type with the same annotation as this * Returns a new key of the specified type with the same annotation as this key.
* key. *
* @since 3.0
*/
public <U> Key<U> ofType(Class<U> type) {
return new Key<>(type, annotationStrategy);
}
/**
* Returns a new key of the specified type with the same annotation as this key.
*
* @since 3.0
*/ */
public Key<?> ofType(Type type) { public Key<?> ofType(Type type) {
return new Key<Object>(type, annotationStrategy); return new Key<>(type, annotationStrategy);
} }
/** /**
* Returns a new key of the specified type with the same annotation as this * Returns a new key of the specified type with the same annotation as this key.
* key. *
* @since 3.0
*/ */
public <T> Key<T> ofType(TypeLiteral<T> type) { public <U> Key<U> ofType(TypeLiteral<U> type) {
return new Key<T>(type, annotationStrategy); return new Key<U>(type, annotationStrategy);
}
/**
* Returns a new key of the same type with the specified annotation.
*
* <p>This is equivalent to {@code Key.get(key.getTypeLiteral(), annotation)} but may be more
* convenient to use in certain cases.
*
* @since 5.0
*/
public Key<T> withAnnotation(Class<? extends Annotation> annotationType) {
return new Key<T>(typeLiteral, strategyFor(annotationType));
}
/**
* Returns a new key of the same type with the specified annotation.
*
* <p>This is equivalent to {@code Key.get(key.getTypeLiteral(), annotation)} but may be more
* convenient to use in certain cases.
*
* @since 5.0
*/
public Key<T> withAnnotation(Annotation annotation) {
return new Key<T>(typeLiteral, strategyFor(annotation));
} }
/** /**
* Returns true if this key has annotation attributes. * Returns true if this key has annotation attributes.
*
* @since 3.0
*/ */
public boolean hasAttributes() { public boolean hasAttributes() {
return annotationStrategy.hasAttributes(); return annotationStrategy.hasAttributes();
} }
/** /**
* Returns this key without annotation attributes, i.e. with only the * Returns this key without annotation attributes, i.e. with only the annotation type.
* annotation type. *
* @since 3.0
*/ */
public Key<T> withoutAttributes() { public Key<T> withoutAttributes() {
return new Key<T>(typeLiteral, annotationStrategy.withoutAttributes()); return new Key<T>(typeLiteral, annotationStrategy.withoutAttributes());
} }
enum NullAnnotationStrategy implements AnnotationStrategy { interface AnnotationStrategy {
Annotation getAnnotation();
Class<? extends Annotation> getAnnotationType();
boolean hasAttributes();
AnnotationStrategy withoutAttributes();
}
/** Gets the strategy for an annotation. */
static AnnotationStrategy strategyFor(Annotation annotation) {
checkNotNull(annotation, "annotation");
Class<? extends Annotation> annotationType = annotation.annotationType();
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
if (Annotations.isMarker(annotationType)) {
return new AnnotationTypeStrategy(annotationType, annotation);
}
return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
}
/** Gets the strategy for an annotation type. */
static AnnotationStrategy strategyFor(Class<? extends Annotation> annotationType) {
annotationType = Annotations.canonicalizeIfNamed(annotationType);
if (isAllDefaultMethods(annotationType)) {
return strategyFor(generateAnnotation(annotationType));
}
checkNotNull(annotationType, "annotation type");
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
return new AnnotationTypeStrategy(annotationType, null);
}
private static void ensureRetainedAtRuntime(Class<? extends Annotation> annotationType) {
checkArgument(
Annotations.isRetainedAtRuntime(annotationType),
"%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
annotationType.getName());
}
private static void ensureIsBindingAnnotation(Class<? extends Annotation> annotationType) {
checkArgument(
Annotations.isBindingAnnotation(annotationType),
"%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
annotationType.getName());
}
static enum NullAnnotationStrategy implements AnnotationStrategy {
INSTANCE; INSTANCE;
@Override @Override
@ -392,17 +396,6 @@ public class Key<T> {
} }
} }
interface AnnotationStrategy {
Annotation getAnnotation();
Class<? extends Annotation> getAnnotationType();
boolean hasAttributes();
AnnotationStrategy withoutAttributes();
}
// this class not test-covered // this class not test-covered
static class AnnotationInstanceStrategy implements AnnotationStrategy { static class AnnotationInstanceStrategy implements AnnotationStrategy {
@ -460,8 +453,7 @@ public class Key<T> {
// Keep the instance around if we have it so the client can request it. // Keep the instance around if we have it so the client can request it.
final Annotation annotation; final Annotation annotation;
AnnotationTypeStrategy(Class<? extends Annotation> annotationType, AnnotationTypeStrategy(Class<? extends Annotation> annotationType, Annotation annotation) {
Annotation annotation) {
this.annotationType = checkNotNull(annotationType, "annotation type"); this.annotationType = checkNotNull(annotationType, "annotation type");
this.annotation = annotation; this.annotation = annotation;
} }

View file

@ -0,0 +1,120 @@
package com.google.inject;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Annotation restricting the binding of the target type to permitted sources.
*
* <p>Bindings restricted by this annotation may only be created by sources annotated with a permit
* from {@link #permits} -- otherwise, an error message including the {@link #explanation} is
* issued.
*
* <p>There are two kinds of binding source:
*
* <ol>
* <li>Module: a module is the source of a binding if it creates it (either directly, or
* indirectly by installing another module). For example: if module A creates restricted
* binding X, and module C installs module B that installs A; then all 3 modules C,B,A are
* sources of X, and it's enough for any one of them to be annotated with a permit from X's
* restriction.
* <li>Method Scanner ({@code ModuleAnnotatedMethodScanner}): If a binding was created by a
* scanner, then that scanner is also a source of the binding (in addition to the module
* sources) and a permit may be given to the scanner by annotating its class.
* </ol>
*
* <p>Bindings with qualifier annotations are restricted solely by the annotation on their qualifier
* (restrictions on the type are ignored for qualified bindings). Unqualified bindings are
* restricted by the annotation on their type.
*
* <p>This allows libraries to prevent their clients from binding their keys, similar to how
* declaring a class final prevents subtyping. For example, a library may want to prevent users from
* creating mock bindings for tests, using the {@link #explanation} - included in the error message
* - to point them to a supported testing module.
*
* <p>Example usage:
*
* <pre>{@code
* @RestrictedBindingSource.Permit
* @Retention(RetentionPolicy.RUNTIME)
* @interface NetworkPermit {}
*
* @RestrictedBindingSource(
* explanation = "Only NetworkModule can create network bindings.",
* permits = {NetworkPermit.class})
* @Qualifier
* @Retention(RetentionPolicy.RUNTIME)
* public @interface GatewayIpAdress {}
*
* @NetworkPermit
* public final class NetworkModule extends AbstractModule {
* @Provides
* @GatewayIpAdress // Allowed because the module is annotated with @NetworkPermit.
* int provideGatewayIp() { ... }
* }
* }</pre>
*
* @author vzm@google.com (Vladimir Makaric)
* @since 5.0
*/
@Inherited
@Retention(RUNTIME)
@Target(TYPE)
public @interface RestrictedBindingSource {
/**
* Explanation of why binding this target type is restricted.
*
* <p>Will appear as the error message if the target type is bound by non-allowed modules.
*/
String explanation();
/**
* Meta-annotation indicating that the target annotation is a permit for binding restricted
* bindings. Annotating a binding source (defined in top-level javadoc) with a permit gives it
* permission to bind the restricted bindings guarded by the permit (see {@link #permits}).
*
* @since 5.0
*/
@Documented
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface Permit {}
/**
* List of {@code Permit} annotations (must be non-empty), one of which has has to be present on a
* restricted binding's source (defined in top-level javadoc).
*/
Class<? extends Annotation>[] permits();
/**
* Exempt modules whose fully qualified class names match this regex.
*
* <p>If any module on the binding's module stack matches this regex, the binding is allowed (no
* permit necessary). No module is exempt by default (empty string).
*
* <p>Inteded to be used when retrofitting a binding with this restriction. When restricting an
* existing binding, it's often practical to first restrict with exemptions for existing
* violations (to prevent new violations), before updating the code in violation to use the
* permitted module(s).
*/
String exemptModules() default "";
/**
* Level of restriction. Determines how violations are handled.
*
* @since 5.0
*/
public static enum RestrictionLevel {
WARNING,
ERROR;
}
RestrictionLevel restrictionLevel() default RestrictionLevel.ERROR;
}

View file

@ -5,14 +5,15 @@ import com.google.inject.Key;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
/** /**
* Provides a factory that combines the caller's arguments with injector-supplied values to * Provides a factory that combines the caller's arguments with injector-supplied values to
* construct objects. * construct objects.
* *
* <h3>Defining a factory</h3> * <h3>Defining a factory</h3>
*
* Create an interface whose methods return the constructed type, or any of its supertypes. The * Create an interface whose methods return the constructed type, or any of its supertypes. The
* method's parameters are the arguments required to build the constructed type. * method's parameters are the arguments required to build the constructed type.
* *
@ -24,6 +25,7 @@ import java.lang.annotation.Annotation;
* or <i>newPayment</i>. * or <i>newPayment</i>.
* *
* <h3>Creating a type that accepts factory parameters</h3> * <h3>Creating a type that accepts factory parameters</h3>
*
* {@code constructedType} is a concrete class with an {@literal @}{@link com.google.inject.Inject * {@code constructedType} is a concrete class with an {@literal @}{@link com.google.inject.Inject
* Inject}-annotated constructor. In addition to injector-supplied parameters, the constructor * Inject}-annotated constructor. In addition to injector-supplied parameters, the constructor
* should have parameters that match each of the factory method's parameters. Each factory-supplied * should have parameters that match each of the factory method's parameters. Each factory-supplied
@ -42,10 +44,10 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* <h3>Multiple factory methods for the same type</h3> * <h3>Multiple factory methods for the same type</h3>
*
* If the factory contains many methods that return the same type, you can create multiple * If the factory contains many methods that return the same type, you can create multiple
* constructors in your concrete class, each constructor marked with with * constructors in your concrete class, each constructor marked with with {@literal @}{@link
* {@literal @}{@link AssistedInject}, in order to match the different parameters types of the * AssistedInject}, in order to match the different parameters types of the factory methods.
* factory methods.
* *
* <pre>public interface PaymentFactory { * <pre>public interface PaymentFactory {
* Payment create(Date startDate, Money amount); * Payment create(Date startDate, Money amount);
@ -72,8 +74,8 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* <h3>Configuring simple factories</h3> * <h3>Configuring simple factories</h3>
* In your {@link Module module}, install a {@code FactoryModuleBuilder} that creates the *
* factory: * In your {@link Module module}, install a {@code FactoryModuleBuilder} that creates the factory:
* *
* <pre>install(new FactoryModuleBuilder() * <pre>install(new FactoryModuleBuilder()
* .implement(Payment.class, RealPayment.class) * .implement(Payment.class, RealPayment.class)
@ -83,8 +85,9 @@ import java.lang.annotation.Annotation;
* factory cannot be used until the injector has been initialized. * factory cannot be used until the injector has been initialized.
* *
* <h3>Configuring complex factories</h3> * <h3>Configuring complex factories</h3>
* Factories can create an arbitrary number of objects, one per each method. Each factory *
* method can be configured using <code>.implement</code>. * Factories can create an arbitrary number of objects, one per each method. Each factory method can
* be configured using <code>.implement</code>.
* *
* <pre>public interface OrderFactory { * <pre>public interface OrderFactory {
* Payment create(Date startDate, Money amount); * Payment create(Date startDate, Money amount);
@ -99,12 +102,14 @@ import java.lang.annotation.Annotation;
* // excluding .implement for Shipment means the implementation class * // excluding .implement for Shipment means the implementation class
* // will be 'Shipment' itself, which is legal if it's not an interface. * // will be 'Shipment' itself, which is legal if it's not an interface.
* .implement(Receipt.class, RealReceipt.class) * .implement(Receipt.class, RealReceipt.class)
* .build(OrderFactory.class)); * .build(OrderFactory.class));</pre>
*
* </pre> * </pre>
* *
* <h3>Using the factory</h3> * <h3>Using the factory</h3>
* Inject your factory into your application classes. When you use the factory, your arguments *
* will be combined with values from the injector to construct an instance. * Inject your factory into your application classes. When you use the factory, your arguments will
* be combined with values from the injector to construct an instance.
* *
* <pre>public class PaymentAction { * <pre>public class PaymentAction {
* {@literal @}Inject private PaymentFactory paymentFactory; * {@literal @}Inject private PaymentFactory paymentFactory;
@ -116,9 +121,10 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* <h3>Making parameter types distinct</h3> * <h3>Making parameter types distinct</h3>
* The types of the factory method's parameters must be distinct. To use multiple parameters of *
* the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the * The types of the factory method's parameters must be distinct. To use multiple parameters of the
* parameters. The names must be applied to the factory method's parameters: * same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the parameters.
* The names must be applied to the factory method's parameters:
* *
* <pre>public interface PaymentFactory { * <pre>public interface PaymentFactory {
* Payment create( * Payment create(
@ -142,14 +148,17 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* <h3>Values are created by Guice</h3> * <h3>Values are created by Guice</h3>
*
* Returned factories use child injectors to create values. The values are eligible for method * Returned factories use child injectors to create values. The values are eligible for method
* interception. In addition, {@literal @}{@literal Inject} members will be injected before they are * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are
* returned. * returned.
* *
* <h3>More configuration options</h3> * <h3>More configuration options</h3>
*
* In addition to simply specifying an implementation class for any returned type, factories' return * In addition to simply specifying an implementation class for any returned type, factories' return
* values can be automatic or can be configured to use annotations: * values can be automatic or can be configured to use annotations:
* If you just want to return the types specified in the factory, do not configure any *
* <p>If you just want to return the types specified in the factory, do not configure any
* implementations: * implementations:
* *
* <pre>public interface FruitFactory { * <pre>public interface FruitFactory {
@ -161,7 +170,8 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* Note that any type returned by the factory in this manner needs to be an implementation class. * Note that any type returned by the factory in this manner needs to be an implementation class.
* To return two different implementations for the same interface from your factory, use binding *
* <p>To return two different implementations for the same interface from your factory, use binding
* annotations on your return types: * annotations on your return types:
* *
* <pre>interface CarFactory { * <pre>interface CarFactory {
@ -177,142 +187,138 @@ import java.lang.annotation.Annotation;
* }</pre> * }</pre>
* *
* <h3>Implementation limitations</h3> * <h3>Implementation limitations</h3>
* As a limitation of the implementation, it is prohibited to declare a factory method that *
* accepts a {@code Provider} as one of its arguments. * As a limitation of the implementation, it is prohibited to declare a factory method that accepts
* a {@code Provider} as one of its arguments.
*
* @since 3.0
* @author schmitt@google.com (Peter Schmitt)
*/ */
public final class FactoryModuleBuilder { public final class FactoryModuleBuilder {
private final BindingCollector bindings = new BindingCollector(); private final BindingCollector bindings = new BindingCollector();
private MethodHandles.Lookup lookups;
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, Class<? extends T> target) { public <T> FactoryModuleBuilder implement(Class<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target)); return implement(source, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Class<T> source, TypeLiteral<? extends T> target) { public <T> FactoryModuleBuilder implement(Class<T> source, TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), target); return implement(TypeLiteral.get(source), target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends T> target) { public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target)); return implement(source, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ TypeLiteral<T> source, TypeLiteral<? extends T> target) {
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
TypeLiteral<? extends T> target) {
return implement(Key.get(source), target); return implement(Key.get(source), target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ Class<T> source, Annotation annotation, Class<? extends T> target) {
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
Class<? extends T> target) {
return implement(source, annotation, TypeLiteral.get(target)); return implement(source, annotation, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ Class<T> source, Annotation annotation, TypeLiteral<? extends T> target) {
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), annotation, target); return implement(TypeLiteral.get(source), annotation, target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ TypeLiteral<T> source, Annotation annotation, Class<? extends T> target) {
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
Class<? extends T> target) {
return implement(source, annotation, TypeLiteral.get(target)); return implement(source, annotation, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ TypeLiteral<T> source, Annotation annotation, TypeLiteral<? extends T> target) {
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
TypeLiteral<? extends T> target) {
return implement(Key.get(source, annotation), target); return implement(Key.get(source, annotation), target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ Class<T> source, Class<? extends Annotation> annotationType, Class<? extends T> target) {
public <T> FactoryModuleBuilder implement(Class<T> source,
Class<? extends Annotation> annotationType, Class<? extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target)); return implement(source, annotationType, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ Class<T> source,
public <T> FactoryModuleBuilder implement(Class<T> source, Class<? extends Annotation> annotationType,
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) { TypeLiteral<? extends T> target) {
return implement(TypeLiteral.get(source), annotationType, target); return implement(TypeLiteral.get(source), annotationType, target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ TypeLiteral<T> source,
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends Annotation> annotationType,
Class<? extends Annotation> annotationType, Class<? extends T> target) { Class<? extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target)); return implement(source, annotationType, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}. public <T> FactoryModuleBuilder implement(
*/ TypeLiteral<T> source,
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends Annotation> annotationType,
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) { TypeLiteral<? extends T> target) {
return implement(Key.get(source, annotationType), target); return implement(Key.get(source, annotationType), target);
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Key<T> source, Class<? extends T> target) { public <T> FactoryModuleBuilder implement(Key<T> source, Class<? extends T> target) {
return implement(source, TypeLiteral.get(target)); return implement(source, TypeLiteral.get(target));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <T> FactoryModuleBuilder implement(Key<T> source, TypeLiteral<? extends T> target) { public <T> FactoryModuleBuilder implement(Key<T> source, TypeLiteral<? extends T> target) {
bindings.addBinding(source, target); bindings.addBinding(source, target);
return this; return this;
} }
/** /**
* See the factory configuration examples at {@link FactoryModuleBuilder}. * Typically called via {@code withLookups(MethodHandles.lookup())}. Sets the MethodHandles.Lookup
* that the factory implementation will use to call default methods on the factory interface.
* While this is not always required, it is always OK to set it. It is required if the factory
* passed to {@link #build} is non-public and javac generated default methods while compiling it
* (which javac can sometimes do if the factory uses generic types).
*
* <p>Guice will try to work properly even if this method is not called (or called with a lookups
* that doesn't have access to the factory), but doing so requires reflection into the JDK, which
* may break at any time (and trigger unsafe access warnings).
*
* @since 5.0
*/ */
public <T> FactoryModuleBuilder withLookups(MethodHandles.Lookup lookups) {
this.lookups = lookups;
return this;
}
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
public <F> Module build(Class<F> factoryInterface) { public <F> Module build(Class<F> factoryInterface) {
return build(TypeLiteral.get(factoryInterface)); return build(TypeLiteral.get(factoryInterface));
} }
/** /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
* See the factory configuration examples at {@link FactoryModuleBuilder}.
*/
public <F> Module build(TypeLiteral<F> factoryInterface) { public <F> Module build(TypeLiteral<F> factoryInterface) {
return build(Key.get(factoryInterface)); return build(Key.get(factoryInterface));
} }
public <F> Module build(final Key<F> factoryInterface) { public <F> Module build(final Key<F> factoryInterface) {
return new AbstractModule() { return new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
Provider<F> provider = new FactoryProvider2<F>(factoryInterface, bindings); Provider<F> provider = new FactoryProvider2<>(factoryInterface, bindings, lookups);
bind(factoryInterface).toProvider(provider); binder().skipSources(this.getClass()).bind(factoryInterface).toProvider(provider);
} }
}; };
} }

View file

@ -1,5 +1,7 @@
package com.google.inject.assistedinject; package com.google.inject.assistedinject;
import static com.google.inject.internal.Annotations.getKey;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -13,11 +15,9 @@ import com.google.inject.Provider;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.internal.Errors; import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException; import com.google.inject.internal.ErrorsException;
import com.google.inject.internal.Messages;
import com.google.inject.spi.Dependency; import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies; import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.Message; import com.google.inject.spi.Message;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
@ -28,8 +28,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static com.google.inject.internal.Annotations.getKey;
/** /**
* <strong>Obsolete.</strong> Prefer {@link FactoryModuleBuilder} for its more concise API and * <strong>Obsolete.</strong> Prefer {@link FactoryModuleBuilder} for its more concise API and
* additional capability. * additional capability.
@ -38,20 +36,25 @@ import static com.google.inject.internal.Annotations.getKey;
* construct objects. * construct objects.
* *
* <h3>Defining a factory</h3> * <h3>Defining a factory</h3>
*
* Create an interface whose methods return the constructed type, or any of its supertypes. The * Create an interface whose methods return the constructed type, or any of its supertypes. The
* method's parameters are the arguments required to build the constructed type. * method's parameters are the arguments required to build the constructed type.
*
* <pre>public interface PaymentFactory { * <pre>public interface PaymentFactory {
* Payment create(Date startDate, Money amount); * Payment create(Date startDate, Money amount);
* }</pre> * }</pre>
*
* You can name your factory methods whatever you like, such as <i>create</i>, <i>createPayment</i> * You can name your factory methods whatever you like, such as <i>create</i>, <i>createPayment</i>
* or <i>newPayment</i>. * or <i>newPayment</i>.
* *
* <h3>Creating a type that accepts factory parameters</h3> * <h3>Creating a type that accepts factory parameters</h3>
*
* {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated * {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated
* constructor. In addition to injector-supplied parameters, the constructor should have * constructor. In addition to injector-supplied parameters, the constructor should have parameters
* parameters that match each of the factory method's parameters. Each factory-supplied parameter * that match each of the factory method's parameters. Each factory-supplied parameter requires an
* requires an {@literal @}{@link Assisted} annotation. This serves to document that the parameter * {@literal @}{@link Assisted} annotation. This serves to document that the parameter is not bound
* is not bound by your application's modules. * by your application's modules.
*
* <pre>public class RealPayment implements Payment { * <pre>public class RealPayment implements Payment {
* {@literal @}Inject * {@literal @}Inject
* public RealPayment( * public RealPayment(
@ -62,19 +65,25 @@ import static com.google.inject.internal.Annotations.getKey;
* ... * ...
* } * }
* }</pre> * }</pre>
*
* Any parameter that permits a null value should also be annotated {@code @Nullable}. * Any parameter that permits a null value should also be annotated {@code @Nullable}.
* *
* <h3>Configuring factories</h3> * <h3>Configuring factories</h3>
*
* In your {@link com.google.inject.Module module}, bind the factory interface to the returned * In your {@link com.google.inject.Module module}, bind the factory interface to the returned
* factory: * factory:
*
* <pre>bind(PaymentFactory.class).toProvider( * <pre>bind(PaymentFactory.class).toProvider(
* FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre> * FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre>
*
* As a side-effect of this binding, Guice will inject the factory to initialize it for use. The * As a side-effect of this binding, Guice will inject the factory to initialize it for use. The
* factory cannot be used until the injector has been initialized. * factory cannot be used until the injector has been initialized.
* *
* <h3>Using the factory</h3> * <h3>Using the factory</h3>
* Inject your factory into your application classes. When you use the factory, your arguments *
* will be combined with values from the injector to construct an instance. * Inject your factory into your application classes. When you use the factory, your arguments will
* be combined with values from the injector to construct an instance.
*
* <pre>public class PaymentAction { * <pre>public class PaymentAction {
* {@literal @}Inject private PaymentFactory paymentFactory; * {@literal @}Inject private PaymentFactory paymentFactory;
* *
@ -85,9 +94,10 @@ import static com.google.inject.internal.Annotations.getKey;
* }</pre> * }</pre>
* *
* <h3>Making parameter types distinct</h3> * <h3>Making parameter types distinct</h3>
* The types of the factory method's parameters must be distinct. To use multiple parameters of *
* the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the * The types of the factory method's parameters must be distinct. To use multiple parameters of the
* parameters. The names must be applied to the factory method's parameters: * same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the parameters.
* The names must be applied to the factory method's parameters:
* *
* <pre>public interface PaymentFactory { * <pre>public interface PaymentFactory {
* Payment create( * Payment create(
@ -95,7 +105,9 @@ import static com.google.inject.internal.Annotations.getKey;
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate, * <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
* Money amount); * Money amount);
* } </pre> * } </pre>
*
* ...and to the concrete type's constructor parameters: * ...and to the concrete type's constructor parameters:
*
* <pre>public class RealPayment implements Payment { * <pre>public class RealPayment implements Payment {
* {@literal @}Inject * {@literal @}Inject
* public RealPayment( * public RealPayment(
@ -109,23 +121,27 @@ import static com.google.inject.internal.Annotations.getKey;
* }</pre> * }</pre>
* *
* <h3>Values are created by Guice</h3> * <h3>Values are created by Guice</h3>
*
* Returned factories use child injectors to create values. The values are eligible for method * Returned factories use child injectors to create values. The values are eligible for method
* interception. In addition, {@literal @}{@literal Inject} members will be injected before they are * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are
* returned. * returned.
* *
* <h3>Backwards compatibility using {@literal @}AssistedInject</h3> * <h3>Backwards compatibility using {@literal @}AssistedInject</h3>
*
* Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with
* {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode.
* *
* <p>Instead of matching factory method arguments to constructor parameters using their names, the * <p>Instead of matching factory method arguments to constructor parameters using their names, the
* <strong>parameters are matched by their order</strong>. The first factory method argument is * <strong>parameters are matched by their order</strong>. The first factory method argument is used
* used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no * for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no effect.
* effect.
* *
* <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for * <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for
* method interception. They do receive post-construction member injection. * method interception. They do receive post-construction member injection.
* *
* @param <F> The factory interface * @param <F> The factory interface
* @author jmourits@google.com (Jerome Mourits)
* @author jessewilson@google.com (Jesse Wilson)
* @author dtm@google.com (Daniel Martin)
* @deprecated use {@link FactoryModuleBuilder} instead. * @deprecated use {@link FactoryModuleBuilder} instead.
*/ */
@Deprecated @Deprecated
@ -136,19 +152,11 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
* to factory methods. The new child injector implementation lives in FactoryProvider2. * to factory methods. The new child injector implementation lives in FactoryProvider2.
*/ */
private Injector injector;
private final TypeLiteral<F> factoryType; private final TypeLiteral<F> factoryType;
private final TypeLiteral<?> implementationType; private final TypeLiteral<?> implementationType;
private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor; private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor;
private Injector injector;
private FactoryProvider(TypeLiteral<F> factoryType,
TypeLiteral<?> implementationType,
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) {
this.factoryType = factoryType;
this.implementationType = implementationType;
this.factoryMethodToConstructor = factoryMethodToConstructor;
checkDeclaredExceptionsMatch();
}
public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType) { public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType) {
return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType));
@ -156,8 +164,8 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
public static <F> Provider<F> newFactory( public static <F> Provider<F> newFactory(
TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) { TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) {
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor Map<Method, AssistedConstructor<?>> factoryMethodToConstructor =
= createMethodMapping(factoryType, implementationType); createMethodMapping(factoryType, implementationType);
if (!factoryMethodToConstructor.isEmpty()) { if (!factoryMethodToConstructor.isEmpty()) {
return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor); return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor);
@ -171,8 +179,8 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
try { try {
for (Method method : factoryType.getRawType().getMethods()) { for (Method method : factoryType.getRawType().getMethods()) {
Key<?> returnType = getKey(factoryType.getReturnType(method), method, Key<?> returnType =
method.getAnnotations(), errors); getKey(factoryType.getReturnType(method), method, method.getAnnotations(), errors);
if (!implementationKey.equals(returnType)) { if (!implementationKey.equals(returnType)) {
collector.addBinding(returnType, implementationType); collector.addBinding(returnType, implementationType);
} }
@ -181,83 +189,18 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
throw new ConfigurationException(e.getErrors().getMessages()); throw new ConfigurationException(e.getErrors().getMessages());
} }
return new FactoryProvider2<F>(Key.get(factoryType), collector); return new FactoryProvider2<F>(Key.get(factoryType), collector, /* userLookups= */ null);
} }
} }
private static Map<Method, AssistedConstructor<?>> createMethodMapping( private FactoryProvider(
TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) { TypeLiteral<F> factoryType,
List<AssistedConstructor<?>> constructors = Lists.newArrayList(); TypeLiteral<?> implementationType,
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) {
for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) { this.factoryType = factoryType;
if (constructor.isAnnotationPresent(AssistedInject.class)) { this.implementationType = implementationType;
AssistedConstructor<?> assistedConstructor = AssistedConstructor.create( this.factoryMethodToConstructor = factoryMethodToConstructor;
constructor, implementationType.getParameterTypes(constructor)); checkDeclaredExceptionsMatch();
constructors.add(assistedConstructor);
}
}
if (constructors.isEmpty()) {
return ImmutableMap.of();
}
Method[] factoryMethods = factoryType.getRawType().getMethods();
if (constructors.size() != factoryMethods.length) {
throw newConfigurationException("Constructor mismatch: %s has %s @AssistedInject "
+ "constructors, factory %s has %s creation methods", implementationType,
constructors.size(), factoryType, factoryMethods.length);
}
Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap();
for (AssistedConstructor<?> c : constructors) {
if (paramsToConstructor.containsKey(c.getAssistedParameters())) {
throw new RuntimeException("Duplicate constructor, " + c);
}
paramsToConstructor.put(c.getAssistedParameters(), c);
}
Map<Method, AssistedConstructor<?>> result = Maps.newHashMap();
for (Method method : factoryMethods) {
if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) {
throw newConfigurationException("Return type of method %s is not assignable from %s",
method, implementationType);
}
List<Type> parameterTypes = Lists.newArrayList();
for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) {
parameterTypes.add(parameterType.getType());
}
ParameterListKey methodParams = new ParameterListKey(parameterTypes);
if (!paramsToConstructor.containsKey(methodParams)) {
throw newConfigurationException("%s has no @AssistInject constructor that takes the "
+ "@Assisted parameters %s in that order. @AssistInject constructors are %s",
implementationType, methodParams, paramsToConstructor.values());
}
method.getParameterAnnotations();
for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
for (Annotation parameterAnnotation : parameterAnnotations) {
if (parameterAnnotation.annotationType() == Assisted.class) {
throw newConfigurationException("Factory method %s has an @Assisted parameter, which "
+ "is incompatible with the deprecated @AssistedInject annotation. Please replace "
+ "@AssistedInject with @Inject on the %s constructor.",
method, implementationType);
}
}
}
AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams);
result.put(method, matchingConstructor);
}
return result;
}
private static ConfigurationException newConfigurationException(String format, Object... args) {
return new ConfigurationException(ImmutableSet.of(new Message(Messages.format(format, args))));
} }
@Inject @Inject
@ -269,8 +212,10 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
// this is lame - we're not using the proper mechanism to add an // this is lame - we're not using the proper mechanism to add an
// error to the injector. Throughout this class we throw exceptions // error to the injector. Throughout this class we throw exceptions
// to add errors, which isn't really the best way in Guice // to add errors, which isn't really the best way in Guice
throw newConfigurationException("Parameter of type '%s' is not injectable or annotated " throw newConfigurationException(
+ "with @Assisted for Constructor '%s'", p, c); "Parameter of type '%s' is not injectable or annotated "
+ "with @Assisted for Constructor '%s'",
p, c);
} }
} }
} }
@ -281,8 +226,10 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) { for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) {
if (!isConstructorExceptionCompatibleWithFactoryExeception( if (!isConstructorExceptionCompatibleWithFactoryExeception(
constructorException, entry.getKey().getExceptionTypes())) { constructorException, entry.getKey().getExceptionTypes())) {
throw newConfigurationException("Constructor %s declares an exception, but no compatible " throw newConfigurationException(
+ "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); "Constructor %s declares an exception, but no compatible "
+ "exception is thrown by the factory method %s",
entry.getValue(), entry.getKey());
} }
} }
} }
@ -302,6 +249,82 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
return parameter.isBound(injector); return parameter.isBound(injector);
} }
private static Map<Method, AssistedConstructor<?>> createMethodMapping(
TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) {
List<AssistedConstructor<?>> constructors = Lists.newArrayList();
for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) {
if (constructor.isAnnotationPresent(AssistedInject.class)) {
AssistedConstructor<?> assistedConstructor =
AssistedConstructor.create(
constructor, implementationType.getParameterTypes(constructor));
constructors.add(assistedConstructor);
}
}
if (constructors.isEmpty()) {
return ImmutableMap.of();
}
Method[] factoryMethods = factoryType.getRawType().getMethods();
if (constructors.size() != factoryMethods.length) {
throw newConfigurationException(
"Constructor mismatch: %s has %s @AssistedInject "
+ "constructors, factory %s has %s creation methods",
implementationType, constructors.size(), factoryType, factoryMethods.length);
}
Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap();
for (AssistedConstructor<?> c : constructors) {
if (paramsToConstructor.containsKey(c.getAssistedParameters())) {
throw new RuntimeException("Duplicate constructor, " + c);
}
paramsToConstructor.put(c.getAssistedParameters(), c);
}
Map<Method, AssistedConstructor<?>> result = Maps.newHashMap();
for (Method method : factoryMethods) {
if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) {
throw newConfigurationException(
"Return type of method %s is not assignable from %s", method, implementationType);
}
List<Type> parameterTypes = Lists.newArrayList();
for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) {
parameterTypes.add(parameterType.getType());
}
ParameterListKey methodParams = new ParameterListKey(parameterTypes);
if (!paramsToConstructor.containsKey(methodParams)) {
throw newConfigurationException(
"%s has no @AssistInject constructor that takes the "
+ "@Assisted parameters %s in that order. @AssistInject constructors are %s",
implementationType, methodParams, paramsToConstructor.values());
}
method.getParameterAnnotations();
for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
for (Annotation parameterAnnotation : parameterAnnotations) {
if (parameterAnnotation.annotationType() == Assisted.class) {
throw newConfigurationException(
"Factory method %s has an @Assisted parameter, which is incompatible with the"
+ " deprecated @AssistedInject annotation. Please replace @AssistedInject with"
+ " @Inject on the %s constructor.",
method, implementationType);
}
}
}
AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams);
result.put(method, matchingConstructor);
}
return result;
}
@Override
public Set<Dependency<?>> getDependencies() { public Set<Dependency<?>> getDependencies() {
List<Dependency<?>> dependencies = Lists.newArrayList(); List<Dependency<?>> dependencies = Lists.newArrayList();
for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) { for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) {
@ -314,9 +337,13 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
return ImmutableSet.copyOf(dependencies); return ImmutableSet.copyOf(dependencies);
} }
@Override
public F get() { public F get() {
InvocationHandler invocationHandler = new InvocationHandler() { InvocationHandler invocationHandler =
public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable { new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] creationArgs)
throws Throwable {
// pass methods from Object.class to the proxy // pass methods from Object.class to the proxy
if (method.getDeclaringClass().equals(Object.class)) { if (method.getDeclaringClass().equals(Object.class)) {
if ("equals".equals(method.getName())) { if ("equals".equals(method.getName())) {
@ -336,8 +363,7 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
} }
public Object[] gatherArgsForConstructor( public Object[] gatherArgsForConstructor(
AssistedConstructor<?> constructor, AssistedConstructor<?> constructor, Object[] factoryArgs) {
Object[] factoryArgs) {
int numParams = constructor.getAllParameters().size(); int numParams = constructor.getAllParameters().size();
int argPosition = 0; int argPosition = 0;
Object[] result = new Object[numParams]; Object[] result = new Object[numParams];
@ -356,9 +382,10 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
}; };
@SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T> @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T>
Class<F> factoryRawType = (Class<F>) factoryType.getRawType(); Class<F> factoryRawType = (Class<F>) (Class<?>) factoryType.getRawType();
return factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(), return factoryRawType.cast(
new Class<?>[]{factoryRawType}, invocationHandler)); Proxy.newProxyInstance(
factoryRawType.getClassLoader(), new Class<?>[] {factoryRawType}, invocationHandler));
} }
@Override @Override
@ -375,4 +402,8 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
return factoryType.equals(other.factoryType) return factoryType.equals(other.factoryType)
&& implementationType.equals(other.implementationType); && implementationType.equals(other.implementationType);
} }
private static ConfigurationException newConfigurationException(String format, Object... args) {
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
}
} }

View file

@ -1,5 +1,8 @@
package com.google.inject.assistedinject; package com.google.inject.assistedinject;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
@ -36,11 +39,12 @@ import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable; import com.google.inject.spi.Toolable;
import com.google.inject.util.Providers; import com.google.inject.util.Providers;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
@ -51,35 +55,46 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
/** /**
* The newer implementation of factory provider. This implementation uses a child injector to * The newer implementation of factory provider. This implementation uses a child injector to create
* create values. * values.
*
* @author jessewilson@google.com (Jesse Wilson)
* @author dtm@google.com (Daniel Martin)
* @author schmitt@google.com (Peter Schmitt)
* @author sameb@google.com (Sam Berlin)
*/ */
final class FactoryProvider2<F> implements InvocationHandler, final class FactoryProvider2<F>
ProviderWithExtensionVisitor<F>, HasDependencies, AssistedInjectBinding<F> { implements InvocationHandler,
ProviderWithExtensionVisitor<F>,
HasDependencies,
AssistedInjectBinding<F> {
/** /** A constant annotation to denote the return value, instead of creating a new one each time. */
* A constant annotation to denote the return value, instead of creating a new one each time.
*/
static final Annotation RETURN_ANNOTATION = UniqueAnnotations.create(); static final Annotation RETURN_ANNOTATION = UniqueAnnotations.create();
// use the logger under a well-known name, not FactoryProvider2 // use the logger under a well-known name, not FactoryProvider2
static final Logger logger = Logger.getLogger(AssistedInject.class.getName()); static final Logger logger = Logger.getLogger(AssistedInject.class.getName());
/** /**
* if a factory method parameter isn't annotated, it gets this annotation. * A constant that determines if we allow fallback to reflection. Typically always true, but
* reflectively set to false in tests.
*/ */
static final Assisted DEFAULT_ANNOTATION = new Assisted() { private static boolean allowLookupReflection = true;
/** if a factory method parameter isn't annotated, it gets this annotation. */
static final Assisted DEFAULT_ANNOTATION =
new Assisted() {
@Override
public String value() { public String value() {
return ""; return "";
} }
@Override
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return Assisted.class; return Assisted.class;
} }
@ -96,40 +111,117 @@ final class FactoryProvider2<F> implements InvocationHandler,
@Override @Override
public String toString() { public String toString() {
return "@" + Assisted.class.getName() + "(value=\"\")"; return "@"
+ Assisted.class.getName()
+ "("
+ Annotations.memberValueString("value", "")
+ ")";
} }
}; };
/**
* Mapping from method to the data about how the method will be assisted. /** All the data necessary to perform an assisted inject. */
*/ private static class AssistData implements AssistedMethod {
/** the constructor the implementation is constructed with. */
final Constructor<?> constructor;
/** the return type in the factory method that the constructor is bound to. */
final Key<?> returnType;
/** the parameters in the factory method associated with this data. */
final ImmutableList<Key<?>> paramTypes;
/** the type of the implementation constructed */
final TypeLiteral<?> implementationType;
/** All non-assisted dependencies required by this method. */
final Set<Dependency<?>> dependencies;
/** The factory method associated with this data */
final Method factoryMethod;
/** true if {@link #isValidForOptimizedAssistedInject} returned true. */
final boolean optimized;
/** the list of optimized providers, empty if not optimized. */
final List<ThreadLocalProvider> providers;
/** used to perform optimized factory creations. */
volatile Binding<?> cachedBinding; // TODO: volatile necessary?
AssistData(
Constructor<?> constructor,
Key<?> returnType,
ImmutableList<Key<?>> paramTypes,
TypeLiteral<?> implementationType,
Method factoryMethod,
Set<Dependency<?>> dependencies,
boolean optimized,
List<ThreadLocalProvider> providers) {
this.constructor = constructor;
this.returnType = returnType;
this.paramTypes = paramTypes;
this.implementationType = implementationType;
this.factoryMethod = factoryMethod;
this.dependencies = dependencies;
this.optimized = optimized;
this.providers = providers;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("ctor", constructor)
.add("return type", returnType)
.add("param type", paramTypes)
.add("implementation type", implementationType)
.add("dependencies", dependencies)
.add("factory method", factoryMethod)
.add("optimized", optimized)
.add("providers", providers)
.add("cached binding", cachedBinding)
.toString();
}
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
@Override
public Method getFactoryMethod() {
return factoryMethod;
}
@Override
public Constructor<?> getImplementationConstructor() {
return constructor;
}
@Override
public TypeLiteral<?> getImplementationType() {
return implementationType;
}
}
/** Mapping from method to the data about how the method will be assisted. */
private final ImmutableMap<Method, AssistData> assistDataByMethod; private final ImmutableMap<Method, AssistData> assistDataByMethod;
/**
* Mapping from method to method handle, for generated default methods. /** Mapping from method to method handle, for generated default methods. */
*/ private final ImmutableMap<Method, MethodHandle> methodHandleByMethod;
private final ImmutableMap<Method, MethodHandleWrapper> methodHandleByMethod;
/** /** the hosting injector, or null if we haven't been initialized yet */
* the factory interface, implemented and provided
*/
private final F factory;
/**
* The key that this is bound to.
*/
private final Key<F> factoryKey;
/**
* The binding collector, for equality/hashing purposes.
*/
private final BindingCollector collector;
/**
* the hosting injector, or null if we haven't been initialized yet
*/
private Injector injector; private Injector injector;
/** the factory interface, implemented and provided */
private final F factory;
/** The key that this is bound to. */
private final Key<F> factoryKey;
/** The binding collector, for equality/hashing purposes. */
private final BindingCollector collector;
/** /**
* @param factoryKey a key for a Java interface that defines one or more create methods. * @param factoryKey a key for a Java interface that defines one or more create methods.
* @param collector binding configuration that maps method return types to * @param collector binding configuration that maps method return types to implementation types.
* implementation types. * @param userLookups user provided lookups, optional.
*/ */
FactoryProvider2(Key<F> factoryKey, BindingCollector collector) { FactoryProvider2(
Key<F> factoryKey, BindingCollector collector, MethodHandles.Lookup userLookups) {
this.factoryKey = factoryKey; this.factoryKey = factoryKey;
this.collector = collector; this.collector = collector;
@ -149,6 +241,11 @@ final class FactoryProvider2<F> implements InvocationHandler,
ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder(); ImmutableMap.Builder<Method, AssistData> assistDataBuilder = ImmutableMap.builder();
// TODO: also grab methods from superinterfaces // TODO: also grab methods from superinterfaces
for (Method method : factoryRawType.getMethods()) { for (Method method : factoryRawType.getMethods()) {
// Skip static methods
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
// Skip default methods that java8 may have created. // Skip default methods that java8 may have created.
if (isDefault(method) && (method.isBridge() || method.isSynthetic())) { if (isDefault(method) && (method.isBridge() || method.isSynthetic())) {
// Even synthetic default methods need the return type validation... // Even synthetic default methods need the return type validation...
@ -162,7 +259,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method); TypeLiteral<?> returnTypeLiteral = factoryType.getReturnType(method);
Key<?> returnType; Key<?> returnType;
try { try {
returnType = Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors); returnType =
Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors);
} catch (ConfigurationException ce) { } catch (ConfigurationException ce) {
// If this was an error due to returnTypeLiteral not being specified, rephrase // If this was an error due to returnTypeLiteral not being specified, rephrase
// it as our factory not being specified, so it makes more sense to users. // it as our factory not being specified, so it makes more sense to users.
@ -182,7 +280,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
Class<?> underlylingType = paramKey.getTypeLiteral().getRawType(); Class<?> underlylingType = paramKey.getTypeLiteral().getRawType();
if (underlylingType.equals(Provider.class) if (underlylingType.equals(Provider.class)
|| underlylingType.equals(javax.inject.Provider.class)) { || underlylingType.equals(javax.inject.Provider.class)) {
errors.addMessage("A Provider may not be a type in a factory method of an AssistedInject." errors.addMessage(
"A Provider may not be a type in a factory method of an AssistedInject."
+ "\n Offending instance is parameter [%s] with key [%s] on method [%s]", + "\n Offending instance is parameter [%s] with key [%s] on method [%s]",
p, paramKey, method); p, paramKey, method);
} }
@ -198,7 +297,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
Class<? extends Annotation> scope = Class<? extends Annotation> scope =
Annotations.findScopeAnnotation(errors, implementation.getRawType()); Annotations.findScopeAnnotation(errors, implementation.getRawType());
if (scope != null) { if (scope != null) {
errors.addMessage("Found scope annotation [%s] on implementation class " errors.addMessage(
"Found scope annotation [%s] on implementation class "
+ "[%s] of AssistedInject factory [%s].\nThis is not allowed, please" + "[%s] of AssistedInject factory [%s].\nThis is not allowed, please"
+ " remove the scope annotation.", + " remove the scope annotation.",
scope, implementation.getRawType(), factoryType); scope, implementation.getRawType(), factoryType);
@ -207,7 +307,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
InjectionPoint ctorInjectionPoint; InjectionPoint ctorInjectionPoint;
try { try {
ctorInjectionPoint = ctorInjectionPoint =
findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList); findMatchingConstructorInjectionPoint(
method, returnType, implementation, immutableParamList);
} catch (ErrorsException ee) { } catch (ErrorsException ee) {
errors.merge(ee.getErrors()); errors.merge(ee.getErrors());
continue; continue;
@ -231,7 +332,9 @@ final class FactoryProvider2<F> implements InvocationHandler,
optimized = true; optimized = true;
} }
AssistData data = new AssistData(constructor, AssistData data =
new AssistData(
constructor,
returnType, returnType,
immutableParamList, immutableParamList,
implementation, implementation,
@ -242,25 +345,61 @@ final class FactoryProvider2<F> implements InvocationHandler,
assistDataBuilder.put(method, data); assistDataBuilder.put(method, data);
} }
factory = factoryRawType.cast(Proxy.newProxyInstance( factory =
factoryRawType.cast(
Proxy.newProxyInstance(
factoryRawType.getClassLoader(), new Class<?>[] {factoryRawType}, this)); factoryRawType.getClassLoader(), new Class<?>[] {factoryRawType}, this));
// Now go back through default methods. Try to use MethodHandles to make things // Now go back through default methods. Try to use MethodHandles to make things
// work. If that doesn't work, fallback to trying to find compatible method // work. If that doesn't work, fallback to trying to find compatible method
// signatures. // signatures.
Map<Method, AssistData> dataSoFar = assistDataBuilder.build(); Map<Method, AssistData> dataSoFar = assistDataBuilder.build();
ImmutableMap.Builder<Method, MethodHandleWrapper> methodHandleBuilder = ImmutableMap.builder(); ImmutableMap.Builder<Method, MethodHandle> methodHandleBuilder = ImmutableMap.builder();
boolean warnedAboutUserLookups = false;
for (Map.Entry<String, Method> entry : defaultMethods.entries()) { for (Map.Entry<String, Method> entry : defaultMethods.entries()) {
if (!warnedAboutUserLookups
&& userLookups == null
&& !Modifier.isPublic(factory.getClass().getModifiers())) {
warnedAboutUserLookups = true;
logger.log(
Level.WARNING,
"AssistedInject factory {0} is non-public and has javac-generated default methods. "
+ " Please pass a `MethodHandles.lookups()` with"
+ " FactoryModuleBuilder.withLookups when using this factory so that Guice can"
+ " properly call the default methods. Guice will try to work around the"
+ " problem, but doing so requires reflection into the JDK and may break at any"
+ " time.",
new Object[] {factoryType});
}
Method defaultMethod = entry.getValue(); Method defaultMethod = entry.getValue();
MethodHandleWrapper handle = MethodHandleWrapper.create(defaultMethod, factory); MethodHandle handle = null;
try {
// Note: this can return null if we fallback to reflecting the private lookup cxtor and it
// fails. In that case, we try the super hacky workaround below (w/ 'foundMatch').
// It can throw an exception if we're not doing private reflection, or if unreflectSpecial
// _still_ fails.
handle = superMethodHandle(defaultMethod, factory, userLookups);
} catch (ReflectiveOperationException roe) {
errors.addMessage(
new Message(
"Unable to use factory "
+ factoryRawType.getName()
+ ". Did you call FactoryModuleBuilder.withLookups(MethodHandles.lookups())"
+ " (with a lookups that has access to the factory)?"));
continue;
}
if (handle != null) { if (handle != null) {
methodHandleBuilder.put(defaultMethod, handle); methodHandleBuilder.put(defaultMethod, handle);
} else { } else {
// TODO: remove this workaround when Java8 support is dropped
boolean foundMatch = false; boolean foundMatch = false;
for (Method otherMethod : otherMethods.get(defaultMethod.getName())) { for (Method otherMethod : otherMethods.get(defaultMethod.getName())) {
if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) { if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) {
if (foundMatch) { if (foundMatch) {
errors.addMessage("Generated default method %s with parameters %s is" errors.addMessage(
"Generated default method %s with parameters %s is"
+ " signature-compatible with more than one non-default method." + " signature-compatible with more than one non-default method."
+ " Unable to create factory. As a workaround, remove the override" + " Unable to create factory. As a workaround, remove the override"
+ " so javac stops generating a default method.", + " so javac stops generating a default method.",
@ -277,7 +416,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
} }
// If we generated any errors (from finding matching constructors, for instance), throw an exception. // If we generated any errors (from finding matching constructors, for instance), throw an
// exception.
if (errors.hasErrors()) { if (errors.hasErrors()) {
throw errors.toException(); throw errors.toException();
} }
@ -297,23 +437,6 @@ final class FactoryProvider2<F> implements InvocationHandler,
== Modifier.PUBLIC; == Modifier.PUBLIC;
} }
/**
* Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping.
*/
static boolean canRethrow(Method invoked, Throwable thrown) {
if (thrown instanceof Error || thrown instanceof RuntimeException) {
return true;
}
for (Class<?> declared : invoked.getExceptionTypes()) {
if (declared.isInstance(thrown)) {
return true;
}
}
return false;
}
private boolean isCompatible(Method src, Method dst) { private boolean isCompatible(Method src, Method dst) {
if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) { if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) {
return false; return false;
@ -331,32 +454,37 @@ final class FactoryProvider2<F> implements InvocationHandler,
return true; return true;
} }
@Override
public F get() { public F get() {
return factory; return factory;
} }
@Override
public Set<Dependency<?>> getDependencies() { public Set<Dependency<?>> getDependencies() {
Set<Dependency<?>> combinedDeps = new HashSet<Dependency<?>>(); Set<Dependency<?>> combinedDeps = new HashSet<>();
for (AssistData data : assistDataByMethod.values()) { for (AssistData data : assistDataByMethod.values()) {
combinedDeps.addAll(data.dependencies); combinedDeps.addAll(data.dependencies);
} }
return ImmutableSet.copyOf(combinedDeps); return ImmutableSet.copyOf(combinedDeps);
} }
@Override
public Key<F> getKey() { public Key<F> getKey() {
return factoryKey; return factoryKey;
} }
// Safe cast because values are typed to AssistedData, which is an AssistedMethod, and // Safe cast because values are typed to AssistedData, which is an AssistedMethod, and
// the collection is immutable. // the collection is immutable.
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Collection<AssistedMethod> getAssistedMethods() { public Collection<AssistedMethod> getAssistedMethods() {
return (Collection<AssistedMethod>) (Collection<?>) assistDataByMethod.values(); return (Collection<AssistedMethod>) (Collection<?>) assistDataByMethod.values();
} }
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T, V> V acceptExtensionVisitor(BindingTargetVisitor<T, V> visitor, public <T, V> V acceptExtensionVisitor(
ProviderInstanceBinding<? extends T> binding) { BindingTargetVisitor<T, V> visitor, ProviderInstanceBinding<? extends T> binding) {
if (visitor instanceof AssistedInjectTargetVisitor) { if (visitor instanceof AssistedInjectTargetVisitor) {
return ((AssistedInjectTargetVisitor<T, V>) visitor).visit((AssistedInjectBinding<T>) this); return ((AssistedInjectTargetVisitor<T, V>) visitor).visit((AssistedInjectBinding<T>) this);
} }
@ -366,7 +494,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
private void validateFactoryReturnType(Errors errors, Class<?> returnType, Class<?> factoryType) { private void validateFactoryReturnType(Errors errors, Class<?> returnType, Class<?> factoryType) {
if (Modifier.isPublic(factoryType.getModifiers()) if (Modifier.isPublic(factoryType.getModifiers())
&& !Modifier.isPublic(returnType.getModifiers())) { && !Modifier.isPublic(returnType.getModifiers())) {
errors.addMessage("%s is public, but has a method that returns a non-public type: %s. " errors.addMessage(
"%s is public, but has a method that returns a non-public type: %s. "
+ "Due to limitations with java.lang.reflect.Proxy, this is not allowed. " + "Due to limitations with java.lang.reflect.Proxy, this is not allowed. "
+ "Please either make the factory non-public or the return type public.", + "Please either make the factory non-public or the return type public.",
factoryType, returnType); factoryType, returnType);
@ -380,8 +509,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
private boolean isTypeNotSpecified(TypeLiteral<?> typeLiteral, ConfigurationException ce) { private boolean isTypeNotSpecified(TypeLiteral<?> typeLiteral, ConfigurationException ce) {
Collection<Message> messages = ce.getErrorMessages(); Collection<Message> messages = ce.getErrorMessages();
if (messages.size() == 1) { if (messages.size() == 1) {
Message msg = Iterables.getOnlyElement( Message msg =
new Errors().keyNotFullySpecified(typeLiteral).getMessages()); Iterables.getOnlyElement(new Errors().keyNotFullySpecified(typeLiteral).getMessages());
return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage()); return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage());
} else { } else {
return false; return false;
@ -391,9 +520,9 @@ final class FactoryProvider2<F> implements InvocationHandler,
/** /**
* Finds a constructor suitable for the method. If the implementation contained any constructors * Finds a constructor suitable for the method. If the implementation contained any constructors
* marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly * marked with {@link AssistedInject}, this requires all {@link Assisted} parameters to exactly
* match the parameters (in any order) listed in the method. Otherwise, if no * match the parameters (in any order) listed in the method. Otherwise, if no {@link
* {@link AssistedInject} constructors exist, this will default to looking for an * AssistedInject} constructors exist, this will default to looking for an {@literal @}{@link
* {@literal @}{@link Inject} constructor. * Inject} constructor.
*/ */
private <T> InjectionPoint findMatchingConstructorInjectionPoint( private <T> InjectionPoint findMatchingConstructorInjectionPoint(
Method method, Key<?> returnType, TypeLiteral<T> implementation, List<Key<?>> paramList) Method method, Key<?> returnType, TypeLiteral<T> implementation, List<Key<?>> paramList)
@ -429,8 +558,7 @@ final class FactoryProvider2<F> implements InvocationHandler,
anyAssistedInjectConstructors = true; anyAssistedInjectConstructors = true;
if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) { if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) {
if (matchingConstructor != null) { if (matchingConstructor != null) {
errors errors.addMessage(
.addMessage(
"%s has more than one constructor annotated with @AssistedInject" "%s has more than one constructor annotated with @AssistedInject"
+ " that matches the parameters in method %s. Unable to create " + " that matches the parameters in method %s. Unable to create "
+ "AssistedInject factory.", + "AssistedInject factory.",
@ -444,7 +572,7 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
if (!anyAssistedInjectConstructors) { if (!anyAssistedInjectConstructors) {
// If none existed, use @Inject. // If none existed, use @Inject or a no-arg constructor.
try { try {
return InjectionPoint.forConstructorOf(implementation); return InjectionPoint.forConstructorOf(implementation);
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
@ -456,7 +584,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
if (matchingConstructor != null) { if (matchingConstructor != null) {
// safe because we got the constructor from this implementation. // safe because we got the constructor from this implementation.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
InjectionPoint ip = InjectionPoint.forConstructor( InjectionPoint ip =
InjectionPoint.forConstructor(
(Constructor<? super T>) matchingConstructor, implementation); (Constructor<? super T>) matchingConstructor, implementation);
return ip; return ip;
} else { } else {
@ -470,21 +599,19 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
/** /**
* Matching logic for constructors annotated with AssistedInject. * Matching logic for constructors annotated with AssistedInject. This returns true if and only if
* This returns true if and only if all @Assisted parameters in the * all @Assisted parameters in the constructor exactly match (in any order) all @Assisted
* constructor exactly match (in any order) all @Assisted parameters * parameters the method's parameter.
* the method's parameter.
*/ */
private boolean constructorHasMatchingParams(TypeLiteral<?> type, private boolean constructorHasMatchingParams(
Constructor<?> constructor, List<Key<?>> paramList, Errors errors) TypeLiteral<?> type, Constructor<?> constructor, List<Key<?>> paramList, Errors errors)
throws ErrorsException { throws ErrorsException {
List<TypeLiteral<?>> params = type.getParameterTypes(constructor); List<TypeLiteral<?>> params = type.getParameterTypes(constructor);
Annotation[][] paramAnnotations = constructor.getParameterAnnotations(); Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
int p = 0; int p = 0;
List<Key<?>> constructorKeys = Lists.newArrayList(); List<Key<?>> constructorKeys = Lists.newArrayList();
for (TypeLiteral<?> param : params) { for (TypeLiteral<?> param : params) {
Key<?> paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++], Key<?> paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++], errors);
errors);
constructorKeys.add(paramKey); constructorKeys.add(paramKey);
} }
// Require that every key exist in the constructor to match up exactly. // Require that every key exist in the constructor to match up exactly.
@ -504,10 +631,9 @@ final class FactoryProvider2<F> implements InvocationHandler,
return true; return true;
} }
/** /** Calculates all dependencies required by the implementation and constructor. */
* Calculates all dependencies required by the implementation and constructor. private Set<Dependency<?>> getDependencies(
*/ InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
private Set<Dependency<?>> getDependencies(InjectionPoint ctorPoint, TypeLiteral<?> implementation) {
ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder(); ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder();
builder.addAll(ctorPoint.getDependencies()); builder.addAll(ctorPoint.getDependencies());
if (!implementation.getRawType().isInterface()) { if (!implementation.getRawType().isInterface()) {
@ -518,9 +644,7 @@ final class FactoryProvider2<F> implements InvocationHandler,
return builder.build(); return builder.build();
} }
/** /** Return all non-assisted dependencies. */
* Return all non-assisted dependencies.
*/
private Set<Dependency<?>> removeAssistedDeps(Set<Dependency<?>> deps) { private Set<Dependency<?>> removeAssistedDeps(Set<Dependency<?>> deps) {
ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder(); ImmutableSet.Builder<Dependency<?>> builder = ImmutableSet.builder();
for (Dependency<?> dep : deps) { for (Dependency<?> dep : deps) {
@ -538,8 +662,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
* the assisted bindings are immediately provided. This looks for hints that the values may be * the assisted bindings are immediately provided. This looks for hints that the values may be
* lazily retrieved, by looking for injections of Injector or a Provider for the assisted values. * lazily retrieved, by looking for injections of Injector or a Provider for the assisted values.
*/ */
private boolean isValidForOptimizedAssistedInject(Set<Dependency<?>> dependencies, private boolean isValidForOptimizedAssistedInject(
Class<?> implementation, TypeLiteral<?> factoryType) { Set<Dependency<?>> dependencies, Class<?> implementation, TypeLiteral<?> factoryType) {
Set<Dependency<?>> badDeps = null; // optimization: create lazily Set<Dependency<?>> badDeps = null; // optimization: create lazily
for (Dependency<?> dep : dependencies) { for (Dependency<?> dep : dependencies) {
if (isInjectorOrAssistedProvider(dep)) { if (isInjectorOrAssistedProvider(dep)) {
@ -550,7 +674,9 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
} }
if (badDeps != null && !badDeps.isEmpty()) { if (badDeps != null && !badDeps.isEmpty()) {
logger.log(Level.WARNING, "AssistedInject factory {0} will be slow " logger.log(
Level.WARNING,
"AssistedInject factory {0} will be slow "
+ "because {1} has assisted Provider dependencies or injects the Injector. " + "because {1} has assisted Provider dependencies or injects the Injector. "
+ "Stop injecting @Assisted Provider<T> (instead use @Assisted T) " + "Stop injecting @Assisted Provider<T> (instead use @Assisted T) "
+ "or Injector to speed things up. (It will be a ~6500% speed bump!) " + "or Injector to speed things up. (It will be a ~6500% speed bump!) "
@ -562,33 +688,43 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
/** /**
* Returns true if the dependency is for {@link Injector} or if the dependency * Returns true if the dependency is for {@link Injector} or if the dependency is a {@link
* is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}. * Provider} for a parameter that is {@literal @}{@link Assisted}.
*/ */
private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) { private boolean isInjectorOrAssistedProvider(Dependency<?> dependency) {
Class<?> annotationType = dependency.getKey().getAnnotationType(); Class<?> annotationType = dependency.getKey().getAnnotationType();
if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted.. if (annotationType != null && annotationType.equals(Assisted.class)) { // If it's assisted..
if (dependency.getKey().getTypeLiteral().getRawType().equals(Provider.class)) { // And a Provider... if (dependency
.getKey()
.getTypeLiteral()
.getRawType()
.equals(Provider.class)) { // And a Provider...
return true; return true;
} }
} else if (dependency.getKey().getTypeLiteral().getRawType().equals(Injector.class)) { // If it's the Injector... } else if (dependency
.getKey()
.getTypeLiteral()
.getRawType()
.equals(Injector.class)) { // If it's the Injector...
return true; return true;
} }
return false; return false;
} }
/** /**
* Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. This
* This fails if another binding annotation is clobbered in the process. If the key already has * fails if another binding annotation is clobbered in the process. If the key already has the
* the {@literal @}Assisted annotation, it is returned as-is to preserve any String value. * {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
*/ */
private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException { private <T> Key<T> assistKey(Method method, Key<T> key, Errors errors) throws ErrorsException {
if (key.getAnnotationType() == null) { if (key.getAnnotationType() == null) {
return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION); return key.withAnnotation(DEFAULT_ANNOTATION);
} else if (key.getAnnotationType() == Assisted.class) { } else if (key.getAnnotationType() == Assisted.class) {
return key; return key;
} else { } else {
errors.withSource(method).addMessage( errors
.withSource(method)
.addMessage(
"Only @Assisted is allowed for factory parameters, but found @%s", "Only @Assisted is allowed for factory parameters, but found @%s",
key.getAnnotationType()); key.getAnnotationType());
throw errors.toException(); throw errors.toException();
@ -596,14 +732,17 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
/** /**
* At injector-creation time, we initialize the invocation handler. At this time we make sure * At injector-creation time, we initialize the invocation handler. At this time we make sure all
* all factory methods will be able to build the target types. * factory methods will be able to build the target types.
*/ */
@Inject @Inject
@Toolable @Toolable
void initialize(Injector injector) { void initialize(Injector injector) {
if (this.injector != null) { if (this.injector != null) {
throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class, throw new ConfigurationException(
ImmutableList.of(
new Message(
FactoryProvider2.class,
"Factories.create() factories may only be used in one Injector!"))); "Factories.create() factories may only be used in one Injector!")));
} }
@ -619,7 +758,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
} else { } else {
args = null; // won't be used -- instead will bind to data.providers. args = null; // won't be used -- instead will bind to data.providers.
} }
getBindingFromNewInjector(method, args, data); // throws if the binding isn't properly configured getBindingFromNewInjector(
method, args, data); // throws if the binding isn't properly configured
} }
} }
@ -628,7 +768,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
*/ */
public Binding<?> getBindingFromNewInjector( public Binding<?> getBindingFromNewInjector(
final Method method, final Object[] args, final AssistData data) { final Method method, final Object[] args, final AssistData data) {
checkState(injector != null, checkState(
injector != null,
"Factories.create() factories cannot be used until they're initialized by Guice."); "Factories.create() factories cannot be used until they're initialized by Guice.");
final Key<?> returnType = data.returnType; final Key<?> returnType = data.returnType;
@ -636,17 +777,21 @@ final class FactoryProvider2<F> implements InvocationHandler,
// We ignore any pre-existing binding annotation. // We ignore any pre-existing binding annotation.
final Key<?> returnKey = Key.get(returnType.getTypeLiteral(), RETURN_ANNOTATION); final Key<?> returnKey = Key.get(returnType.getTypeLiteral(), RETURN_ANNOTATION);
Module assistedModule = new AbstractModule() { Module assistedModule =
new AbstractModule() {
@Override @Override
@SuppressWarnings({ @SuppressWarnings({
"unchecked", "rawtypes"}) // raw keys are necessary for the args array and return value "unchecked",
"rawtypes"
}) // raw keys are necessary for the args array and return value
protected void configure() { protected void configure() {
Binder binder = binder().withSource(method); Binder binder = binder().withSource(method);
int p = 0; int p = 0;
if (!data.optimized) { if (!data.optimized) {
for (Key<?> paramKey : data.paramTypes) { for (Key<?> paramKey : data.paramTypes) {
// Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter // Wrap in a Provider to cover null, and to prevent Guice from injecting the
// parameter
binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
} }
} else { } else {
@ -661,7 +806,8 @@ final class FactoryProvider2<F> implements InvocationHandler,
// but if it isn't, we'll end up throwing a fairly good error // but if it isn't, we'll end up throwing a fairly good error
// message for the user. // message for the user.
if (constructor != null) { if (constructor != null) {
binder.bind(returnKey) binder
.bind(returnKey)
.toConstructor(constructor, (TypeLiteral) data.implementationType) .toConstructor(constructor, (TypeLiteral) data.implementationType)
.in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type .in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type
} }
@ -681,6 +827,7 @@ final class FactoryProvider2<F> implements InvocationHandler,
* When a factory method is invoked, we create a child injector that binds all parameters, then * When a factory method is invoked, we create a child injector that binds all parameters, then
* use that to get an instance of the return type. * use that to get an instance of the return type.
*/ */
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
// If we setup a method handle earlier for this method, call it. // If we setup a method handle earlier for this method, call it.
// This is necessary for default methods that java8 creates, so we // This is necessary for default methods that java8 creates, so we
@ -749,93 +896,19 @@ final class FactoryProvider2<F> implements InvocationHandler,
return factoryKey.equals(other.factoryKey) && Objects.equal(collector, other.collector); return factoryKey.equals(other.factoryKey) && Objects.equal(collector, other.collector);
} }
/** /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */
* All the data necessary to perform an assisted inject. static boolean canRethrow(Method invoked, Throwable thrown) {
*/ if (thrown instanceof Error || thrown instanceof RuntimeException) {
private static class AssistData implements AssistedMethod { return true;
/**
* the constructor the implementation is constructed with.
*/
final Constructor<?> constructor;
/**
* the return type in the factory method that the constructor is bound to.
*/
final Key<?> returnType;
/**
* the parameters in the factory method associated with this data.
*/
final ImmutableList<Key<?>> paramTypes;
/**
* the type of the implementation constructed
*/
final TypeLiteral<?> implementationType;
/**
* All non-assisted dependencies required by this method.
*/
final Set<Dependency<?>> dependencies;
/**
* The factory method associated with this data
*/
final Method factoryMethod;
/**
* true if {@link #isValidForOptimizedAssistedInject} returned true.
*/
final boolean optimized;
/**
* the list of optimized providers, empty if not optimized.
*/
final List<ThreadLocalProvider> providers;
/**
* used to perform optimized factory creations.
*/
volatile Binding<?> cachedBinding; // TODO: volatile necessary?
AssistData(Constructor<?> constructor, Key<?> returnType, ImmutableList<Key<?>> paramTypes,
TypeLiteral<?> implementationType, Method factoryMethod,
Set<Dependency<?>> dependencies,
boolean optimized, List<ThreadLocalProvider> providers) {
this.constructor = constructor;
this.returnType = returnType;
this.paramTypes = paramTypes;
this.implementationType = implementationType;
this.factoryMethod = factoryMethod;
this.dependencies = dependencies;
this.optimized = optimized;
this.providers = providers;
} }
@Override for (Class<?> declared : invoked.getExceptionTypes()) {
public String toString() { if (declared.isInstance(thrown)) {
return MoreObjects.toStringHelper(getClass()) return true;
.add("ctor", constructor) }
.add("return type", returnType)
.add("param type", paramTypes)
.add("implementation type", implementationType)
.add("dependencies", dependencies)
.add("factory method", factoryMethod)
.add("optimized", optimized)
.add("providers", providers)
.add("cached binding", cachedBinding)
.toString();
} }
public Set<Dependency<?>> getDependencies() { return false;
return dependencies;
}
public Method getFactoryMethod() {
return factoryMethod;
}
public Constructor<?> getImplementationConstructor() {
return constructor;
}
public TypeLiteral<?> getImplementationType() {
return implementationType;
}
} }
// not <T> because we'll never know and this is easier than suppressing warnings. // not <T> because we'll never know and this is easier than suppressing warnings.
@ -848,86 +921,106 @@ final class FactoryProvider2<F> implements InvocationHandler,
} }
} }
/** private static MethodHandle superMethodHandle(
* Wrapper around MethodHandles/MethodHandle, so we can compile+run on java6. Method method, Object proxy, MethodHandles.Lookup userLookups)
*/ throws ReflectiveOperationException {
private static class MethodHandleWrapper { MethodHandles.Lookup lookup = userLookups == null ? MethodHandles.lookup() : userLookups;
static final int ALL_MODES = Modifier.PRIVATE MethodHandle handle = SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup);
| Modifier.STATIC /* package */ return handle != null ? handle.bindTo(proxy) : null;
| Modifier.PUBLIC }
| Modifier.PROTECTED;
static final Method unreflectSpecial; // begin by trying unreflectSpecial to find super method handles; this should work on Java14+
static final Method bindTo; private static final AtomicReference<SuperMethodLookup> SUPER_METHOD_LOOKUP =
static final Method invokeWithArguments; new AtomicReference<>(SuperMethodLookup.UNREFLECT_SPECIAL);
static final Constructor<?> lookupCxtor;
static final boolean valid;
static { private static enum SuperMethodLookup {
Method unreflectSpecialTmp = null; UNREFLECT_SPECIAL {
Method bindToTmp = null; @Override
Method invokeWithArgumentsTmp = null; MethodHandle superMethodHandle(Method method, MethodHandles.Lookup lookup)
boolean validTmp = false; throws ReflectiveOperationException {
Constructor<?> lookupCxtorTmp = null;
try { try {
Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup"); return lookup.unreflectSpecial(method, method.getDeclaringClass());
unreflectSpecialTmp = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class); } catch (ReflectiveOperationException e) {
Class<?> methodHandleClass = Class.forName("java.lang.invoke.MethodHandle"); // fall back to findSpecial which should work on Java9+; use that for future lookups
bindToTmp = methodHandleClass.getMethod("bindTo", Object.class); SUPER_METHOD_LOOKUP.compareAndSet(this, FIND_SPECIAL);
invokeWithArgumentsTmp = methodHandleClass.getMethod("invokeWithArguments", Object[].class); return SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup);
lookupCxtorTmp = lookupClass.getDeclaredConstructor(Class.class, int.class);
lookupCxtorTmp.setAccessible(true);
validTmp = true;
} catch (Exception invalid) {
// Ignore the exception, store the values & exit early in create(..) if invalid.
} }
// Store refs to later.
valid = validTmp;
unreflectSpecial = unreflectSpecialTmp;
bindTo = bindToTmp;
invokeWithArguments = invokeWithArgumentsTmp;
lookupCxtor = lookupCxtorTmp;
}
final Object handle;
MethodHandleWrapper(Object handle) {
this.handle = handle;
}
static MethodHandleWrapper create(Method method, Object proxy) {
if (!valid) {
return null;
} }
},
FIND_SPECIAL {
@Override
MethodHandle superMethodHandle(Method method, MethodHandles.Lookup lookup)
throws ReflectiveOperationException {
try { try {
Class<?> declaringClass = method.getDeclaringClass(); Class<?> declaringClass = method.getDeclaringClass();
// Note: this isn't a public API, but we need to use it in order to call default methods. // use findSpecial to workaround https://bugs.openjdk.java.net/browse/JDK-8209005
Object lookup = lookupCxtor.newInstance(declaringClass, ALL_MODES); return lookup.findSpecial(
method.setAccessible(true); declaringClass,
// These are part of the public API, but we use reflection since we run on java6 method.getName(),
// and they were introduced in java7. MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
lookup = unreflectSpecial.invoke(lookup, method, declaringClass); declaringClass);
Object handle = bindTo.invoke(lookup, proxy); } catch (ReflectiveOperationException e) {
return new MethodHandleWrapper(handle); if (!allowLookupReflection) {
} catch (InvocationTargetException ite) { throw e;
return null; }
} catch (IllegalAccessException iae) { // fall back to private Lookup which should work on Java8; use that for future lookups
return null; SUPER_METHOD_LOOKUP.compareAndSet(this, PRIVATE_LOOKUP);
} catch (InstantiationException ie) { return SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup);
return null;
} }
} }
},
Object invokeWithArguments(Object[] args) throws Exception { PRIVATE_LOOKUP {
// We must cast the args to an object so the Object[] is the first param,
// as opposed to each individual varargs param.
return invokeWithArguments.invoke(handle, (Object) args);
}
@Override @Override
public String toString() { MethodHandle superMethodHandle(Method method, MethodHandles.Lookup unused)
return handle.toString(); throws ReflectiveOperationException {
return PrivateLookup.superMethodHandle(method);
}
};
abstract MethodHandle superMethodHandle(Method method, MethodHandles.Lookup lookup)
throws ReflectiveOperationException;
};
// Note: this isn't a public API, but we need to use it in order to call default methods on (or
// with) non-public types. If it doesn't exist, the code falls back to a less precise check.
static class PrivateLookup {
PrivateLookup() {}
private static final int ALL_MODES =
Modifier.PRIVATE | Modifier.STATIC /* package */ | Modifier.PUBLIC | Modifier.PROTECTED;
private static final Constructor<MethodHandles.Lookup> privateLookupCxtor =
findPrivateLookupCxtor();
private static Constructor<MethodHandles.Lookup> findPrivateLookupCxtor() {
try {
Constructor<MethodHandles.Lookup> cxtor;
try {
cxtor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
} catch (NoSuchMethodException ignored) {
cxtor =
MethodHandles.Lookup.class.getDeclaredConstructor(
Class.class, Class.class, int.class);
}
cxtor.setAccessible(true);
return cxtor;
} catch (ReflectiveOperationException | SecurityException e) {
return null;
}
}
static MethodHandle superMethodHandle(Method method) throws ReflectiveOperationException {
if (privateLookupCxtor == null) {
return null; // fall back to assistDataBuilder workaround
}
Class<?> declaringClass = method.getDeclaringClass();
MethodHandles.Lookup lookup;
if (privateLookupCxtor.getParameterCount() == 2) {
lookup = privateLookupCxtor.newInstance(declaringClass, ALL_MODES);
} else {
lookup = privateLookupCxtor.newInstance(declaringClass, null, ALL_MODES);
}
return lookup.unreflectSpecial(method, declaringClass);
} }
} }
} }

View file

@ -14,17 +14,16 @@ import com.google.inject.Stage;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.spi.DefaultBindingTargetVisitor; import com.google.inject.spi.DefaultBindingTargetVisitor;
import java.util.Set;
/** /**
* Guarantees that processing of Binding elements happens in a sane way. * Guarantees that processing of Binding elements happens in a sane way.
*/ */
abstract class AbstractBindingProcessor extends AbstractProcessor { abstract class AbstractBindingProcessor extends AbstractProcessor {
// It's unfortunate that we have to maintain a blacklist of specific // It's unfortunate that we have to maintain a list of specific
// classes, but we can't easily block the whole package because of // classes, but we can't easily block the whole package because of
// all our unit tests. // all our unit tests.
private static final Set<Class<?>> FORBIDDEN_TYPES = ImmutableSet.of( private static final ImmutableSet<Class<?>> FORBIDDEN_TYPES =
ImmutableSet.<Class<?>>of(
AbstractModule.class, AbstractModule.class,
Binder.class, Binder.class,
Binding.class, Binding.class,
@ -37,11 +36,11 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
Stage.class, Stage.class,
TypeLiteral.class); TypeLiteral.class);
protected final ProcessedBindingData bindingData; protected final ProcessedBindingData processedBindingData;
AbstractBindingProcessor(Errors errors, ProcessedBindingData bindingData) { AbstractBindingProcessor(Errors errors, ProcessedBindingData processedBindingData) {
super(errors); super(errors);
this.bindingData = bindingData; this.processedBindingData = processedBindingData;
} }
protected <T> UntargettedBindingImpl<T> invalidBinding( protected <T> UntargettedBindingImpl<T> invalidBinding(
@ -61,10 +60,10 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
BindingImpl<?> original = injector.getExistingBinding(key); BindingImpl<?> original = injector.getExistingBinding(key);
if (original != null) { if (original != null) {
// If it failed because of an explicit duplicate binding... // If it failed because of an explicit duplicate binding...
if (injector.state.getExplicitBinding(key) != null) { if (injector.getBindingData().getExplicitBinding(key) != null) {
try { try {
if (!isOkayDuplicate(original, binding, injector.state)) { if (!isOkayDuplicate(original, binding, injector.getBindingData())) {
errors.bindingAlreadySet(key, original.getSource()); errors.bindingAlreadySet(binding, original);
return; return;
} }
} catch (Throwable t) { } catch (Throwable t) {
@ -80,27 +79,34 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
} }
// prevent the parent from creating a JIT binding for this key // prevent the parent from creating a JIT binding for this key
injector.state.parent().blacklist(key, injector.state, binding.getSource()); injector
injector.state.putBinding(key, binding); .getJitBindingData()
.banKeyInParent(key, injector.getBindingData(), binding.getSource());
injector.getBindingData().putBinding(key, binding);
} }
/** /**
* We tolerate duplicate bindings if one exposes the other or if the two bindings * We tolerate duplicate bindings if one exposes the other or if the two bindings are considered
* are considered duplicates. * duplicates (see {@link Bindings#areDuplicates(BindingImpl, BindingImpl)}.
* *
* @param original the binding in the parent injector (candidate for an exposing binding) * @param original the binding in the parent injector (candidate for an exposing binding)
* @param binding the binding to check (candidate for the exposed binding) * @param binding the binding to check (candidate for the exposed binding)
*/ */
private boolean isOkayDuplicate(BindingImpl<?> original, BindingImpl<?> binding, State state) { private static boolean isOkayDuplicate(
BindingImpl<?> original, BindingImpl<?> binding, InjectorBindingData bindingData) {
if (original instanceof ExposedBindingImpl) { if (original instanceof ExposedBindingImpl) {
ExposedBindingImpl<?> exposed = (ExposedBindingImpl) original; ExposedBindingImpl<?> exposed = (ExposedBindingImpl<?>) original;
InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector(); InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector();
return (exposedFrom == binding.getInjector()); return (exposedFrom == binding.getInjector());
} else { } else {
original = (BindingImpl<?>) state.getExplicitBindingsThisLevel().get(binding.getKey()); original = (BindingImpl<?>) bindingData.getExplicitBindingsThisLevel().get(binding.getKey());
// If no original at this level, the original was on a parent, and we don't // If no original at this level, the original was on a parent, and we don't
// allow deduplication between parents & children. // allow deduplication between parents & children.
return original != null && original.equals(binding); if (original == null) {
return false;
} else {
return original.equals(binding);
}
} }
} }
@ -110,8 +116,8 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
} }
/** /**
* Processor for visiting bindings. Each overriden method that wants to * Processor for visiting bindings. Each overriden method that wants to actually process the
* actually process the binding should call prepareBinding first. * binding should call prepareBinding first.
*/ */
abstract class Processor<T, V> extends DefaultBindingTargetVisitor<T, V> { abstract class Processor<T, V> extends DefaultBindingTargetVisitor<T, V> {
final Object source; final Object source;
@ -136,7 +142,7 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
* initialially processed. * initialially processed.
*/ */
protected void scheduleInitialization(BindingImpl<?> binding) { protected void scheduleInitialization(BindingImpl<?> binding) {
bindingData.addUninitializedBinding(() -> initializeBinding(binding)); processedBindingData.addUninitializedBinding(() -> initializeBinding(binding));
} }
/** /**
@ -144,7 +150,7 @@ abstract class AbstractBindingProcessor extends AbstractProcessor {
* bindings. * bindings.
*/ */
protected void scheduleDelayedInitialization(BindingImpl<?> binding) { protected void scheduleDelayedInitialization(BindingImpl<?> binding) {
bindingData.addDelayedUninitializedBinding(() -> initializeBinding(binding)); processedBindingData.addDelayedUninitializedBinding(() -> initializeBinding(binding));
} }
private void initializeBinding(BindingImpl<?> binding) { private void initializeBinding(BindingImpl<?> binding) {

View file

@ -2,7 +2,6 @@ package com.google.inject.internal;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.base.Function;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner; import com.google.common.base.Joiner.MapJoiner;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@ -18,8 +17,6 @@ import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Classes; import com.google.inject.internal.util.Classes;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import javax.inject.Qualifier;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -30,40 +27,16 @@ import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import javax.inject.Qualifier;
/** /**
* Annotation utilities. * Annotation utilities.
* *
* @author crazybob@google.com (Bob Lee)
*/ */
public class Annotations { public class Annotations {
private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("="); /** Returns {@code true} if the given annotation type has no attributes. */
private static final Function<Object, String> DEEP_TO_STRING_FN = new Function<Object, String>() {
@Override
public String apply(Object arg) {
String s = Arrays.deepToString(new Object[]{arg});
return s.substring(1, s.length() - 1); // cut off brackets
}
};
private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
CacheBuilder.newBuilder()
.weakKeys()
.build(new CacheLoader<>() {
@Override
public Annotation load(Class<? extends Annotation> input) {
return generateAnnotationImpl(input);
}
});
private static final AnnotationChecker scopeChecker = new AnnotationChecker(
Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));
private static final AnnotationChecker bindingAnnotationChecker = new AnnotationChecker(
Arrays.asList(BindingAnnotation.class, Qualifier.class));
/**
* Returns {@code true} if the given annotation type has no attributes.
*/
public static boolean isMarker(Class<? extends Annotation> annotationType) { public static boolean isMarker(Class<? extends Annotation> annotationType) {
return annotationType.getDeclaredMethods().length == 0; return annotationType.getDeclaredMethods().length == 0;
} }
@ -79,11 +52,22 @@ public class Annotations {
return hasMethods; return hasMethods;
} }
private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
CacheBuilder.newBuilder()
.weakKeys()
.build(
new CacheLoader<Class<? extends Annotation>, Annotation>() {
@Override
public Annotation load(Class<? extends Annotation> input) {
return generateAnnotationImpl(input);
}
});
/** /**
* Generates an Annotation for the annotation class. Requires that the annotation is all * Generates an Annotation for the annotation class. Requires that the annotation is all
* optionals. * optionals.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") // Safe because generateAnnotationImpl returns T for Class<T>
public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) { public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) {
Preconditions.checkState( Preconditions.checkState(
isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType); isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType);
@ -92,23 +76,26 @@ public class Annotations {
private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) { private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) {
final Map<String, Object> members = resolveMembers(annotationType); final Map<String, Object> members = resolveMembers(annotationType);
return annotationType.cast(Proxy.newProxyInstance( return annotationType.cast(
Proxy.newProxyInstance(
annotationType.getClassLoader(), annotationType.getClassLoader(),
new Class<?>[] {annotationType}, new Class<?>[] {annotationType},
(proxy, method, args) -> { new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
String name = method.getName(); String name = method.getName();
switch (name) { if (name.equals("annotationType")) {
case "annotationType":
return annotationType; return annotationType;
case "toString": } else if (name.equals("toString")) {
return annotationToString(annotationType, members); return annotationToString(annotationType, members);
case "hashCode": } else if (name.equals("hashCode")) {
return annotationHashCode(annotationType, members); return annotationHashCode(annotationType, members);
case "equals": } else if (name.equals("equals")) {
return annotationEquals(annotationType, members, args[0]); return annotationEquals(annotationType, members, args[0]);
default: } else {
return members.get(name); return members.get(name);
} }
}
})); }));
} }
@ -121,11 +108,10 @@ public class Annotations {
return result.build(); return result.build();
} }
/** /** Implements {@link Annotation#equals}. */
* Implements {@link Annotation#equals}. private static boolean annotationEquals(
*/ Class<? extends Annotation> type, Map<String, Object> members, Object other)
private static boolean annotationEquals(Class<? extends Annotation> type, throws Exception {
Map<String, Object> members, Object other) throws Exception {
if (!type.isInstance(other)) { if (!type.isInstance(other)) {
return false; return false;
} }
@ -139,11 +125,9 @@ public class Annotations {
return true; return true;
} }
/** /** Implements {@link Annotation#hashCode}. */
* Implements {@link Annotation#hashCode}. private static int annotationHashCode(
*/ Class<? extends Annotation> type, Map<String, Object> members) throws Exception {
private static int annotationHashCode(Class<? extends Annotation> type,
Map<String, Object> members) throws Exception {
int result = 0; int result = 0;
for (Method method : type.getDeclaredMethods()) { for (Method method : type.getDeclaredMethods()) {
String name = method.getName(); String name = method.getName();
@ -153,36 +137,38 @@ public class Annotations {
return result; return result;
} }
/** private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("=");
* Implements {@link Annotation#toString}.
*/ /** Implements {@link Annotation#toString}. */
private static String annotationToString(Class<? extends Annotation> type, private static String annotationToString(
Map<String, Object> members) throws Exception { Class<? extends Annotation> type, Map<String, Object> members) throws Exception {
StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("("); StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("(");
JOINER.appendTo(sb, Maps.transformValues(members, DEEP_TO_STRING_FN)); JOINER.appendTo(
sb,
Maps.transformValues(
members,
arg -> {
String s = Arrays.deepToString(new Object[] {arg});
return s.substring(1, s.length() - 1); // cut off brackets
}));
return sb.append(")").toString(); return sb.append(")").toString();
} }
/** /** Returns true if the given annotation is retained at runtime. */
* Returns true if the given annotation is retained at runtime.
*/
public static boolean isRetainedAtRuntime(Class<? extends Annotation> annotationType) { public static boolean isRetainedAtRuntime(Class<? extends Annotation> annotationType) {
Retention retention = annotationType.getAnnotation(Retention.class); Retention retention = annotationType.getAnnotation(Retention.class);
return retention != null && retention.value() == RetentionPolicy.RUNTIME; return retention != null && retention.value() == RetentionPolicy.RUNTIME;
} }
/** /** Returns the scope annotation on {@code type}, or null if none is specified. */
* Returns the scope annotation on {@code type}, or null if none is specified.
*/
public static Class<? extends Annotation> findScopeAnnotation( public static Class<? extends Annotation> findScopeAnnotation(
Errors errors, Class<?> implementation) { Errors errors, Class<?> implementation) {
return findScopeAnnotation(errors, implementation.getAnnotations()); return findScopeAnnotation(errors, implementation.getAnnotations());
} }
/** /** Returns the scoping annotation, or null if there isn't one. */
* Returns the scoping annotation, or null if there isn't one. public static Class<? extends Annotation> findScopeAnnotation(
*/ Errors errors, Annotation[] annotations) {
public static Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
Class<? extends Annotation> found = null; Class<? extends Annotation> found = null;
for (Annotation annotation : annotations) { for (Annotation annotation : annotations) {
@ -210,10 +196,50 @@ public class Annotations {
return false; return false;
} }
private static final boolean QUOTE_MEMBER_VALUES = determineWhetherToQuote(); private static class AnnotationToStringConfig {
final boolean quote;
final boolean includeMemberName;
AnnotationToStringConfig(boolean quote, boolean includeMemberName) {
this.quote = quote;
this.includeMemberName = includeMemberName;
}
}
private static final AnnotationToStringConfig ANNOTATION_TO_STRING_CONFIG =
determineAnnotationToStringConfig();
/**
* Returns {@code value}, quoted if annotation implementations quote their member values. In Java
* 9, annotations quote their string members.
*/
public static String memberValueString(String value) { public static String memberValueString(String value) {
return QUOTE_MEMBER_VALUES ? "\"" + value + "\"" : value; return ANNOTATION_TO_STRING_CONFIG.quote ? "\"" + value + "\"" : value;
}
/**
* Returns string representation of the annotation memeber.
*
* <p>The value of the member is prefixed with `memberName=` unless the runtime omits the member
* name. The value of the member is quoted if annotation implementations quote their member values
* and the value type is String.
*
* <p>In Java 9, annotations quote their string members and in Java 15, the member name is
* omitted.
*/
public static String memberValueString(String memberName, Object value) {
StringBuilder sb = new StringBuilder();
boolean quote = ANNOTATION_TO_STRING_CONFIG.quote;
boolean includeMemberName = ANNOTATION_TO_STRING_CONFIG.includeMemberName;
if (includeMemberName) {
sb.append(memberName).append('=');
}
if (quote && (value instanceof String)) {
sb.append('"').append(value).append('"');
} else {
sb.append(value);
}
return sb.toString();
} }
@Retention(RUNTIME) @Retention(RUNTIME)
@ -221,26 +247,64 @@ public class Annotations {
String value(); String value();
} }
@TestAnnotation("determineWhetherToQuote") @TestAnnotation("determineAnnotationToStringConfig")
private static boolean determineWhetherToQuote() { private static AnnotationToStringConfig determineAnnotationToStringConfig() {
try { try {
String annotation = Annotations.class String annotation =
.getDeclaredMethod("determineWhetherToQuote") Annotations.class
.getDeclaredMethod("determineAnnotationToStringConfig")
.getAnnotation(TestAnnotation.class) .getAnnotation(TestAnnotation.class)
.toString(); .toString();
return annotation.contains("\"determineWhetherToQuote\""); boolean quote = annotation.contains("\"determineAnnotationToStringConfig\"");
boolean includeMemberName = annotation.contains("value=");
return new AnnotationToStringConfig(quote, includeMemberName);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
/** Checks for the presence of annotations. Caches results because Android doesn't. */
static class AnnotationChecker {
private final Collection<Class<? extends Annotation>> annotationTypes;
/** Returns true if the given class has one of the desired annotations. */
private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
new CacheLoader<Class<? extends Annotation>, Boolean>() {
@Override
public Boolean load(Class<? extends Annotation> annotationType) {
for (Annotation annotation : annotationType.getAnnotations()) {
if (annotationTypes.contains(annotation.annotationType())) {
return true;
}
}
return false;
}
};
final LoadingCache<Class<? extends Annotation>, Boolean> cache =
CacheBuilder.newBuilder().weakKeys().build(hasAnnotations);
/** Constructs a new checker that looks for annotations of the given types. */
AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
this.annotationTypes = annotationTypes;
}
/** Returns true if the given type has one of the desired annotations. */
boolean hasAnnotations(Class<? extends Annotation> annotated) {
return cache.getUnchecked(annotated);
}
}
private static final AnnotationChecker scopeChecker =
new AnnotationChecker(Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));
public static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) { public static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) {
return scopeChecker.hasAnnotations(annotationType); return scopeChecker.hasAnnotations(annotationType);
} }
/** /**
* Adds an error if there is a misplaced annotations on {@code type}. Scoping * Adds an error if there is a misplaced annotations on {@code type}. Scoping annotations are not
* annotations are not allowed on abstract classes or interfaces. * allowed on abstract classes or interfaces.
*/ */
public static void checkForMisplacedScopeAnnotations( public static void checkForMisplacedScopeAnnotations(
Class<?> type, Object source, Errors errors) { Class<?> type, Object source, Errors errors) {
@ -256,21 +320,22 @@ public class Annotations {
} }
} }
/** // NOTE: getKey/findBindingAnnotation are used by Gin which is abandoned. So changing this API
* Gets a key for the given type, member and annotations. // will prevent Gin users from upgrading Guice version.
*/
public static Key<?> getKey(TypeLiteral<?> type, Member member, Annotation[] annotations, /** Gets a key for the given type, member and annotations. */
Errors errors) throws ErrorsException { public static Key<?> getKey(
TypeLiteral<?> type, Member member, Annotation[] annotations, Errors errors)
throws ErrorsException {
int numErrorsBefore = errors.size(); int numErrorsBefore = errors.size();
Annotation found = findBindingAnnotation(errors, member, annotations); Annotation found = findBindingAnnotation(errors, member, annotations);
errors.throwIfNewErrors(numErrorsBefore); errors.throwIfNewErrors(numErrorsBefore);
return found == null ? Key.get(type) : Key.get(type, found); return found == null ? Key.get(type) : Key.get(type, found);
} }
/** /** Returns the binding annotation on {@code member}, or null if there isn't one. */
* Returns the binding annotation on {@code member}, or null if there isn't one. public static Annotation findBindingAnnotation(
*/ Errors errors, Member member, Annotation[] annotations) {
public static Annotation findBindingAnnotation(Errors errors, Member member, Annotation[] annotations) {
Annotation found = null; Annotation found = null;
for (Annotation annotation : annotations) { for (Annotation annotation : annotations) {
@ -287,9 +352,10 @@ public class Annotations {
return found; return found;
} }
/** private static final AnnotationChecker bindingAnnotationChecker =
* Returns true if annotations of the specified type are binding annotations. new AnnotationChecker(Arrays.asList(BindingAnnotation.class, Qualifier.class));
*/
/** Returns true if annotations of the specified type are binding annotations. */
public static boolean isBindingAnnotation(Class<? extends Annotation> annotationType) { public static boolean isBindingAnnotation(Class<? extends Annotation> annotationType) {
return bindingAnnotationChecker.hasAnnotations(annotationType); return bindingAnnotationChecker.hasAnnotations(annotationType);
} }
@ -336,44 +402,4 @@ public class Annotations {
return ""; return "";
} }
} }
/**
* Checks for the presence of annotations. Caches results because Android doesn't.
*/
static class AnnotationChecker {
private final Collection<Class<? extends Annotation>> annotationTypes;
/**
* Returns true if the given class has one of the desired annotations.
*/
private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
new CacheLoader<>() {
@Override
public Boolean load(Class<? extends Annotation> annotationType) {
for (Annotation annotation : annotationType.getAnnotations()) {
if (annotationTypes.contains(annotation.annotationType())) {
return true;
}
}
return false;
}
};
final LoadingCache<Class<? extends Annotation>, Boolean> cache =
CacheBuilder.newBuilder().weakKeys().build(hasAnnotations);
/**
* Constructs a new checker that looks for annotations of the given types.
*/
AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
this.annotationTypes = annotationTypes;
}
/**
* Returns true if the given type has one of the desired annotations.
*/
boolean hasAnnotations(Class<? extends Annotation> annotated) {
return cache.getUnchecked(annotated);
}
}
} }

View file

@ -0,0 +1,52 @@
package com.google.inject.internal;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binding;
import com.google.inject.spi.ErrorDetail;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.stream.Collectors;
/** Error reported by Guice when a key is bound at multiple places the injector. */
final class BindingAlreadySetError extends InternalErrorDetail<BindingAlreadySetError> {
private final Binding<?> binding;
private final Binding<?> original;
BindingAlreadySetError(Binding<?> binding, Binding<?> original, List<Object> sources) {
super(
ErrorId.BINDING_ALREADY_SET,
String.format("%s was bound multiple times.", Messages.convert(binding.getKey())),
sources,
null);
this.binding = binding;
this.original = original;
}
@Override
public boolean isMergeable(ErrorDetail<?> otherError) {
return otherError instanceof BindingAlreadySetError
&& ((BindingAlreadySetError) otherError).binding.getKey().equals(binding.getKey());
}
@Override
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
List<List<Object>> sourcesList = new ArrayList<>();
sourcesList.add(ImmutableList.of(original.getSource()));
sourcesList.add(ImmutableList.of(binding.getSource()));
sourcesList.addAll(
mergeableErrors.stream()
.map(e -> ((BindingAlreadySetError) e).binding.getSource())
.map(ImmutableList::of)
.collect(Collectors.toList()));
formatter.format("%n%s%n", Messages.bold("Bound at:"));
for (int i = 0; i < sourcesList.size(); i++) {
ErrorFormatter.formatSources(i + 1, sourcesList.get(i), formatter);
}
}
@Override
public BindingAlreadySetError withSources(List<Object> newSources) {
return new BindingAlreadySetError(binding, original, newSources);
}
}

View file

@ -15,7 +15,6 @@ import com.google.inject.spi.ProviderBinding;
import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.ProviderKeyBinding;
import com.google.inject.spi.UntargettedBinding; import com.google.inject.spi.UntargettedBinding;
import java.util.Set; import java.util.Set;
/** /**
@ -25,8 +24,9 @@ final class BindingProcessor extends AbstractBindingProcessor {
private final Initializer initializer; private final Initializer initializer;
BindingProcessor(Errors errors, Initializer initializer, ProcessedBindingData bindingData) { BindingProcessor(
super(errors, bindingData); Errors errors, Initializer initializer, ProcessedBindingData processedBindingData) {
super(errors, processedBindingData);
this.initializer = initializer; this.initializer = initializer;
} }
@ -49,13 +49,22 @@ final class BindingProcessor extends AbstractBindingProcessor {
return true; return true;
} }
return command.acceptTargetVisitor(new Processor<>((BindingImpl<T>) command) { return command.acceptTargetVisitor(
new Processor<T, Boolean>((BindingImpl<T>) command) {
@Override @Override
public Boolean visit(ConstructorBinding<? extends T> binding) { public Boolean visit(ConstructorBinding<? extends T> binding) {
prepareBinding(); prepareBinding();
try { try {
ConstructorBindingImpl<T> onInjector = ConstructorBindingImpl.create(injector, key, ConstructorBindingImpl<T> onInjector =
binding.getConstructor(), source, scoping, errors, false, false); ConstructorBindingImpl.create(
injector,
key,
binding.getConstructor(),
source,
scoping,
errors,
false,
false);
scheduleInitialization(onInjector); scheduleInitialization(onInjector);
putBinding(onInjector); putBinding(onInjector);
} catch (ErrorsException e) { } catch (ErrorsException e) {
@ -72,13 +81,15 @@ final class BindingProcessor extends AbstractBindingProcessor {
T instance = binding.getInstance(); T instance = binding.getInstance();
@SuppressWarnings("unchecked") // safe to cast to binding<T> because @SuppressWarnings("unchecked") // safe to cast to binding<T> because
// the processor was constructed w/ it // the processor was constructed w/ it
Initializable<T> ref = initializer.requestInjection( Initializable<T> ref =
initializer.requestInjection(
injector, instance, (Binding<T>) binding, source, injectionPoints); injector, instance, (Binding<T>) binding, source, injectionPoints);
ConstantFactory<? extends T> factory = new ConstantFactory<>(ref); ConstantFactory<? extends T> factory = new ConstantFactory<>(ref);
InternalFactory<? extends T> scopedFactory = InternalFactory<? extends T> scopedFactory =
Scoping.scope(key, injector, factory, source, scoping); Scoping.scope(key, injector, factory, source, scoping);
putBinding(new InstanceBindingImpl<>(injector, key, source, putBinding(
scopedFactory, injectionPoints, instance)); new InstanceBindingImpl<T>(
injector, key, source, scopedFactory, injectionPoints, instance));
return true; return true;
} }
@ -94,31 +105,46 @@ final class BindingProcessor extends AbstractBindingProcessor {
} }
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints(); Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
Initializable<? extends javax.inject.Provider<? extends T>> initializable = Initializable<? extends javax.inject.Provider<? extends T>> initializable =
initializer.requestInjection(injector, provider, null, source, injectionPoints); initializer.<javax.inject.Provider<? extends T>>requestInjection(
injector, provider, null, source, injectionPoints);
// always visited with Binding<T> // always visited with Binding<T>
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
InternalFactory<T> factory = new InternalFactoryToInitializableAdapter<>( InternalFactory<T> factory =
initializable, source, new InternalFactoryToInitializableAdapter<T>(
initializable,
source,
injector.provisionListenerStore.get((ProviderInstanceBinding<T>) binding)); injector.provisionListenerStore.get((ProviderInstanceBinding<T>) binding));
InternalFactory<? extends T> scopedFactory = Scoping.scope(key, injector, factory, source, scoping); InternalFactory<? extends T> scopedFactory =
putBinding(new ProviderInstanceBindingImpl<>(injector, key, source, scopedFactory, scoping, Scoping.scope(key, injector, factory, source, scoping);
provider, injectionPoints)); putBinding(
new ProviderInstanceBindingImpl<T>(
injector, key, source, scopedFactory, scoping, provider, injectionPoints));
return true; return true;
} }
@Override @Override
public Boolean visit(ProviderKeyBinding<? extends T> binding) { public Boolean visit(ProviderKeyBinding<? extends T> binding) {
prepareBinding(); prepareBinding();
Key<? extends javax.inject.Provider<? extends T>> providerKey = binding.getProviderKey(); Key<? extends javax.inject.Provider<? extends T>> providerKey =
binding.getProviderKey();
// always visited with Binding<T> // always visited with Binding<T>
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
BoundProviderFactory<T> boundProviderFactory = new BoundProviderFactory<>( BoundProviderFactory<T> boundProviderFactory =
injector, providerKey, source, new BoundProviderFactory<T>(
injector,
providerKey,
source,
injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding)); injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding));
bindingData.addCreationListener(boundProviderFactory); processedBindingData.addCreationListener(boundProviderFactory);
InternalFactory<? extends T> scopedFactory = Scoping.scope( InternalFactory<? extends T> scopedFactory =
key, injector, boundProviderFactory, source, scoping); Scoping.scope(
putBinding(new LinkedProviderBindingImpl<>( key,
injector,
(InternalFactory<? extends T>) boundProviderFactory,
source,
scoping);
putBinding(
new LinkedProviderBindingImpl<T>(
injector, key, source, scopedFactory, scoping, providerKey)); injector, key, source, scopedFactory, scoping, providerKey));
return true; return true;
} }
@ -128,13 +154,16 @@ final class BindingProcessor extends AbstractBindingProcessor {
prepareBinding(); prepareBinding();
Key<? extends T> linkedKey = binding.getLinkedKey(); Key<? extends T> linkedKey = binding.getLinkedKey();
if (key.equals(linkedKey)) { if (key.equals(linkedKey)) {
errors.recursiveBinding(); // TODO: b/168656899 check for transitive recursive binding
errors.recursiveBinding(key, linkedKey);
} }
FactoryProxy<T> factory = new FactoryProxy<>(injector, key, linkedKey, source); FactoryProxy<T> factory = new FactoryProxy<>(injector, key, linkedKey, source);
bindingData.addCreationListener(factory); processedBindingData.addCreationListener(factory);
InternalFactory<? extends T> scopedFactory = InternalFactory<? extends T> scopedFactory =
Scoping.scope(key, injector, factory, source, scoping); Scoping.scope(key, injector, factory, source, scoping);
putBinding(new LinkedBindingImpl<>(injector, key, source, scopedFactory, scoping, linkedKey)); putBinding(
new LinkedBindingImpl<T>(injector, key, source, scopedFactory, scoping, linkedKey));
return true; return true;
} }
@ -142,7 +171,7 @@ final class BindingProcessor extends AbstractBindingProcessor {
private Boolean visitInternalProviderInstanceBindingFactory( private Boolean visitInternalProviderInstanceBindingFactory(
InternalProviderInstanceBindingImpl.Factory<T> provider) { InternalProviderInstanceBindingImpl.Factory<T> provider) {
InternalProviderInstanceBindingImpl<T> binding = InternalProviderInstanceBindingImpl<T> binding =
new InternalProviderInstanceBindingImpl<>( new InternalProviderInstanceBindingImpl<T>(
injector, injector,
key, key,
source, source,
@ -200,8 +229,13 @@ final class BindingProcessor extends AbstractBindingProcessor {
private <T> void bindExposed(PrivateElements privateElements, Key<T> key) { private <T> void bindExposed(PrivateElements privateElements, Key<T> key) {
ExposedKeyFactory<T> exposedKeyFactory = new ExposedKeyFactory<>(key, privateElements); ExposedKeyFactory<T> exposedKeyFactory = new ExposedKeyFactory<>(key, privateElements);
bindingData.addCreationListener(exposedKeyFactory); processedBindingData.addCreationListener(exposedKeyFactory);
putBinding(new ExposedBindingImpl<>( putBinding(
injector, privateElements.getExposedSource(key), key, exposedKeyFactory, privateElements)); new ExposedBindingImpl<T>(
injector,
privateElements.getExposedSource(key),
key,
exposedKeyFactory,
privateElements));
} }
} }

View file

@ -0,0 +1,85 @@
package com.google.inject.internal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.inject.Key;
import com.google.inject.spi.ErrorDetail;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* Error reported by Guice when a key is already bound in one or more child injectors or private
* modules.
*/
final class ChildBindingAlreadySetError extends InternalErrorDetail<ChildBindingAlreadySetError> {
private final Key<?> key;
private final ImmutableList<Object> existingSources;
ChildBindingAlreadySetError(Key<?> key, Iterable<Object> existingSoruces, List<Object> sources) {
super(
ErrorId.CHILD_BINDING_ALREADY_SET,
String.format(
"Unable to create binding for %s because it was already configured on one or more"
+ " child injectors or private modules.",
Messages.convert(key)),
sources,
null);
this.key = key;
// Can't use ImmutableList.toImmutableList here because of b/156759807.
this.existingSources =
ImmutableList.copyOf(
Streams.stream(existingSoruces)
.map(source -> source == null ? "" : source)
.collect(Collectors.toList()));
}
@Override
public boolean isMergeable(ErrorDetail<?> otherError) {
return otherError instanceof ChildBindingAlreadySetError
&& ((ChildBindingAlreadySetError) otherError).key.equals(this.key);
}
@Override
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
formatter.format("%n%s%n", Messages.bold("Bound at:"));
int index = 1;
for (Object source : existingSources) {
formatter.format("%-2s: ", index++);
if (source.equals("")) {
formatter.format("as a just-in-time binding%n");
} else {
new SourceFormatter(source, formatter, /* omitPreposition= */ true).format();
}
}
List<List<Object>> sourcesList = new ArrayList<>();
sourcesList.add(getSources());
mergeableErrors.forEach(error -> sourcesList.add(error.getSources()));
List<List<Object>> filteredSources =
sourcesList.stream()
.map(this::trimSource)
.filter(list -> !list.isEmpty())
.collect(Collectors.toList());
if (!filteredSources.isEmpty()) {
formatter.format("%n%s%n", Messages.bold("Requested by:"));
for (int i = 0; i < sourcesList.size(); i++) {
ErrorFormatter.formatSources(i + 1, sourcesList.get(i), formatter);
}
}
// TODO(b/151482394): Detect if the key was bound in any PrivateModule and suggest exposing the
// key in those cases.
}
@Override
public ChildBindingAlreadySetError withSources(List<Object> newSources) {
return new ChildBindingAlreadySetError(key, existingSources, newSources);
}
/** Omit the key itself in the source list since the information is redundant. */
private List<Object> trimSource(List<Object> sources) {
return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList());
}
}

View file

@ -1,11 +1,15 @@
package com.google.inject.internal; package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.internal.Annotations.findScopeAnnotation;
import static com.google.inject.internal.GuiceInternal.GUICE_INTERNAL;
import static com.google.inject.spi.Elements.withTrustedSource;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.ConfigurationException; import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Classes; import com.google.inject.internal.util.Classes;
@ -13,23 +17,19 @@ import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.ConstructorBinding; import com.google.inject.spi.ConstructorBinding;
import com.google.inject.spi.Dependency; import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.InjectionPoint;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Set; import java.util.Set;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.internal.Annotations.findScopeAnnotation;
final class ConstructorBindingImpl<T> extends BindingImpl<T> final class ConstructorBindingImpl<T> extends BindingImpl<T>
implements ConstructorBinding<T>, DelayedInitialize { implements ConstructorBinding<T>, DelayedInitialize {
private final Factory<T> factory; private final Factory<T> factory;
private final InjectionPoint constructorInjectionPoint; private final InjectionPoint constructorInjectionPoint;
private ConstructorBindingImpl(InjectorImpl injector, private ConstructorBindingImpl(
InjectorImpl injector,
Key<T> key, Key<T> key,
Object source, Object source,
InternalFactory<? extends T> scopedFactory, InternalFactory<? extends T> scopedFactory,
@ -41,35 +41,42 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
this.constructorInjectionPoint = constructorInjectionPoint; this.constructorInjectionPoint = constructorInjectionPoint;
} }
public ConstructorBindingImpl(Key<T> key, Object source, Scoping scoping, public ConstructorBindingImpl(
InjectionPoint constructorInjectionPoint, Set<InjectionPoint> injectionPoints) { Key<T> key,
Object source,
Scoping scoping,
InjectionPoint constructorInjectionPoint,
Set<InjectionPoint> injectionPoints) {
super(source, key, scoping); super(source, key, scoping);
this.factory = new Factory<T>(false, key); this.factory = new Factory<>(false, key);
ConstructionProxy<T> constructionProxy ConstructionProxy<T> constructionProxy =
= new DefaultConstructionProxyFactory<T>(constructorInjectionPoint).create(); new DefaultConstructionProxyFactory<T>(constructorInjectionPoint).create();
this.constructorInjectionPoint = constructorInjectionPoint; this.constructorInjectionPoint = constructorInjectionPoint;
factory.constructorInjector = new ConstructorInjector<T>( factory.constructorInjector =
injectionPoints, constructionProxy, null, null); new ConstructorInjector<T>(injectionPoints, constructionProxy, null, null);
} }
/** /**
* @param constructorInjector the constructor to use, or {@code null} to use the default. * @param constructorInjector the constructor to use, or {@code null} to use the default.
* @param failIfNotLinked true if this ConstructorBindingImpl's InternalFactory should * @param failIfNotLinked true if this ConstructorBindingImpl's InternalFactory should only
* only succeed if retrieved from a linked binding * succeed if retrieved from a linked binding
*/ */
static <T> ConstructorBindingImpl<T> create(InjectorImpl injector, Key<T> key, static <T> ConstructorBindingImpl<T> create(
InjectorImpl injector,
Key<T> key,
InjectionPoint constructorInjector, InjectionPoint constructorInjector,
Object source, Object source,
Scoping scoping, Scoping scoping,
Errors errors, Errors errors,
boolean failIfNotLinked, boolean failIfNotExplicit) boolean failIfNotLinked,
boolean atInjectRequired)
throws ErrorsException { throws ErrorsException {
int numErrors = errors.size(); int numErrors = errors.size();
@SuppressWarnings("unchecked") // constructorBinding guarantees type is consistent Class<?> rawType =
Class<? super T> rawType = constructorInjector == null constructorInjector == null
? key.getTypeLiteral().getRawType() ? key.getTypeLiteral().getRawType()
: (Class) constructorInjector.getDeclaringType().getRawType(); : constructorInjector.getDeclaringType().getRawType();
// We can't inject abstract classes. // We can't inject abstract classes.
if (Modifier.isAbstract(rawType.getModifiers())) { if (Modifier.isAbstract(rawType.getModifiers())) {
@ -86,10 +93,8 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
// Find a constructor annotated @Inject // Find a constructor annotated @Inject
if (constructorInjector == null) { if (constructorInjector == null) {
try { try {
constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral()); constructorInjector =
if (failIfNotExplicit && !hasAtInject((Constructor) constructorInjector.getMember())) { InjectionPoint.forConstructorOf(key.getTypeLiteral(), atInjectRequired);
errors.atInjectRequired(rawType);
}
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
throw errors.merge(e.getErrorMessages()).toException(); throw errors.merge(e.getErrorMessages()).toException();
} }
@ -100,47 +105,36 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
Class<?> annotatedType = constructorInjector.getMember().getDeclaringClass(); Class<?> annotatedType = constructorInjector.getMember().getDeclaringClass();
Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, annotatedType); Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, annotatedType);
if (scopeAnnotation != null) { if (scopeAnnotation != null) {
scoping = Scoping.makeInjectable(Scoping.forAnnotation(scopeAnnotation), scoping =
injector, errors.withSource(rawType)); Scoping.makeInjectable(
Scoping.forAnnotation(scopeAnnotation), injector, errors.withSource(rawType));
} }
} }
errors.throwIfNewErrors(numErrors); errors.throwIfNewErrors(numErrors);
Factory<T> factoryFactory = new Factory<T>(failIfNotLinked, key); Factory<T> factoryFactory = new Factory<>(failIfNotLinked, key);
InternalFactory<? extends T> scopedFactory InternalFactory<? extends T> scopedFactory =
= Scoping.scope(key, injector, factoryFactory, source, scoping); Scoping.scope(key, injector, factoryFactory, source, scoping);
return new ConstructorBindingImpl<T>( return new ConstructorBindingImpl<T>(
injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector); injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector);
} }
/** @Override
* Returns true if the inject annotation is on the constructor.
*/
private static boolean hasAtInject(Constructor<?> cxtor) {
return cxtor.isAnnotationPresent(Inject.class)
|| cxtor.isAnnotationPresent(javax.inject.Inject.class);
}
@SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type @SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type
public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
factory.constructorInjector = factory.constructorInjector =
(ConstructorInjector<T>) injector.constructors.get(constructorInjectionPoint, errors); (ConstructorInjector<T>) injector.constructors.get(constructorInjectionPoint, errors);
factory.provisionCallback = factory.provisionCallback = injector.provisionListenerStore.get(this);
injector.provisionListenerStore.get(this);
} }
/** /** True if this binding has been initialized and is ready for use. */
* True if this binding has been initialized and is ready for use.
*/
boolean isInitialized() { boolean isInitialized() {
return factory.constructorInjector != null; return factory.constructorInjector != null;
} }
/** /** Returns an injection point that can be used to clean up the constructor store. */
* Returns an injection point that can be used to clean up the constructor store.
*/
InjectionPoint getInternalConstructor() { InjectionPoint getInternalConstructor() {
if (factory.constructorInjector != null) { if (factory.constructorInjector != null) {
return factory.constructorInjector.getConstructionProxy().getInjectionPoint(); return factory.constructorInjector.getConstructionProxy().getInjectionPoint();
@ -149,22 +143,21 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
} }
} }
/** /** Returns a set of dependencies that can be iterated over to clean up stray JIT bindings. */
* Returns a set of dependencies that can be iterated over to clean up stray JIT bindings.
*/
Set<Dependency<?>> getInternalDependencies() { Set<Dependency<?>> getInternalDependencies() {
ImmutableSet.Builder<InjectionPoint> builder = ImmutableSet.builder(); ImmutableSet.Builder<InjectionPoint> builder = ImmutableSet.builder();
if (factory.constructorInjector == null) { if (factory.constructorInjector == null) {
builder.add(constructorInjectionPoint); builder.add(constructorInjectionPoint);
// If the below throws, it's OK -- we just ignore those dependencies, because no one
// could have used them anyway.
try { try {
builder.addAll(InjectionPoint.forInstanceMethodsAndFields(constructorInjectionPoint.getDeclaringType())); builder.addAll(
InjectionPoint.forInstanceMethodsAndFields(
constructorInjectionPoint.getDeclaringType()));
} catch (ConfigurationException ignored) { } catch (ConfigurationException ignored) {
// This is OK -- we just ignore those dependencies, because no one could have used them
// anyway.
} }
} else { } else {
builder.add(getConstructor()) builder.add(getConstructor()).addAll(getInjectableMembers());
.addAll(getInjectableMembers());
} }
return Dependency.forInjectionPoints(builder.build()); return Dependency.forInjectionPoints(builder.build());
@ -190,7 +183,8 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
@Override @Override
public Set<Dependency<?>> getDependencies() { public Set<Dependency<?>> getDependencies() {
return Dependency.forInjectionPoints(new ImmutableSet.Builder<InjectionPoint>() return Dependency.forInjectionPoints(
new ImmutableSet.Builder<InjectionPoint>()
.add(getConstructor()) .add(getConstructor())
.addAll(getInjectableMembers()) .addAll(getInjectableMembers())
.build()); .build());
@ -208,11 +202,17 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
null, key, getSource(), factory, getScoping(), factory, constructorInjectionPoint); null, key, getSource(), factory, getScoping(), factory, constructorInjectionPoint);
} }
@Override
@SuppressWarnings("unchecked") // the raw constructor member and declaring type always agree @SuppressWarnings("unchecked") // the raw constructor member and declaring type always agree
public void applyTo(Binder binder) { public void applyTo(Binder binder) {
InjectionPoint constructor = getConstructor(); InjectionPoint constructor = getConstructor();
getScoping().applyTo(binder.withSource(getSource()).bind(getKey()).toConstructor( getScoping()
(Constructor) getConstructor().getMember(), (TypeLiteral) constructor.getDeclaringType())); .applyTo(
withTrustedSource(GUICE_INTERNAL, binder, getSource())
.bind(getKey())
.toConstructor(
(Constructor) getConstructor().getMember(),
(TypeLiteral) constructor.getDeclaringType()));
} }
@Override @Override
@ -252,6 +252,7 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
this.key = key; this.key = key;
} }
@Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public T get(InternalContext context, Dependency<?> dependency, boolean linked) public T get(InternalContext context, Dependency<?> dependency, boolean linked)
throws InternalProvisionException { throws InternalProvisionException {
@ -259,6 +260,7 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
if (localInjector == null) { if (localInjector == null) {
throw new IllegalStateException("Constructor not ready"); throw new IllegalStateException("Constructor not ready");
} }
if (!linked && failIfNotLinked) { if (!linked && failIfNotLinked) {
throw InternalProvisionException.jitDisabled(key); throw InternalProvisionException.jitDisabled(key);
} }

View file

@ -0,0 +1,127 @@
package com.google.inject.internal;
import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ErrorDetail;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Error reported by Guice when duplicate elements are found in a {@link Multibinder} that does not
* permit duplicates.
*/
final class DuplicateElementError<T> extends InternalErrorDetail<DuplicateElementError<T>> {
private final Key<Set<T>> setKey;
private final ImmutableMultimap<T, Element<T>> elements;
DuplicateElementError(
Key<Set<T>> setKey, List<Binding<T>> bindings, T[] values, List<Object> sources) {
this(setKey, indexElements(bindings, values), sources);
}
private DuplicateElementError(
Key<Set<T>> setKey, ImmutableMultimap<T, Element<T>> elements, List<Object> sources) {
super(
ErrorId.DUPLICATE_ELEMENT,
String.format("Duplicate elements found in Multibinder %s.", Messages.convert(setKey)),
sources,
null);
this.setKey = setKey;
this.elements = elements;
}
@Override
protected void formatDetail(List<ErrorDetail<?>> others, Formatter formatter) {
formatter.format("%n%s%n", Messages.bold("Duplicates:"));
int duplicateIndex = 1;
for (Map.Entry<T, Collection<Element<T>>> entry : elements.asMap().entrySet()) {
formatter.format("%-2s: ", duplicateIndex++);
if (entry.getValue().size() > 1) {
Set<String> valuesAsString =
entry.getValue().stream()
.map(element -> element.value.toString())
.collect(Collectors.toSet());
if (valuesAsString.size() == 1) {
// String representation of the duplicates elements are the same, so only print out one.
formatter.format("Element: %s%n", Messages.redBold(valuesAsString.iterator().next()));
formatter.format(" Bound at:%n");
int index = 1;
for (Element<T> element : entry.getValue()) {
formatter.format(" %-2s: ", index++);
formatElement(element, formatter);
}
} else {
// Print out all elements as string when there are different string representations of the
// elements. To keep the logic simple, same strings are not grouped together unless all
// elements have the same string represnetation. This means some strings may be printed
// out multiple times.
// There is no indentation for the first duplicate element.
boolean indent = false;
for (Element<T> element : entry.getValue()) {
if (indent) {
formatter.format(" ");
} else {
indent = true;
}
formatter.format("Element: %s%n", Messages.redBold(element.value.toString()));
formatter.format(" Bound at: ");
formatElement(element, formatter);
}
}
}
}
formatter.format("%n%s%n", Messages.bold("Multibinder declared at:"));
// Multibinder source includes the key of the set. Filter it out since it's not useful in the
// printed error stack.
List<Object> filteredSource =
getSources().stream()
.filter(
source -> {
if (source instanceof Dependency) {
return !((Dependency<?>) source).getKey().equals(setKey);
}
return true;
})
.collect(Collectors.toList());
ErrorFormatter.formatSources(filteredSource, formatter);
}
private void formatElement(Element<T> element, Formatter formatter) {
Object source = element.binding.getSource();
new SourceFormatter(
source,
formatter,
/** omitPreposition= */
true)
.format();
}
@Override
public DuplicateElementError<T> withSources(List<Object> newSources) {
return new DuplicateElementError<>(setKey, elements, newSources);
}
static <T> ImmutableMultimap<T, Element<T>> indexElements(List<Binding<T>> bindings, T[] values) {
ImmutableMultimap.Builder<T, Element<T>> map = ImmutableMultimap.builder();
for (int i = 0; i < values.length; i++) {
map.put(values[i], new Element<T>(values[i], bindings.get(i)));
}
return map.build();
}
static class Element<T> {
T value;
Binding<T> binding;
Element(T value, Binding<T> binding) {
this.value = value;
this.binding = binding;
}
}
}

View file

@ -0,0 +1,70 @@
package com.google.inject.internal;
import com.google.common.collect.Multimap;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.spi.ErrorDetail;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Error reported by Guice when a duplicate key is found in a {@link MapBinder} that does not permit
* duplicates.
*/
final class DuplicateMapKeyError<K, V> extends InternalErrorDetail<DuplicateMapKeyError<K, V>> {
private final Key<Map<K, V>> mapKey;
private final Multimap<K, Binding<V>> duplicates;
DuplicateMapKeyError(
Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates, List<Object> sources) {
super(ErrorId.DUPLICATE_MAP_KEY, getDuplicateKeysMessage(mapKey, duplicates), sources, null);
this.mapKey = mapKey;
this.duplicates = duplicates;
}
@Override
protected final void formatDetail(List<ErrorDetail<?>> others, Formatter formatter) {
formatter.format("%n%s%n", Messages.bold("Duplicates:"));
for (Map.Entry<K, Collection<Binding<V>>> entry : duplicates.asMap().entrySet()) {
formatter.format(" Key: \"%s\"%n", Messages.redBold(entry.getKey().toString()));
formatter.format(" Bound at:%n");
int index = 1;
for (Binding<V> binding : entry.getValue()) {
formatter.format(" %-2s: ", index++);
new SourceFormatter(
binding.getSource(),
formatter,
/** omitPreposition= */
true)
.format();
}
formatter.format("%n");
}
formatter.format("%s%n", Messages.bold("MapBinder declared at:"));
ErrorFormatter.formatSources(getSources(), formatter);
}
@Override
public DuplicateMapKeyError<K, V> withSources(List<Object> newSources) {
return new DuplicateMapKeyError<>(mapKey, duplicates, newSources);
}
private static <K, V> String getDuplicateKeysMessage(
Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates) {
Set<K> duplicateKeys = duplicates.keySet();
String mapBinderKey = Messages.convert(mapKey).toString();
String firstDuplicateKey = duplicateKeys.iterator().next().toString();
if (duplicateKeys.size() == 1) {
return String.format("Duplicate key \"%s\" found in %s.", firstDuplicateKey, mapBinderKey);
} else {
return String.format(
"\"%s\" and %s other duplicate keys found in %s.",
firstDuplicateKey, duplicateKeys.size() - 1, mapBinderKey);
}
}
}

View file

@ -0,0 +1,33 @@
package com.google.inject.internal;
import java.util.Formatter;
import java.util.List;
/** Helper for formatting Guice errors. */
final class ErrorFormatter {
private ErrorFormatter() {}
/**
* Format a list of sources to the given {@code formatter}, prefixed by the give {@code index}.
*/
static void formatSources(int index, List<Object> sources, Formatter formatter) {
for (int i = 0; i < sources.size(); i++) {
Object source = sources.get(i);
if (i == 0) {
formatter.format("%-3s: ", index);
} else {
formatter.format(SourceFormatter.INDENT);
}
new SourceFormatter(source, formatter, i == 0).format();
}
}
/** Format a list of sources to the given {@code formatter}. */
static void formatSources(List<Object> sources, Formatter formatter) {
for (int i = 0; i < sources.size(); i++) {
Object source = sources.get(i);
formatter.format(" ");
new SourceFormatter(source, formatter, i == 0).format();
}
}
}

View file

@ -0,0 +1,65 @@
package com.google.inject.internal;
/** Enum used to identify a specific Guice error. */
public enum ErrorId {
AMBIGUOUS_TYPE_CONVERSION,
AOP_DISABLED,
AT_INJECT_REQUIRED,
AT_TARGET_IS_MISSING_PARAMETER,
BINDING_ALREADY_SET,
BINDING_TO_GUICE_TYPE,
BINDING_TO_PROVIDER,
CAN_NOT_PROXY_CLASS,
CHILD_BINDING_ALREADY_SET,
CIRCULAR_PROXY_DISABLED,
CONSTRUCTOR_NOT_DEFINED_BY_TYPE,
CONVERSION_TYPE_ERROR,
CONVERTER_RETURNED_NULL,
DUPLICATE_BINDING_ANNOTATIONS,
DUPLICATE_ELEMENT,
DUPLICATE_MAP_KEY,
DUPLICATE_SCOPES,
DUPLICATE_SCOPE_ANNOTATIONS,
ERROR_ENHANCING_CLASS,
ERROR_INJECTING_CONSTRUCTOR,
ERROR_INJECTING_METHOD,
ERROR_IN_CUSTOM_PROVIDER,
ERROR_IN_USER_CODE,
ERROR_IN_USER_INJECTOR,
ERROR_NOTIFYING_TYPE_LISTENER,
EXPOSED_BUT_NOT_BOUND,
INJECT_ABSTRACT_METHOD,
INJECT_FINAL_FIELD,
INJECT_INNER_CLASS,
INJECT_METHOD_WITH_TYPE_PARAMETER,
INJECT_RAW_MEMBERS_INJECTOR,
INJECT_RAW_PROVIDER,
INJECT_RAW_TYPE_LITERAL,
JIT_BINDING_ALREADY_SET,
JIT_DISABLED,
JIT_DISABLED_IN_PARENT,
KEY_NOT_FULLY_SPECIFIED,
MISPLACED_BINDING_ANNOTATION,
MISSING_CONSTANT_VALUES,
MISSING_CONSTRUCTOR,
MISSING_IMPLEMENTATION,
MISSING_RUNTIME_RETENTION,
MISSING_SCOPE_ANNOTATION,
NOT_A_SUBTYPE,
NULL_ELEMENT_IN_SET,
NULL_INJECTED_INTO_NON_NULLABLE,
NULL_VALUE_IN_MAP,
OPTIONAL_CONSTRUCTOR,
RECURSIVE_BINDING,
RECURSIVE_IMPLEMENTATION_TYPE,
RECURSIVE_PROVIDER_TYPE,
SCOPE_ANNOTATION_ON_ABSTRACT_TYPE,
SCOPE_NOT_FOUND,
STATIC_INJECTION_ON_INTERFACE,
SUBTYPE_NOT_PROVIDED,
TOO_MANY_CONSTRUCTORS,
VOID_PROVIDER_METHOD,
// All other uncommon type of errors
OTHER;
}

View file

@ -1,10 +1,11 @@
package com.google.inject.internal; package com.google.inject.internal;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.primitives.Primitives;
import com.google.inject.Binding; import com.google.inject.Binding;
import com.google.inject.ConfigurationException; import com.google.inject.ConfigurationException;
import com.google.inject.CreationException; import com.google.inject.CreationException;
@ -14,18 +15,18 @@ import com.google.inject.ProvisionException;
import com.google.inject.Scope; import com.google.inject.Scope;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.SourceProvider; import com.google.inject.internal.util.SourceProvider;
import com.google.inject.spi.ElementSource;
import com.google.inject.spi.Message; import com.google.inject.spi.Message;
import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.ScopeBinding;
import com.google.inject.spi.TypeConverterBinding; import com.google.inject.spi.TypeConverterBinding;
import com.google.inject.spi.TypeListenerBinding; import com.google.inject.spi.TypeListenerBinding;
import java.io.Serializable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Formatter; import java.util.Formatter;
import java.util.List; import java.util.List;
@ -45,36 +46,35 @@ import java.util.Set;
* identify that method. When calling a method that's defined in a different context, call that * 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. * method with an errors object that includes its context.
* *
* @author jessewilson@google.com (Jesse Wilson)
*/ */
public final class Errors { public final class Errors implements Serializable {
/** 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;
/**
* Throws a ConfigurationException with an NullPointerExceptions as the cause if the given
* reference is {@code null}.
*/
static <T> T checkNotNull(T reference, String name) { static <T> T checkNotNull(T reference, String name) {
if (reference != null) { if (reference != null) {
return reference; return reference;
} }
NullPointerException npe = new NullPointerException(name); NullPointerException npe = new NullPointerException(name);
throw new ConfigurationException(ImmutableSet.of(new Message(npe.toString(), npe))); 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) { static void checkConfiguration(boolean condition, String format, Object... args) {
if (condition) { if (condition) {
return; return;
} }
throw new ConfigurationException(ImmutableSet.of(new Message(Messages.format(format, args))));
}
private static final ImmutableSet<Class<?>> COMMON_AMBIGUOUS_TYPES = throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
ImmutableSet.<Class<?>>builder() }
.add(Object.class)
.add(String.class)
.addAll(Primitives.allWrapperTypes())
.build();
/** The root errors object. Used to access the list of error messages. */ /** The root errors object. Used to access the list of error messages. */
private final Errors root; private final Errors root;
@ -82,13 +82,10 @@ public final class Errors {
/** The parent errors object. Used to obtain the chain of source objects. */ /** The parent errors object. Used to obtain the chain of source objects. */
private final Errors parent; private final Errors parent;
/** /** The leaf source for errors added here. */
* The leaf source for errors added here.
*/
private final Object source; private final Object source;
/**
* null unless (root == this) and error messages exist. Never an empty list. /** null unless (root == this) and error messages exist. Never an empty list. */
*/
private List<Message> errors; // lazy, use getErrorsForAdd() private List<Message> errors; // lazy, use getErrorsForAdd()
public Errors() { public Errors() {
@ -116,305 +113,380 @@ public final class Errors {
: new Errors(this, source); : new Errors(this, source);
} }
/**
* We use a fairly generic error message here. The motivation is to share the same message for
* both bind time errors:
*
* <pre><code>Guice.createInjector(new AbstractModule() {
* public void configure() {
* bind(Runnable.class);
* }
* }</code></pre>
*
* ...and at provide-time errors:
*
* <pre><code>Guice.createInjector().getInstance(Runnable.class);</code></pre>
*
* 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) { public Errors missingImplementation(Key<?> key) {
return addMessage("No implementation for %s was bound.", key); return addMessage(ErrorId.MISSING_IMPLEMENTATION, "No implementation for %s was bound.", key);
} }
/** Within guice's core, allow for better missing binding messages */ /** Within guice's core, allow for better missing binding messages */
<T> Errors missingImplementationWithHint(Key<T> key, Injector injector) { <T> Errors missingImplementationWithHint(Key<T> key, Injector injector) {
StringBuilder sb = new StringBuilder(); MissingImplementationError<T> error =
new MissingImplementationError<T>(key, injector, getSources());
sb.append(Messages.format("No implementation for %s was bound.", key)); return addMessage(
new Message(GuiceInternal.GUICE_INTERNAL, ErrorId.MISSING_IMPLEMENTATION, error));
// Keys which have similar strings as the desired key
List<String> possibleMatches = new ArrayList<>();
// Check for other keys that may have the same type,
// but not the same annotation
TypeLiteral<T> type = key.getTypeLiteral();
List<Binding<T>> 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<Key<?>, 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) { public Errors jitDisabled(Key<?> key) {
return addMessage("Explicit bindings are required and %s is not explicitly bound.", key); return addMessage(
ErrorId.JIT_DISABLED,
"Explicit bindings are required and %s is not explicitly bound.",
key);
} }
public Errors jitDisabledInParent(Key<?> key) { public Errors jitDisabledInParent(Key<?> key) {
return addMessage("Explicit bindings are required and %s would be bound in a parent injector.%n" 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.", + "Please add an explicit binding for it, either in the child or the parent.",
key); key);
} }
public Errors atInjectRequired(Class<?> clazz) { public Errors atInjectRequired(TypeLiteral<?> type) {
return addMessage("Explicit @Inject annotations are required on constructors," return addMessage(
+ " but %s has no constructors annotated with @Inject.", new Message(
clazz); GuiceInternal.GUICE_INTERNAL,
ErrorId.MISSING_CONSTRUCTOR,
new MissingConstructorError(type, /* atInjectRequired= */ true, getSources())));
} }
public Errors converterReturnedNull(String stringValue, Object source, public Errors converterReturnedNull(
TypeLiteral<?> type, TypeConverterBinding typeConverterBinding) { String stringValue,
return addMessage("Received null converting '%s' (bound at %s) to %s%n" Object source,
+ " using %s.", TypeLiteral<?> type,
stringValue, Messages.convert(source), type, typeConverterBinding); 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, public Errors conversionTypeError(
TypeConverterBinding typeConverterBinding, Object converted) { String stringValue,
return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n" 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" + " using %s.%n"
+ " Converter returned %s.", + " Converter returned %s.",
stringValue, Messages.convert(source), type, typeConverterBinding, converted); stringValue,
convert(source),
type,
typeConverterBinding,
converted);
} }
public Errors conversionError(String stringValue, Object source, public Errors conversionError(
TypeLiteral<?> type, TypeConverterBinding typeConverterBinding, RuntimeException cause) { String stringValue,
return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n" Object source,
+ " using %s.%n" TypeLiteral<?> type,
+ " Reason: %s", TypeConverterBinding typeConverterBinding,
stringValue, Messages.convert(source), type, typeConverterBinding, cause); 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, public Errors ambiguousTypeConversion(
TypeConverterBinding a, TypeConverterBinding b) { String stringValue,
return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n" 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 and%n"
+ " %s.%n" + " %s.%n"
+ " Please adjust your type converter configuration to avoid overlapping matches.", + " Please adjust your type converter configuration to avoid overlapping matches.",
stringValue, Messages.convert(source), type, a, b); stringValue,
convert(source),
type,
a,
b);
} }
public Errors bindingToProvider() { public Errors bindingToProvider() {
return addMessage("Binding to Provider is not allowed."); return addMessage(ErrorId.BINDING_TO_PROVIDER, "Binding to Provider is not allowed.");
} }
public Errors notASubtype(Class<?> implementationType, Class<?> type) { public Errors notASubtype(Class<?> implementationType, Class<?> type) {
return addMessage("%s doesn't extend %s.", implementationType, type); return addMessage(ErrorId.NOT_A_SUBTYPE, "%s doesn't extend %s.", implementationType, type);
} }
public Errors recursiveImplementationType() { public Errors recursiveImplementationType() {
return addMessage("@ImplementedBy points to the same class it annotates."); return addMessage(
ErrorId.RECURSIVE_IMPLEMENTATION_TYPE,
"@ImplementedBy points to the same class it annotates.");
} }
public Errors recursiveProviderType() { public Errors recursiveProviderType() {
return addMessage("@ProvidedBy points to the same class it annotates."); return addMessage(
ErrorId.RECURSIVE_PROVIDER_TYPE, "@ProvidedBy points to the same class it annotates.");
} }
public Errors missingRuntimeRetention(Class<? extends Annotation> annotation) { public Errors missingRuntimeRetention(Class<? extends Annotation> annotation) {
return addMessage(Messages.format("Please annotate %s with @Retention(RUNTIME).", annotation)); return addMessage(
ErrorId.MISSING_RUNTIME_RETENTION,
format("Please annotate %s with @Retention(RUNTIME).", annotation));
} }
public Errors missingScopeAnnotation(Class<? extends Annotation> annotation) { public Errors missingScopeAnnotation(Class<? extends Annotation> annotation) {
return addMessage(Messages.format("Please annotate %s with @ScopeAnnotation.", annotation)); return addMessage(
ErrorId.MISSING_SCOPE_ANNOTATION,
format("Please annotate %s with @ScopeAnnotation.", annotation));
} }
public Errors optionalConstructor(Constructor<?> constructor) { public Errors optionalConstructor(Constructor<?> constructor) {
return addMessage("%s is annotated @Inject(optional=true), " return addMessage(
+ "but constructors cannot be optional.", constructor); ErrorId.OPTIONAL_CONSTRUCTOR,
"%s is annotated @Inject(optional=true), but constructors cannot be optional.",
constructor);
} }
public Errors cannotBindToGuiceType(String simpleName) { public Errors cannotBindToGuiceType(String simpleName) {
return addMessage("Binding to core guice framework type is not allowed: %s.", simpleName); return addMessage(
ErrorId.BINDING_TO_GUICE_TYPE,
"Binding to core guice framework type is not allowed: %s.",
simpleName);
} }
public Errors scopeNotFound(Class<? extends Annotation> scopeAnnotation) { public Errors scopeNotFound(Class<? extends Annotation> scopeAnnotation) {
return addMessage("No scope is bound to %s.", scopeAnnotation); return addMessage(
new Message(
GuiceInternal.GUICE_INTERNAL,
ErrorId.SCOPE_NOT_FOUND,
new ScopeNotFoundError(scopeAnnotation, getSources())));
} }
public Errors scopeAnnotationOnAbstractType( public Errors scopeAnnotationOnAbstractType(
Class<? extends Annotation> scopeAnnotation, Class<?> type, Object source) { Class<? extends Annotation> scopeAnnotation, Class<?> type, Object source) {
return addMessage("%s is annotated with %s, but scope annotations are not supported " return addMessage(
+ "for abstract types.%n Bound at %s.", type, scopeAnnotation, Messages.convert(source)); 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) { public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
return addMessage("%s is annotated with %s, but binding annotations should be applied " return addMessage(
+ "to its parameters instead.", member, bindingAnnotation); 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 = private static final String CONSTRUCTOR_RULES =
"Injectable classes must have either one (and only one) constructor annotated with @Inject" "Injectable classes must have either one (and only one) constructor annotated with @Inject"
+ " or a zero-argument constructor that is not private."; + " or a zero-argument constructor that is not private.";
public Errors missingConstructor(TypeLiteral<?> type) { 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( return addMessage(
"No implementation for %s (with no qualifier annotation) was bound, and could not find an" new Message(
+ " injectable constructor%s. %s", GuiceInternal.GUICE_INTERNAL,
typeString, ErrorId.MISSING_CONSTRUCTOR,
typeString.equals(rawTypeString) ? "" : " in " + rawTypeString, new MissingConstructorError(type, /* atInjectRequired= */ false, getSources())));
CONSTRUCTOR_RULES);
} }
public Errors tooManyConstructors(Class<?> implementation) { public Errors tooManyConstructors(Class<?> implementation) {
return addMessage( return addMessage(
ErrorId.TOO_MANY_CONSTRUCTORS,
"%s has more than one constructor annotated with @Inject. %s", "%s has more than one constructor annotated with @Inject. %s",
implementation, CONSTRUCTOR_RULES); implementation,
CONSTRUCTOR_RULES);
} }
public Errors constructorNotDefinedByType(Constructor<?> constructor, TypeLiteral<?> type) { public Errors constructorNotDefinedByType(Constructor<?> constructor, TypeLiteral<?> type) {
return addMessage("%s does not define %s", type, constructor); return addMessage(
ErrorId.CONSTRUCTOR_NOT_DEFINED_BY_TYPE, "%s does not define %s", type, constructor);
} }
public Errors duplicateScopes(ScopeBinding existing, public <K, V> Errors duplicateMapKey(Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates) {
Class<? extends Annotation> annotationType, Scope scope) { return addMessage(
return addMessage("Scope %s is already bound to %s at %s.%n Cannot bind %s.", new Message(
existing.getScope(), annotationType, existing.getSource(), scope); GuiceInternal.GUICE_INTERNAL,
ErrorId.DUPLICATE_MAP_KEY,
new DuplicateMapKeyError<K, V>(mapKey, duplicates, getSources())));
}
public Errors duplicateScopes(
ScopeBinding existing, Class<? extends Annotation> 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() { public Errors voidProviderMethod() {
return addMessage("Provider methods must return a value. Do not return void."); return addMessage(
ErrorId.VOID_PROVIDER_METHOD, "Provider methods must return a value. Do not return void.");
} }
public Errors missingConstantValues() { public Errors missingConstantValues() {
return addMessage("Missing constant value. Please call to(...)."); return addMessage(
ErrorId.MISSING_CONSTANT_VALUES, "Missing constant value. Please call to(...).");
} }
public Errors cannotInjectInnerClass(Class<?> type) { public Errors cannotInjectInnerClass(Class<?> type) {
return addMessage("Injecting into inner classes is not supported. " return addMessage(
+ "Please use a 'static' class (top-level or nested) instead of %s.", type); 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 duplicateBindingAnnotations(Member member, public Errors duplicateBindingAnnotations(
Class<? extends Annotation> a, Class<? extends Annotation> b) { Member member, Class<? extends Annotation> a, Class<? extends Annotation> b) {
return addMessage("%s has more than one annotation annotated with @BindingAnnotation: " return addMessage(
+ "%s and %s", member, a, b); ErrorId.DUPLICATE_BINDING_ANNOTATIONS,
"%s has more than one annotation annotated with @BindingAnnotation: %s and %s",
member,
a,
b);
} }
public Errors staticInjectionOnInterface(Class<?> clazz) { public Errors staticInjectionOnInterface(Class<?> clazz) {
return addMessage("%s is an interface, but interfaces have no static injection points.", 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) { public Errors cannotInjectFinalField(Field field) {
return addMessage("Injected field %s cannot be final.", 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) { public Errors cannotInjectAbstractMethod(Method method) {
return addMessage("Injected method %s cannot be abstract.", method); return addMessage(
} ErrorId.INJECT_ABSTRACT_METHOD, "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) { public Errors cannotInjectMethodWithTypeParameters(Method method) {
return addMessage("Injected method %s cannot declare type parameters of its own.", method); return addMessage(
ErrorId.INJECT_METHOD_WITH_TYPE_PARAMETER,
"Injected method %s cannot declare type parameters of its own.",
method);
} }
public Errors duplicateScopeAnnotations( public Errors duplicateScopeAnnotations(
Class<? extends Annotation> a, Class<? extends Annotation> b) { Class<? extends Annotation> a, Class<? extends Annotation> b) {
return addMessage("More than one scope annotation was found: %s and %s.", a, b); return addMessage(
ErrorId.DUPLICATE_SCOPE_ANNOTATIONS,
"More than one scope annotation was found: %s and %s.",
a,
b);
} }
public Errors recursiveBinding() { public Errors recursiveBinding(Key<?> key, Key<?> linkedKey) {
return addMessage("Binding points to itself."); 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) { public Errors bindingAlreadySet(Key<?> key, Object source) {
return addMessage("A binding to %s was already configured at %s.", key, Messages.convert(source)); return addMessage(
ErrorId.BINDING_ALREADY_SET,
"A binding to %s was already configured at %s.",
key,
convert(source));
} }
public Errors jitBindingAlreadySet(Key<?> key) { public Errors jitBindingAlreadySet(Key<?> key) {
return addMessage("A just-in-time binding to %s was already configured on a parent injector.", 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<Object> sources) { public Errors childBindingAlreadySet(Key<?> key, Set<Object> sources) {
Formatter allSources = new Formatter(); Message message =
for (Object source : sources) { new Message(
if (source == null) { GuiceInternal.GUICE_INTERNAL,
allSources.format("%n (bound by a just-in-time binding)"); ErrorId.CHILD_BINDING_ALREADY_SET,
} else { new ChildBindingAlreadySetError(key, sources, getSources()));
allSources.format("%n bound at %s", source); return addMessage(message);
}
}
return addMessage(
"Unable to create binding for %s."
+ " It was already configured on one or more child injectors or private modules"
+ "%s%n"
+ " If it was in a PrivateModule, did you forget to expose the binding?",
key, allSources.out());
} }
public Errors errorCheckingDuplicateBinding(Key<?> key, Object source, Throwable t) { public Errors errorCheckingDuplicateBinding(Key<?> key, Object source, Throwable t) {
return addMessage( return addMessage(
ErrorId.OTHER,
"A binding to %s was already configured at %s and an error was thrown " "A binding to %s was already configured at %s and an error was thrown "
+ "while checking duplicate bindings. Error: %s", + "while checking duplicate bindings. Error: %s",
key, Messages.convert(source), t); key,
convert(source),
t);
} }
public Errors errorNotifyingTypeListener(TypeListenerBinding listener, public Errors errorNotifyingTypeListener(
TypeLiteral<?> type, Throwable cause) { TypeListenerBinding listener, TypeLiteral<?> type, Throwable cause) {
return errorInUserCode(cause, return errorInUserCode(
"Error notifying TypeListener %s (bound at %s) of %s.%n" cause,
+ " Reason: %s", "Error notifying TypeListener %s (bound at %s) of %s.%n Reason: %s",
listener.getListener(), Messages.convert(listener.getSource()), type, cause); listener.getListener(),
convert(listener.getSource()),
type,
cause);
} }
public Errors exposedButNotBound(Key<?> key) { public Errors exposedButNotBound(Key<?> key) {
return addMessage("Could not expose() %s, it must be explicitly bound.", key); return addMessage(
ErrorId.EXPOSED_BUT_NOT_BOUND, "Could not expose() %s, it must be explicitly bound.", key);
} }
public Errors keyNotFullySpecified(TypeLiteral<?> typeLiteral) { public Errors keyNotFullySpecified(TypeLiteral<?> typeLiteral) {
return addMessage("%s cannot be used as a key; It is not fully specified.", 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) { public Errors errorEnhancingClass(Class<?> clazz, Throwable cause) {
@ -439,39 +511,55 @@ public final class Errors {
if (!messages.isEmpty()) { if (!messages.isEmpty()) {
return merge(messages); return merge(messages);
} else { } else {
return addMessage(cause, messageFormat, arguments); return addMessage(ErrorId.ERROR_IN_USER_CODE, cause, messageFormat, arguments);
} }
} }
public Errors cannotInjectRawProvider() { public Errors cannotInjectRawProvider() {
return addMessage("Cannot inject a Provider that has no type parameter"); return addMessage(
ErrorId.INJECT_RAW_PROVIDER, "Cannot inject a Provider that has no type parameter");
} }
public Errors cannotInjectRawMembersInjector() { public Errors cannotInjectRawMembersInjector() {
return addMessage("Cannot inject a MembersInjector that has no type parameter"); return addMessage(
ErrorId.INJECT_RAW_MEMBERS_INJECTOR,
"Cannot inject a MembersInjector that has no type parameter");
} }
public Errors cannotInjectTypeLiteralOf(Type unsupportedType) { public Errors cannotInjectTypeLiteralOf(Type unsupportedType) {
return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType); return addMessage(ErrorId.OTHER, "Cannot inject a TypeLiteral of %s", unsupportedType);
} }
public Errors cannotInjectRawTypeLiteral() { public Errors cannotInjectRawTypeLiteral() {
return addMessage("Cannot inject a TypeLiteral that has no type parameter"); return addMessage(
ErrorId.INJECT_RAW_TYPE_LITERAL, "Cannot inject a TypeLiteral that has no type parameter");
} }
public void throwCreationExceptionIfErrorsExist() { public void throwCreationExceptionIfErrorsExist() {
if (!hasErrors()) { if (!hasErrors()) {
return; return;
} }
throw new CreationException(getMessages());
CreationException exception = new CreationException(getMessages());
throw exception;
} }
public void throwConfigurationExceptionIfErrorsExist() { public void throwConfigurationExceptionIfErrorsExist() {
if (!hasErrors()) { if (!hasErrors()) {
return; return;
} }
throw new ConfigurationException(getMessages());
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<Message> messages) { public Errors merge(Collection<Message> messages) {
@ -486,6 +574,7 @@ public final class Errors {
if (moreErrors.root == root || moreErrors.root.errors == null) { if (moreErrors.root == root || moreErrors.root.errors == null) {
return this; return this;
} }
merge(moreErrors.root.errors); merge(moreErrors.root.errors);
return this; return this;
} }
@ -495,7 +584,7 @@ public final class Errors {
return this; return this;
} }
public List<Object> getSources() { private List<Object> getSources() {
List<Object> sources = Lists.newArrayList(); List<Object> sources = Lists.newArrayList();
for (Errors e = this; e != null; e = e.parent) { for (Errors e = this; e != null; e = e.parent) {
if (e.source != SourceProvider.UNKNOWN_SOURCE) { if (e.source != SourceProvider.UNKNOWN_SOURCE) {
@ -522,11 +611,16 @@ public final class Errors {
} }
public Errors addMessage(String messageFormat, Object... arguments) { public Errors addMessage(String messageFormat, Object... arguments) {
return addMessage(null, messageFormat, arguments); return addMessage(ErrorId.OTHER, null, messageFormat, arguments);
} }
private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) { public Errors addMessage(ErrorId errorId, String messageFormat, Object... arguments) {
addMessage(Messages.create(cause, getSources(), messageFormat, 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; return this;
} }
@ -538,10 +632,16 @@ public final class Errors {
return this; return this;
} }
// TODO(lukes): inline into callers
public static String format(String messageFormat, Object... arguments) {
return Messages.format(messageFormat, arguments);
}
public List<Message> getMessages() { public List<Message> getMessages() {
if (root.errors == null) { if (root.errors == null) {
return ImmutableList.of(); return ImmutableList.of();
} }
return new Ordering<Message>() { return new Ordering<Message>() {
@Override @Override
public int compare(Message a, Message b) { public int compare(Message a, Message b) {
@ -553,4 +653,20 @@ public final class Errors {
public int size() { public int size() {
return root.errors == null ? 0 : root.errors.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();
}
} }

View file

@ -21,7 +21,7 @@ final class ExposedKeyFactory<T> implements InternalFactory<T>, CreationListener
@Override @Override
public void notify(Errors errors) { public void notify(Errors errors) {
InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector(); InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector();
BindingImpl<T> explicitBinding = privateInjector.state.getExplicitBinding(key); BindingImpl<T> explicitBinding = privateInjector.getBindingData().getExplicitBinding(key);
// validate that the child injector has its own factory. If the getInternalFactory() returns // validate that the child injector has its own factory. If the getInternalFactory() returns
// this, then that child injector doesn't have a factory (and getExplicitBinding has returned // this, then that child injector doesn't have a factory (and getExplicitBinding has returned
@ -34,8 +34,10 @@ final class ExposedKeyFactory<T> implements InternalFactory<T>, CreationListener
this.delegate = explicitBinding; this.delegate = explicitBinding;
} }
@Override
public T get(InternalContext context, Dependency<?> dependency, boolean linked) public T get(InternalContext context, Dependency<?> dependency, boolean linked)
throws InternalProvisionException { throws InternalProvisionException {
// TODO(lukes): add a source to the thrown exception?
return delegate.getInternalFactory().get(context, dependency, linked); return delegate.getInternalFactory().get(context, dependency, linked);
} }
} }

View file

@ -0,0 +1,34 @@
package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.spi.ErrorDetail;
import java.io.Serializable;
import java.util.Formatter;
import java.util.List;
/** Generic error message representing a Guice internal error. */
public final class GenericErrorDetail extends InternalErrorDetail<GenericErrorDetail>
implements Serializable {
public GenericErrorDetail(
ErrorId errorId, String message, List<Object> sources, Throwable cause) {
super(errorId, checkNotNull(message, "message"), sources, cause);
}
@Override
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
Preconditions.checkArgument(mergeableErrors.isEmpty(), "Unexpected mergeable errors");
List<Object> dependencies = getSources();
for (Object source : Lists.reverse(dependencies)) {
formatter.format(" ");
new SourceFormatter(source, formatter, /* omitPreposition= */ false).format();
}
}
@Override
public GenericErrorDetail withSources(List<Object> newSources) {
return new GenericErrorDetail(errorId, getMessage(), newSources, getCause());
}
}

View file

@ -0,0 +1,14 @@
package com.google.inject.internal;
/**
* Class used for restricting APIs in other packages to only be used by this package.
*
* <p>Other packages can reference this class but only this package can reference an instance of it,
* so adding this class as a method param ensures that only this package can call it (provided null
* is disallowed).
*/
public final class GuiceInternal {
static final GuiceInternal GUICE_INTERNAL = new GuiceInternal();
private GuiceInternal() {}
}

View file

@ -1,6 +1,9 @@
package com.google.inject.internal; package com.google.inject.internal;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -17,19 +20,22 @@ import com.google.inject.spi.ScopeBinding;
import com.google.inject.spi.StaticInjectionRequest; import com.google.inject.spi.StaticInjectionRequest;
import com.google.inject.spi.TypeConverterBinding; import com.google.inject.spi.TypeConverterBinding;
import com.google.inject.spi.TypeListenerBinding; import com.google.inject.spi.TypeListenerBinding;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull; /**
* A container that stores an injector's binding data. This excludes JIT binding data, which is
* stored in {@link InjectorJitBindingData}.
*/
class InjectorBindingData {
final class InheritingState implements State { // The parent injector's InjectorBindingData, if the parent injector exists.
private final Optional<InjectorBindingData> parent;
private final State parent;
// Must be a linked hashmap in order to preserve order of bindings in Modules. // Must be a linked hashmap in order to preserve order of bindings in Modules.
private final Map<Key<?>, Binding<?>> explicitBindingsMutable = Maps.newLinkedHashMap(); private final Map<Key<?>, Binding<?>> explicitBindingsMutable = Maps.newLinkedHashMap();
@ -44,109 +50,97 @@ final class InheritingState implements State {
private final List<TypeListenerBinding> typeListenerBindings = Lists.newArrayList(); private final List<TypeListenerBinding> typeListenerBindings = Lists.newArrayList();
private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList(); private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList();
private final List<ModuleAnnotatedMethodScannerBinding> scannerBindings = Lists.newArrayList(); private final List<ModuleAnnotatedMethodScannerBinding> scannerBindings = Lists.newArrayList();
private final WeakKeySet blacklistedKeys; // The injector's explicit bindings, indexed by the binding's type.
private final Object lock; private final ListMultimap<TypeLiteral<?>, Binding<?>> indexedExplicitBindings =
ArrayListMultimap.create();
InheritingState(State parent) { InjectorBindingData(Optional<InjectorBindingData> parent) {
this.parent = checkNotNull(parent, "parent"); this.parent = parent;
this.lock = (parent == State.NONE) ? this : parent.lock();
this.blacklistedKeys = new WeakKeySet(lock);
} }
@Override public Optional<InjectorBindingData> parent() {
public State parent() {
return parent; return parent;
} }
@SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types @SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types
@Override
public <T> BindingImpl<T> getExplicitBinding(Key<T> key) { public <T> BindingImpl<T> getExplicitBinding(Key<T> key) {
Binding<?> binding = explicitBindings.get(key); Binding<?> binding = explicitBindings.get(key);
return binding != null ? (BindingImpl<T>) binding : parent.getExplicitBinding(key); if (binding == null && parent.isPresent()) {
return parent.get().getExplicitBinding(key);
}
return (BindingImpl<T>) binding;
} }
@Override
public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() { public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() {
return explicitBindings; return explicitBindings;
} }
@Override
public void putBinding(Key<?> key, BindingImpl<?> binding) { public void putBinding(Key<?> key, BindingImpl<?> binding) {
explicitBindingsMutable.put(key, binding); explicitBindingsMutable.put(key, binding);
} }
@Override
public void putProviderLookup(ProviderLookup<?> lookup) { public void putProviderLookup(ProviderLookup<?> lookup) {
providerLookups.add(lookup); providerLookups.add(lookup);
} }
@Override
public Set<ProviderLookup<?>> getProviderLookupsThisLevel() { public Set<ProviderLookup<?>> getProviderLookupsThisLevel() {
return providerLookups; return providerLookups;
} }
@Override
public void putStaticInjectionRequest(StaticInjectionRequest staticInjectionRequest) { public void putStaticInjectionRequest(StaticInjectionRequest staticInjectionRequest) {
staticInjectionRequests.add(staticInjectionRequest); staticInjectionRequests.add(staticInjectionRequest);
} }
@Override
public Set<StaticInjectionRequest> getStaticInjectionRequestsThisLevel() { public Set<StaticInjectionRequest> getStaticInjectionRequestsThisLevel() {
return staticInjectionRequests; return staticInjectionRequests;
} }
@Override
public void putInjectionRequest(InjectionRequest<?> injectionRequest) { public void putInjectionRequest(InjectionRequest<?> injectionRequest) {
injectionRequests.add(injectionRequest); injectionRequests.add(injectionRequest);
} }
@Override
public Set<InjectionRequest<?>> getInjectionRequestsThisLevel() { public Set<InjectionRequest<?>> getInjectionRequestsThisLevel() {
return injectionRequests; return injectionRequests;
} }
@Override
public void putMembersInjectorLookup(MembersInjectorLookup<?> membersInjectorLookup) { public void putMembersInjectorLookup(MembersInjectorLookup<?> membersInjectorLookup) {
membersInjectorLookups.add(membersInjectorLookup); membersInjectorLookups.add(membersInjectorLookup);
} }
@Override
public Set<MembersInjectorLookup<?>> getMembersInjectorLookupsThisLevel() { public Set<MembersInjectorLookup<?>> getMembersInjectorLookupsThisLevel() {
return membersInjectorLookups; return membersInjectorLookups;
} }
@Override
public ScopeBinding getScopeBinding(Class<? extends Annotation> annotationType) { public ScopeBinding getScopeBinding(Class<? extends Annotation> annotationType) {
ScopeBinding scopeBinding = scopes.get(annotationType); ScopeBinding scopeBinding = scopes.get(annotationType);
return scopeBinding != null ? scopeBinding : parent.getScopeBinding(annotationType); if (scopeBinding == null && parent.isPresent()) {
return parent.get().getScopeBinding(annotationType);
}
return scopeBinding;
} }
@Override
public void putScopeBinding(Class<? extends Annotation> annotationType, ScopeBinding scope) { public void putScopeBinding(Class<? extends Annotation> annotationType, ScopeBinding scope) {
scopes.put(annotationType, scope); scopes.put(annotationType, scope);
} }
@Override
public Collection<ScopeBinding> getScopeBindingsThisLevel() { public Collection<ScopeBinding> getScopeBindingsThisLevel() {
return scopes.values(); return scopes.values();
} }
@Override
public Iterable<TypeConverterBinding> getConvertersThisLevel() { public Iterable<TypeConverterBinding> getConvertersThisLevel() {
return converters; return converters;
} }
@Override
public void addConverter(TypeConverterBinding typeConverterBinding) { public void addConverter(TypeConverterBinding typeConverterBinding) {
converters.add(typeConverterBinding); converters.add(typeConverterBinding);
} }
@Override
public TypeConverterBinding getConverter( public TypeConverterBinding getConverter(
String stringValue, TypeLiteral<?> type, Errors errors, Object source) { String stringValue, TypeLiteral<?> type, Errors errors, Object source) {
TypeConverterBinding matchingConverter = null; TypeConverterBinding matchingConverter = null;
for (State s = this; s != State.NONE; s = s.parent()) { InjectorBindingData b = this;
for (TypeConverterBinding converter : s.getConvertersThisLevel()) { while (b != null) {
for (TypeConverterBinding converter : b.getConvertersThisLevel()) {
if (converter.getTypeMatcher().matches(type)) { if (converter.getTypeMatcher().matches(type)) {
if (matchingConverter != null) { if (matchingConverter != null) {
errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter); errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter);
@ -154,92 +148,65 @@ final class InheritingState implements State {
matchingConverter = converter; matchingConverter = converter;
} }
} }
b = b.parent().orElse(null);
} }
return matchingConverter; return matchingConverter;
} }
@Override
public void addTypeListener(TypeListenerBinding listenerBinding) { public void addTypeListener(TypeListenerBinding listenerBinding) {
typeListenerBindings.add(listenerBinding); typeListenerBindings.add(listenerBinding);
} }
@Override public ImmutableList<TypeListenerBinding> getTypeListenerBindings() {
public List<TypeListenerBinding> getTypeListenerBindings() { if (parent.isPresent()) {
List<TypeListenerBinding> parentBindings = parent.getTypeListenerBindings(); return new ImmutableList.Builder<TypeListenerBinding>()
List<TypeListenerBinding> result = .addAll(parent.get().getTypeListenerBindings())
Lists.newArrayListWithCapacity(parentBindings.size() + typeListenerBindings.size()); .addAll(typeListenerBindings)
result.addAll(parentBindings); .build();
result.addAll(typeListenerBindings); }
return result; return ImmutableList.copyOf(typeListenerBindings);
} }
@Override public ImmutableList<TypeListenerBinding> getTypeListenerBindingsThisLevel() {
public List<TypeListenerBinding> getTypeListenerBindingsThisLevel() { return ImmutableList.copyOf(typeListenerBindings);
return typeListenerBindings;
} }
@Override
public void addProvisionListener(ProvisionListenerBinding listenerBinding) { public void addProvisionListener(ProvisionListenerBinding listenerBinding) {
provisionListenerBindings.add(listenerBinding); provisionListenerBindings.add(listenerBinding);
} }
@Override public ImmutableList<ProvisionListenerBinding> getProvisionListenerBindings() {
public List<ProvisionListenerBinding> getProvisionListenerBindings() { if (parent.isPresent()) {
List<ProvisionListenerBinding> parentBindings = parent.getProvisionListenerBindings(); return new ImmutableList.Builder<ProvisionListenerBinding>()
List<ProvisionListenerBinding> result = .addAll(parent.get().getProvisionListenerBindings())
Lists.newArrayListWithCapacity(parentBindings.size() + provisionListenerBindings.size()); .addAll(provisionListenerBindings)
result.addAll(parentBindings); .build();
result.addAll(provisionListenerBindings); }
return result; return ImmutableList.copyOf(provisionListenerBindings);
} }
@Override public ImmutableList<ProvisionListenerBinding> getProvisionListenerBindingsThisLevel() {
public List<ProvisionListenerBinding> getProvisionListenerBindingsThisLevel() { return ImmutableList.copyOf(provisionListenerBindings);
return provisionListenerBindings;
} }
@Override
public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) {
scannerBindings.add(scanner); scannerBindings.add(scanner);
} }
@Override public ImmutableList<ModuleAnnotatedMethodScannerBinding> getScannerBindings() {
public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() { if (parent.isPresent()) {
List<ModuleAnnotatedMethodScannerBinding> parentBindings = parent.getScannerBindings(); return new ImmutableList.Builder<ModuleAnnotatedMethodScannerBinding>()
List<ModuleAnnotatedMethodScannerBinding> result = .addAll(parent.get().getScannerBindings())
Lists.newArrayListWithCapacity(parentBindings.size() + scannerBindings.size()); .addAll(scannerBindings)
result.addAll(parentBindings); .build();
result.addAll(scannerBindings); }
return result; return ImmutableList.copyOf(scannerBindings);
} }
@Override public ImmutableList<ModuleAnnotatedMethodScannerBinding> getScannerBindingsThisLevel() {
public List<ModuleAnnotatedMethodScannerBinding> getScannerBindingsThisLevel() { return ImmutableList.copyOf(scannerBindings);
return scannerBindings;
} }
@Override
public void blacklist(Key<?> key, State state, Object source) {
parent.blacklist(key, state, source);
blacklistedKeys.add(key, state, source);
}
@Override
public boolean isBlacklisted(Key<?> key) {
return blacklistedKeys.contains(key);
}
@Override
public Set<Object> getSourcesForBlacklistedKey(Key<?> key) {
return blacklistedKeys.getSources(key);
}
@Override
public Object lock() {
return lock;
}
@Override
public Map<Class<? extends Annotation>, Scope> getScopes() { public Map<Class<? extends Annotation>, Scope> getScopes() {
ImmutableMap.Builder<Class<? extends Annotation>, Scope> builder = ImmutableMap.builder(); ImmutableMap.Builder<Class<? extends Annotation>, Scope> builder = ImmutableMap.builder();
for (Map.Entry<Class<? extends Annotation>, ScopeBinding> entry : scopes.entrySet()) { for (Map.Entry<Class<? extends Annotation>, ScopeBinding> entry : scopes.entrySet()) {
@ -247,4 +214,18 @@ final class InheritingState implements State {
} }
return builder.build(); return builder.build();
} }
/**
* Once the injector's explicit bindings are finalized, this method is called to index all
* explicit bindings by their return type.
*/
void indexBindingsByType() {
for (Binding<?> binding : getExplicitBindingsThisLevel().values()) {
indexedExplicitBindings.put(binding.getKey().getTypeLiteral(), binding);
}
}
public ListMultimap<TypeLiteral<?>, Binding<?>> getIndexedExplicitBindings() {
return indexedExplicitBindings;
}
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
package com.google.inject.internal;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Key;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* A container for most just-in-time (JIT) binding data corresponding to an Injector. It
* includes @Inject constructor bindings. It does not include {@link MembersInjectorStore} or {@link
* ProvisionListenerCallbackStore}, which are still stored in {@link InjectorImpl}.
*/
final class InjectorJitBindingData {
/** Just-in-time binding cache. Guarded by {@link #lock}. */
private final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap();
/**
* Cache of Keys that we were unable to create JIT bindings for, so we don't keep trying. Guarded
* by {@link #lock}.
*/
private final Set<Key<?>> failedJitBindings = Sets.newHashSet();
// The set of JIT binding keys that are banned for this particular injector, because a binding
// already exists in a child injector. Guarded by {@link #lock}.
private final WeakKeySet bannedKeys;
// The InjectorJitBindingData corresponding to the Injector's parent, if it exists.
private final Optional<InjectorJitBindingData> parent;
/**
* This lock is needed for threadsafe InjectorJitBindingData accesses. It corresponds to this
* InjectorJitBindingData's highest ancestor.
*/
private final Object lock;
InjectorJitBindingData(Optional<InjectorJitBindingData> parent) {
this.parent = parent;
this.lock = parent.isPresent() ? parent.get().lock() : this;
this.bannedKeys = new WeakKeySet(lock);
}
Map<Key<?>, BindingImpl<?>> getJitBindings() {
return Collections.unmodifiableMap(jitBindings);
}
BindingImpl<?> getJitBinding(Key<?> key) {
return jitBindings.get(key);
}
void putJitBinding(Key<?> key, BindingImpl<?> binding) {
jitBindings.put(key, binding);
}
void removeJitBinding(Key<?> key) {
jitBindings.remove(key);
}
boolean isFailedJitBinding(Key<?> key) {
return failedJitBindings.contains(key);
}
void addFailedJitBinding(Key<?> key) {
failedJitBindings.add(key);
}
/**
* Forbids the corresponding injector and its ancestors from creating a binding to {@code key}.
* Child injectors ban their bound keys on their parent injectors to prevent just-in-time bindings
* on the parent injector that would conflict, and pass along their InjectorBindingData to control
* the banned key's lifetime.
*/
void banKey(Key<?> key, InjectorBindingData injectorBindingData, Object source) {
banKeyInParent(key, injectorBindingData, source);
bannedKeys.add(key, injectorBindingData, source);
}
/**
* Similar to {@link #banKey(Key, InjectorBindingData, Object)} but we only begin banning the
* binding at the parent level. This is used to prevent JIT bindings in the parent injector from
* overriding explicit bindings declared in a child injector.
*/
void banKeyInParent(Key<?> key, InjectorBindingData injectorBindingData, Object source) {
if (parent.isPresent()) {
parent.get().banKey(key, injectorBindingData, source);
}
}
/**
* Returns true if {@code key} is forbidden from being bound in the injector corresponding to this
* data object. This indicates that one of the injector's children has bound the key.
*/
boolean isBannedKey(Key<?> key) {
return bannedKeys.contains(key);
}
/** Returns the source of a banned key. */
Set<Object> getSourcesForBannedKey(Key<?> key) {
return bannedKeys.getSources(key);
}
Object lock() {
return lock;
}
}

View file

@ -1,5 +1,9 @@
package com.google.inject.internal; package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.internal.GuiceInternal.GUICE_INTERNAL;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.inject.Binder; import com.google.inject.Binder;
@ -10,24 +14,34 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.Stage; import com.google.inject.Stage;
import com.google.inject.internal.InjectorImpl.InjectorOptions; import com.google.inject.internal.InjectorImpl.InjectorOptions;
import com.google.inject.internal.util.ContinuousStopwatch;
import com.google.inject.internal.util.SourceProvider; import com.google.inject.internal.util.SourceProvider;
import com.google.inject.internal.util.Stopwatch; import com.google.inject.spi.BindingSourceRestriction;
import com.google.inject.spi.Dependency; import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element; import com.google.inject.spi.Element;
import com.google.inject.spi.Elements; import com.google.inject.spi.Elements;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding;
import com.google.inject.spi.PrivateElements; import com.google.inject.spi.PrivateElements;
import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.ProvisionListenerBinding;
import com.google.inject.spi.TypeListenerBinding; import com.google.inject.spi.TypeListenerBinding;
import java.util.List; import java.util.List;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkState; import java.util.logging.Logger;
import static com.google.inject.Scopes.SINGLETON;
/** /**
* A partially-initialized injector. See {@link InternalInjectorCreator}, which * InjectorShell is used by {@link InternalInjectorCreator} to recursively create a tree of
* uses this to build a tree of injectors in batch. * uninitialized {@link Injector}s. Each InjectorShell corresponds to either the top-level root
* injector, or a private child injector.
*
* <p>The root InjectorShell extracts elements from its list of modules and processes these elements
* to aggregate data that is used to populate its injector's fields. Child injectors are constructed
* similarly, but using {@link PrivateElements} instead of modules.
*
* <p>It is necessary to create the root and child injectors in a single batch because there can be
* bidirectional parent <-> child injector dependencies that require the entire tree of injectors to
* be initialized together in the {@link InternalInjectorCreator}.
*
*/ */
final class InjectorShell { final class InjectorShell {
@ -47,48 +61,19 @@ final class InjectorShell {
return elements; return elements;
} }
/**
* The Injector is a special case because we allow both parent and child injectors to both have
* a binding for that key.
*/
private static void bindInjector(InjectorImpl injector) {
Key<Injector> key = Key.get(Injector.class);
InjectorFactory injectorFactory = new InjectorFactory(injector);
injector.state.putBinding(key,
new ProviderInstanceBindingImpl<>(injector, key, SourceProvider.UNKNOWN_SOURCE,
injectorFactory, Scoping.UNSCOPED, injectorFactory,
ImmutableSet.of()));
}
private static void bindStage(InjectorImpl injector, Stage stage) {
Key<Stage> key = Key.get(Stage.class);
InstanceBindingImpl<Stage> stageBinding = new InstanceBindingImpl<>(
injector,
key,
SourceProvider.UNKNOWN_SOURCE,
new ConstantFactory<>(Initializables.of(stage)),
ImmutableSet.of(),
stage);
injector.state.putBinding(key, stageBinding);
}
static class Builder { static class Builder {
private final List<Element> elements = Lists.newArrayList(); private final List<Element> elements = Lists.newArrayList();
private final List<Module> modules = Lists.newArrayList(); private final List<Module> modules = Lists.newArrayList();
/** // lazily constructed fields
* lazily constructed private InjectorBindingData bindingData;
*/ private InjectorJitBindingData jitBindingData;
private State state;
private InjectorImpl parent; private InjectorImpl parent;
private InjectorOptions options; private InjectorOptions options;
private Stage stage; private Stage stage;
/** /** null unless this exists in a {@link Binder#newPrivateBinder private environment} */
* null unless this exists in a {@link Binder#newPrivateBinder private environment}
*/
private PrivateElementsImpl privateElements; private PrivateElementsImpl privateElements;
Builder stage(Stage stage) { Builder stage(Stage stage) {
@ -98,7 +83,8 @@ final class InjectorShell {
Builder parent(InjectorImpl parent) { Builder parent(InjectorImpl parent) {
this.parent = parent; this.parent = parent;
this.state = new InheritingState(parent.state); this.jitBindingData = new InjectorJitBindingData(Optional.of(parent.getJitBindingData()));
this.bindingData = new InjectorBindingData(Optional.of(parent.getBindingData()));
this.options = parent.options; this.options = parent.options;
this.stage = options.stage; this.stage = options.stage;
return this; return this;
@ -111,22 +97,24 @@ final class InjectorShell {
} }
void addModules(Iterable<? extends Module> modules) { void addModules(Iterable<? extends Module> modules) {
if (modules != null) {
for (Module module : modules) { for (Module module : modules) {
this.modules.add(module); this.modules.add(module);
} }
} }
}
Stage getStage() { Stage getStage() {
return options.stage; return options.stage;
} }
/** /** Synchronize on this before calling {@link #build}. */
* Synchronize on this before calling {@link #build}.
*/
Object lock() { Object lock() {
return getState().lock(); // Lazily initializes bindingData and jitBindingData, if they were not already
// initialized with a parent injector by {@link #parent(InjectorImpl)}.
if (bindingData == null) {
jitBindingData = new InjectorJitBindingData(Optional.empty());
bindingData = new InjectorBindingData(Optional.empty());
}
return jitBindingData.lock();
} }
/** /**
@ -136,27 +124,38 @@ final class InjectorShell {
*/ */
List<InjectorShell> build( List<InjectorShell> build(
Initializer initializer, Initializer initializer,
ProcessedBindingData bindingData, ProcessedBindingData processedBindingData,
Stopwatch stopwatch, ContinuousStopwatch stopwatch,
Errors errors) { Errors errors) {
checkState(stage != null, "Stage not initialized"); checkState(stage != null, "Stage not initialized");
checkState(privateElements == null || parent != null, "PrivateElements with no parent"); checkState(privateElements == null || parent != null, "PrivateElements with no parent");
checkState(state != null, "no state. Did you remember to lock() ?"); checkState(bindingData != null, "no binding data. Did you remember to lock() ?");
checkState(
(privateElements == null && elements.isEmpty()) || modules.isEmpty(),
"The shell is either built from modules (root) or from PrivateElements (children).");
// bind Singleton if this is a top-level injector // bind Singleton if this is a top-level injector
if (parent == null) { if (parent == null) {
modules.add(0, new RootModule()); modules.add(0, new RootModule());
} else { } else {
modules.add(0, new InheritedScannersModule(parent.state)); modules.add(0, new InheritedScannersModule(parent.getBindingData()));
} }
elements.addAll(Elements.getElements(stage, modules)); elements.addAll(Elements.getElements(stage, modules));
// Check binding source restrictions only for the root shell (note that the root shell
// can have a parent Injector, when Injector.createChildInjector is called). It isn't
// necessary to call this check on child PrivateElements shells because it walks the entire
// tree of elements, recurring on PrivateElements.
if (privateElements == null) {
elements.addAll(BindingSourceRestriction.check(GUICE_INTERNAL, elements));
}
// Look for injector-changing options // Look for injector-changing options
InjectorOptionsProcessor optionsProcessor = new InjectorOptionsProcessor(errors); InjectorOptionsProcessor optionsProcessor = new InjectorOptionsProcessor(errors);
optionsProcessor.process(null, elements); optionsProcessor.process(null, elements);
options = optionsProcessor.getOptions(stage, options); options = optionsProcessor.getOptions(stage, options);
InjectorImpl injector = new InjectorImpl(parent, state, options); InjectorImpl injector = new InjectorImpl(parent, bindingData, jitBindingData, options);
if (privateElements != null) { if (privateElements != null) {
privateElements.initInjector(injector); privateElements.initInjector(injector);
} }
@ -171,10 +170,11 @@ final class InjectorShell {
new MessageProcessor(errors).process(injector, elements); new MessageProcessor(errors).process(injector, elements);
new ListenerBindingProcessor(errors).process(injector, elements); new ListenerBindingProcessor(errors).process(injector, elements);
List<TypeListenerBinding> typeListenerBindings = injector.state.getTypeListenerBindings(); List<TypeListenerBinding> typeListenerBindings =
injector.getBindingData().getTypeListenerBindings();
injector.membersInjectorStore = new MembersInjectorStore(injector, typeListenerBindings); injector.membersInjectorStore = new MembersInjectorStore(injector, typeListenerBindings);
List<ProvisionListenerBinding> provisionListenerBindings = List<ProvisionListenerBinding> provisionListenerBindings =
injector.state.getProvisionListenerBindings(); injector.getBindingData().getProvisionListenerBindings();
injector.provisionListenerStore = injector.provisionListenerStore =
new ProvisionListenerCallbackStore(provisionListenerBindings); new ProvisionListenerCallbackStore(provisionListenerBindings);
stopwatch.resetAndLog("TypeListeners & ProvisionListener creation"); stopwatch.resetAndLog("TypeListeners & ProvisionListener creation");
@ -187,13 +187,13 @@ final class InjectorShell {
bindStage(injector, stage); bindStage(injector, stage);
bindInjector(injector); bindInjector(injector);
//bindLogger(injector); bindLogger(injector);
// Process all normal bindings, then UntargettedBindings. // Process all normal bindings, then UntargettedBindings.
// This is necessary because UntargettedBindings can create JIT bindings // This is necessary because UntargettedBindings can create JIT bindings
// and need all their other dependencies set up ahead of time. // and need all their other dependencies set up ahead of time.
new BindingProcessor(errors, initializer, bindingData).process(injector, elements); new BindingProcessor(errors, initializer, processedBindingData).process(injector, elements);
new UntargettedBindingProcessor(errors, bindingData).process(injector, elements); new UntargettedBindingProcessor(errors, processedBindingData).process(injector, elements);
stopwatch.resetAndLog("Binding creation"); stopwatch.resetAndLog("Binding creation");
new ModuleAnnotatedMethodScannerProcessor(errors).process(injector, elements); new ModuleAnnotatedMethodScannerProcessor(errors).process(injector, elements);
@ -206,19 +206,34 @@ final class InjectorShell {
PrivateElementProcessor processor = new PrivateElementProcessor(errors); PrivateElementProcessor processor = new PrivateElementProcessor(errors);
processor.process(injector, elements); processor.process(injector, elements);
for (Builder builder : processor.getInjectorShellBuilders()) { for (Builder builder : processor.getInjectorShellBuilders()) {
injectorShells.addAll(builder.build(initializer, bindingData, stopwatch, errors)); injectorShells.addAll(builder.build(initializer, processedBindingData, stopwatch, errors));
} }
stopwatch.resetAndLog("Private environment creation"); stopwatch.resetAndLog("Private environment creation");
return injectorShells; return injectorShells;
} }
private State getState() {
if (state == null) {
state = new InheritingState(State.NONE);
}
return state;
} }
/**
* The Injector is a special case because we allow both parent and child injectors to both have a
* binding for that key.
*/
private static void bindInjector(InjectorImpl injector) {
Key<Injector> key = Key.get(Injector.class);
InjectorFactory injectorFactory = new InjectorFactory(injector);
injector
.getBindingData()
.putBinding(
key,
new ProviderInstanceBindingImpl<Injector>(
injector,
key,
SourceProvider.UNKNOWN_SOURCE,
injectorFactory,
Scoping.UNSCOPED,
injectorFactory,
ImmutableSet.<InjectionPoint>of()));
} }
private static class InjectorFactory implements InternalFactory<Injector>, Provider<Injector> { private static class InjectorFactory implements InternalFactory<Injector>, Provider<Injector> {
@ -238,11 +253,66 @@ final class InjectorShell {
return injector; return injector;
} }
@Override
public String toString() { public String toString() {
return "Provider<Injector>"; return "Provider<Injector>";
} }
} }
/**
* The Logger is a special case because it knows the injection point of the injected member. It's
* the only binding that does this.
*/
private static void bindLogger(InjectorImpl injector) {
Key<Logger> key = Key.get(Logger.class);
LoggerFactory loggerFactory = new LoggerFactory();
injector
.getBindingData()
.putBinding(
key,
new ProviderInstanceBindingImpl<Logger>(
injector,
key,
SourceProvider.UNKNOWN_SOURCE,
loggerFactory,
Scoping.UNSCOPED,
loggerFactory,
ImmutableSet.<InjectionPoint>of()));
}
private static class LoggerFactory implements InternalFactory<Logger>, Provider<Logger> {
@Override
public Logger get(InternalContext context, Dependency<?> dependency, boolean linked) {
InjectionPoint injectionPoint = dependency.getInjectionPoint();
return injectionPoint == null
? Logger.getAnonymousLogger()
: Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
@Override
public Logger get() {
return Logger.getAnonymousLogger();
}
@Override
public String toString() {
return "Provider<Logger>";
}
}
private static void bindStage(InjectorImpl injector, Stage stage) {
Key<Stage> key = Key.get(Stage.class);
InstanceBindingImpl<Stage> stageBinding =
new InstanceBindingImpl<Stage>(
injector,
key,
SourceProvider.UNKNOWN_SOURCE,
new ConstantFactory<Stage>(Initializables.of(stage)),
ImmutableSet.<InjectionPoint>of(),
stage);
injector.getBindingData().putBinding(key, stageBinding);
}
private static class RootModule implements Module { private static class RootModule implements Module {
@Override @Override
public void configure(Binder binder) { public void configure(Binder binder) {
@ -253,15 +323,15 @@ final class InjectorShell {
} }
private static class InheritedScannersModule implements Module { private static class InheritedScannersModule implements Module {
private final State state; private final InjectorBindingData bindingData;
InheritedScannersModule(State state) { InheritedScannersModule(InjectorBindingData bindingData) {
this.state = state; this.bindingData = bindingData;
} }
@Override @Override
public void configure(Binder binder) { public void configure(Binder binder) {
for (ModuleAnnotatedMethodScannerBinding binding : state.getScannerBindings()) { for (ModuleAnnotatedMethodScannerBinding binding : bindingData.getScannerBindings()) {
binding.applyTo(binder); binding.applyTo(binder);
} }
} }

View file

@ -0,0 +1,61 @@
package com.google.inject.internal;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableSet;
import com.google.inject.spi.ErrorDetail;
import java.util.List;
import java.util.Optional;
/**
* Represents an error created by Guice as opposed to custom error added to the binder from
* application code.
*/
abstract class InternalErrorDetail<T extends ErrorDetail<T>> extends ErrorDetail<T> {
// A list of errors that have help documentation.
private static final ImmutableSet<ErrorId> DOCUMENTED_ERRORS =
ImmutableSet.<ErrorId>builder()
.add(ErrorId.BINDING_ALREADY_SET)
.add(ErrorId.CAN_NOT_PROXY_CLASS)
.add(ErrorId.CIRCULAR_PROXY_DISABLED)
.add(ErrorId.DUPLICATE_BINDING_ANNOTATIONS)
.add(ErrorId.DUPLICATE_ELEMENT)
.add(ErrorId.DUPLICATE_SCOPES)
.add(ErrorId.ERROR_INJECTING_CONSTRUCTOR)
.add(ErrorId.ERROR_INJECTING_METHOD)
.add(ErrorId.ERROR_IN_CUSTOM_PROVIDER)
.add(ErrorId.INJECT_INNER_CLASS)
.add(ErrorId.MISSING_CONSTRUCTOR)
.add(ErrorId.MISSING_IMPLEMENTATION)
.add(ErrorId.NULL_INJECTED_INTO_NON_NULLABLE)
.add(ErrorId.NULL_VALUE_IN_MAP)
.add(ErrorId.SCOPE_NOT_FOUND)
.add(ErrorId.TOO_MANY_CONSTRUCTORS)
.build();
private static final String DOC_BASE_URL = "https://github.com/google/guice/wiki/";
protected final ErrorId errorId;
protected InternalErrorDetail(
ErrorId errorId, String message, List<Object> sources, Throwable cause) {
super(message, sources, cause);
this.errorId = errorId;
}
@Override
protected final Optional<String> getLearnMoreLink() {
if (DOCUMENTED_ERRORS.contains(errorId)) {
return Optional.of(DOC_BASE_URL + errorId.name());
}
return Optional.empty();
}
@Override
protected final Optional<String> getErrorIdentifier() {
if (errorId == ErrorId.OTHER) {
return Optional.empty();
}
String id = "Guice/" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, errorId.name());
return Optional.of(id);
}
}

View file

@ -2,20 +2,131 @@ package com.google.inject.internal;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.logging.Logger;
/** Contains flags for Guice. */
public final class InternalFlags {
private static final Logger logger = Logger.getLogger(InternalFlags.class.getName());
private static final IncludeStackTraceOption INCLUDE_STACK_TRACES =
getSystemOption(
"guice_include_stack_traces",
IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE);
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING =
getSystemOption(
"guice_custom_class_loading",
CustomClassLoadingOption.BRIDGE,
CustomClassLoadingOption.OFF);
private static final NullableProvidesOption NULLABLE_PROVIDES =
getSystemOption("guice_check_nullable_provides_params", NullableProvidesOption.ERROR);
private static final BytecodeGenOption BYTECODE_GEN_OPTION =
getSystemOption("guice_bytecode_gen_option", BytecodeGenOption.ENABLED);
private static final ColorizeOption COLORIZE_OPTION =
getSystemOption("guice_colorize_error_messages", ColorizeOption.OFF);
/** The options for Guice stack trace collection. */
public enum IncludeStackTraceOption {
/** No stack trace collection */
OFF,
/** Minimum stack trace collection (Default) */
ONLY_FOR_DECLARING_SOURCE,
/** Full stack trace for everything */
COMPLETE
}
/** The options for Guice custom class loading. */
public enum CustomClassLoadingOption {
/**
* Define fast/enhanced types in the same class loader as their original type, never creates
* class loaders. Uses Unsafe.defineAnonymousClass to gain access to existing class loaders.
*/
OFF,
/** /**
* Contains flags for Guice. * Define fast/enhanced types with Unsafe.defineAnonymousClass, never creates class loaders.
* This is faster than regular class loading and anonymous classes are easier to unload.
*
* <p>Note: with this option you cannot look up fast/enhanced types by name or mock/spy them.
*/ */
public class InternalFlags { ANONYMOUS,
private static final IncludeStackTraceOption INCLUDE_STACK_TRACES /**
= parseIncludeStackTraceOption(); * Attempt to define fast/enhanced types in the same class loader as their original type.
* Otherwise creates a child class loader whose parent is the original class loader. (Default)
*/
BRIDGE,
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING /**
= parseCustomClassLoadingOption(); * Define fast/enhanced types in a child class loader whose parent is the original class loader.
*
* <p>Note: with this option you cannot intercept package-private methods.
*/
CHILD
}
private static final NullableProvidesOption NULLABLE_PROVIDES /** Options for handling nullable parameters used in provides methods. */
= parseNullableProvidesOption(NullableProvidesOption.ERROR); 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
}
/**
* Options for controlling whether Guice uses bytecode generation at runtime. When bytecode
* generation is enabled, the following features will be enabled in Guice:
*
* <ul>
* <li>Runtime bytecode generation (instead of reflection) will be used when Guice need to
* invoke application code.
* <li>Method interception.
* </ul>
*
* <p>Bytecode generation is generally faster than using reflection when invoking application
* code, however, it can use more memory and slower in certain cases due to the time spent in
* generating the classes. If you prefer to use reflection over bytecode generation then set
* {@link BytecodeGenOption} to {@code DISABLED}.
*/
public enum BytecodeGenOption {
/**
* Bytecode generation is disabled and using features that require it such as method
* interception will throw errors at run time.
*/
DISABLED,
/** Bytecode generation is enabled. */
ENABLED,
}
/** Options for enable or disable using ansi color in error messages. */
public enum ColorizeOption {
AUTO {
@Override
boolean enabled() {
return System.console() != null && System.getenv("TERM") != null;
}
},
ON {
@Override
boolean enabled() {
return true;
}
},
OFF {
@Override
boolean enabled() {
return false;
}
};
abstract boolean enabled();
}
public static IncludeStackTraceOption getIncludeStackTraceOption() { public static IncludeStackTraceOption getIncludeStackTraceOption() {
return INCLUDE_STACK_TRACES; return INCLUDE_STACK_TRACES;
@ -29,19 +140,12 @@ public class InternalFlags {
return NULLABLE_PROVIDES; return NULLABLE_PROVIDES;
} }
private static IncludeStackTraceOption parseIncludeStackTraceOption() { public static boolean isBytecodeGenEnabled() {
return getSystemOption("guice_include_stack_traces", return BYTECODE_GEN_OPTION == BytecodeGenOption.ENABLED;
IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE);
} }
private static CustomClassLoadingOption parseCustomClassLoadingOption() { public static boolean enableColorizeErrorMessages() {
return getSystemOption("guice_custom_class_loading", return COLORIZE_OPTION.enabled();
CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
}
private static NullableProvidesOption parseNullableProvidesOption(
NullableProvidesOption defaultValue) {
return getSystemOption("guice_check_nullable_provides_params", defaultValue);
} }
/** /**
@ -61,66 +165,31 @@ public class InternalFlags {
* @param name of the system option * @param name of the system option
* @param defaultValue if the option is not set * @param defaultValue if the option is not set
* @param secureValue if the security manager disallows access to the option * @param secureValue if the security manager disallows access to the option
*
* @return value of the option, defaultValue if not set, secureValue if no access * @return value of the option, defaultValue if not set, secureValue if no access
*/ */
private static <T extends Enum<T>> T getSystemOption(final String name, T defaultValue, private static <T extends Enum<T>> T getSystemOption(final String name, T defaultValue,
T secureValue) { T secureValue) {
Class<T> enumType = defaultValue.getDeclaringClass(); Class<T> enumType = defaultValue.getDeclaringClass();
String value = null;
try { try {
String value = AccessController.doPrivileged((PrivilegedAction<String>) () value =
-> System.getProperty(name)); AccessController.doPrivileged(
new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty(name);
}
});
return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) : defaultValue; return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) : defaultValue;
} catch (SecurityException e) { } catch (SecurityException e) {
return secureValue; return secureValue;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.warning(value + " is not a valid flag value for " + name + ". "
+ " Values must be one of " + Arrays.asList(enumType.getEnumConstants()));
return defaultValue; return defaultValue;
} }
} }
/** private InternalFlags() {}
* The options for Guice stack trace collection.
*/
public enum IncludeStackTraceOption {
/**
* No stack trace collection
*/
OFF,
/**
* Minimum stack trace collection (Default)
*/
ONLY_FOR_DECLARING_SOURCE,
/**
* Full stack trace for everything
*/
COMPLETE
}
/**
* The options for Guice custom class loading.
*/
public enum CustomClassLoadingOption {
/**
* No custom class loading
*/
OFF,
/**
* Automatically bridge between class loaders (Default)
*/
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
}
} }

View file

@ -1,5 +1,6 @@
package com.google.inject.internal; package com.google.inject.internal;
import com.google.common.base.Stopwatch;
import com.google.inject.Binding; import com.google.inject.Binding;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
@ -9,12 +10,11 @@ import com.google.inject.Provider;
import com.google.inject.Scope; import com.google.inject.Scope;
import com.google.inject.Stage; import com.google.inject.Stage;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Stopwatch; import com.google.inject.internal.util.ContinuousStopwatch;
import com.google.inject.spi.Dependency; import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element; import com.google.inject.spi.Element;
import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.TypeConverterBinding; import com.google.inject.spi.TypeConverterBinding;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -28,24 +28,28 @@ import java.util.Set;
* top-level injector. * top-level injector.
* *
* <p>Injector construction happens in two phases. * <p>Injector construction happens in two phases.
*
* <ol> * <ol>
* <li>Static building. In this phase, we interpret commands, create bindings, and inspect * <li>Static building. In this phase, we interpret commands, create bindings, and inspect
* dependencies. During this phase, we hold a lock to ensure consistency with parent injectors. * dependencies. During this phase, we hold a lock to ensure consistency with parent
* No user code is executed in this phase.</li> * injectors. No user code is executed in this phase.
* <li>Dynamic injection. In this phase, we call user code. We inject members that requested * <li>Dynamic injection. In this phase, we call user code. We inject members that requested
* injection. This may require user's objects be created and their providers be called. And we * injection. This may require user's objects be created and their providers be called. And we
* create eager singletons. In this phase, user code may have started other threads. This phase * create eager singletons. In this phase, user code may have started other threads. This
* is not executed for injectors created using {@link Stage#TOOL the tool stage}</li> * phase is not executed for injectors created using {@link Stage#TOOL the tool stage}
* </ol> * </ol>
* *
* @author crazybob@google.com (Bob Lee)
* @author jessewilson@google.com (Jesse Wilson)
*/ */
public final class InternalInjectorCreator { public final class InternalInjectorCreator {
private final Stopwatch stopwatch = new Stopwatch(); private final ContinuousStopwatch stopwatch =
new ContinuousStopwatch(Stopwatch.createUnstarted());
private final Errors errors = new Errors(); private final Errors errors = new Errors();
private final Initializer initializer = new Initializer(); private final Initializer initializer = new Initializer();
private final ProcessedBindingData bindingData; private final ProcessedBindingData processedBindingData;
private final InjectionRequestProcessor injectionRequestProcessor; private final InjectionRequestProcessor injectionRequestProcessor;
private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder(); private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder();
@ -53,7 +57,7 @@ public final class InternalInjectorCreator {
public InternalInjectorCreator() { public InternalInjectorCreator() {
injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer); injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer);
bindingData = new ProcessedBindingData(); processedBindingData = new ProcessedBindingData();
} }
public InternalInjectorCreator stage(Stage stage) { public InternalInjectorCreator stage(Stage stage) {
@ -63,9 +67,10 @@ public final class InternalInjectorCreator {
/** /**
* Sets the parent of the injector to-be-constructed. As a side effect, this sets this injector's * Sets the parent of the injector to-be-constructed. As a side effect, this sets this injector's
* stage to the stage of {@code parent}. * stage to the stage of {@code parent} and sets {@link #requireExplicitBindings()} if the parent
* injector also required them.
*/ */
InternalInjectorCreator parentInjector(InjectorImpl parent) { public InternalInjectorCreator parentInjector(InjectorImpl parent) {
shellBuilder.parent(parent); shellBuilder.parent(parent);
return this; return this;
} }
@ -76,11 +81,14 @@ public final class InternalInjectorCreator {
} }
public Injector build() { public Injector build() {
if (shellBuilder == null) {
throw new AssertionError("Already built, builders are not reusable.");
}
// Synchronize while we're building up the bindings and other injector state. This ensures that // Synchronize while we're building up the bindings and other injector data. This ensures that
// the JIT bindings in the parent injector don't change while we're being built // the JIT bindings in the parent injector don't change while we're being built
synchronized (shellBuilder.lock()) { synchronized (shellBuilder.lock()) {
shells = shellBuilder.build(initializer, bindingData, stopwatch, errors); shells = shellBuilder.build(initializer, processedBindingData, stopwatch, errors);
stopwatch.resetAndLog("Injector construction"); stopwatch.resetAndLog("Injector construction");
initializeStatically(); initializeStatically();
@ -97,44 +105,49 @@ public final class InternalInjectorCreator {
} }
} }
/** /** Initialize and validate everything. */
* Initialize and validate everything.
*/
private void initializeStatically() { private void initializeStatically() {
bindingData.initializeBindings(); processedBindingData.initializeBindings();
stopwatch.resetAndLog("Binding initialization"); stopwatch.resetAndLog("Binding initialization");
for (InjectorShell shell : shells) { for (InjectorShell shell : shells) {
shell.getInjector().index(); shell.getInjector().getBindingData().indexBindingsByType();
} }
stopwatch.resetAndLog("Binding indexing"); stopwatch.resetAndLog("Binding indexing");
injectionRequestProcessor.process(shells); injectionRequestProcessor.process(shells);
stopwatch.resetAndLog("Collecting injection requests"); stopwatch.resetAndLog("Collecting injection requests");
bindingData.runCreationListeners(errors);
processedBindingData.runCreationListeners(errors);
stopwatch.resetAndLog("Binding validation"); stopwatch.resetAndLog("Binding validation");
injectionRequestProcessor.validate(); injectionRequestProcessor.validate();
stopwatch.resetAndLog("Static validation"); stopwatch.resetAndLog("Static validation");
initializer.validateOustandingInjections(errors); initializer.validateOustandingInjections(errors);
stopwatch.resetAndLog("Instance member validation"); stopwatch.resetAndLog("Instance member validation");
new LookupProcessor(errors).process(shells); new LookupProcessor(errors).process(shells);
for (InjectorShell shell : shells) { for (InjectorShell shell : shells) {
((DeferredLookups) shell.getInjector().lookups).initialize(errors); ((DeferredLookups) shell.getInjector().lookups).initialize(errors);
} }
stopwatch.resetAndLog("Provider verification"); stopwatch.resetAndLog("Provider verification");
// This needs to come late since some user bindings rely on requireBinding calls to create // This needs to come late since some user bindings rely on requireBinding calls to create
// jit bindings during the LookupProcessor. // jit bindings during the LookupProcessor.
bindingData.initializeDelayedBindings(); processedBindingData.initializeDelayedBindings();
stopwatch.resetAndLog("Delayed Binding initialization"); stopwatch.resetAndLog("Delayed Binding initialization");
for (InjectorShell shell : shells) { for (InjectorShell shell : shells) {
if (!shell.getElements().isEmpty()) { if (!shell.getElements().isEmpty()) {
throw new AssertionError("Failed to execute " + shell.getElements()); throw new AssertionError("Failed to execute " + shell.getElements());
} }
} }
errors.throwCreationExceptionIfErrorsExist(); errors.throwCreationExceptionIfErrorsExist();
} }
/** /** Returns the injector being constructed. This is not necessarily the root injector. */
* Returns the injector being constructed. This is not necessarily the root injector.
*/
private Injector primaryInjector() { private Injector primaryInjector() {
return shells.get(0).getInjector(); return shells.get(0).getInjector();
} }
@ -166,28 +179,29 @@ public final class InternalInjectorCreator {
* while we're binding these singletons are not be eager. * while we're binding these singletons are not be eager.
*/ */
void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) { void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) {
List<BindingImpl<?>> candidateBindings = new ArrayList<>();
@SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe @SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe
Collection<BindingImpl<?>> bindingsAtThisLevel = Collection<BindingImpl<?>> bindingsAtThisLevel =
(Collection) injector.state.getExplicitBindingsThisLevel().values(); (Collection) injector.getBindingData().getExplicitBindingsThisLevel().values();
List<BindingImpl<?>> candidateBindings = new ArrayList<>(bindingsAtThisLevel); candidateBindings.addAll(bindingsAtThisLevel);
synchronized (injector.state.lock()) { synchronized (injector.getJitBindingData().lock()) {
// jit bindings must be accessed while holding the lock. // jit bindings must be accessed while holding the lock.
candidateBindings.addAll(injector.jitBindings.values()); candidateBindings.addAll(injector.getJitBindingData().getJitBindings().values());
} }
try (InternalContext context = injector.enterContext()) { InternalContext context = injector.enterContext();
try {
for (BindingImpl<?> binding : candidateBindings) { for (BindingImpl<?> binding : candidateBindings) {
if (isEagerSingleton(injector, binding, stage)) { if (isEagerSingleton(injector, binding, stage)) {
Dependency<?> dependency = Dependency.get(binding.getKey()); Dependency<?> dependency = Dependency.get(binding.getKey());
Dependency<?> previous = context.pushDependency(dependency, binding.getSource());
try { try {
binding.getInternalFactory().get(context, dependency, false); binding.getInternalFactory().get(context, dependency, false);
} catch (InternalProvisionException e) { } catch (InternalProvisionException e) {
errors.withSource(dependency).merge(e); errors.withSource(dependency).merge(e);
}
}
}
} finally { } finally {
context.popStateAndSetDependency(previous); context.close();
}
}
}
} }
} }
@ -201,16 +215,14 @@ public final class InternalInjectorCreator {
// bindings. This only applies if the linked binding is not itself scoped. // bindings. This only applies if the linked binding is not itself scoped.
if (binding instanceof LinkedBindingImpl) { if (binding instanceof LinkedBindingImpl) {
Key<?> linkedBinding = ((LinkedBindingImpl<?>) binding).getLinkedKey(); Key<?> linkedBinding = ((LinkedBindingImpl<?>) binding).getLinkedKey();
return binding.getScoping().isNoScope() && return binding.getScoping().isNoScope()
isEagerSingleton(injector, injector.getBinding(linkedBinding), stage); && isEagerSingleton(injector, injector.getBinding(linkedBinding), stage);
} }
return false; return false;
} }
/** /** {@link Injector} exposed to users in {@link Stage#TOOL}. */
* {@link Injector} exposed to users in {@link Stage#TOOL}.
*/
static class ToolStageInjector implements Injector { static class ToolStageInjector implements Injector {
private final Injector delegateInjector; private final Injector delegateInjector;

View file

@ -48,74 +48,89 @@ import java.util.logging.Logger;
* #errorInUserCode} is called with an exception that holds multiple errors (like * #errorInUserCode} is called with an exception that holds multiple errors (like
* ProvisionException). * ProvisionException).
*/ */
@SuppressWarnings("serial")
public final class InternalProvisionException extends Exception { public final class InternalProvisionException extends Exception {
private static final Logger logger = Logger.getLogger(Guice.class.getName()); private static final Logger logger = Logger.getLogger(Guice.class.getName());
private static final Set<Dependency<?>> warnedDependencies = private static final Set<Dependency<?>> warnedDependencies =
Collections.newSetFromMap(new ConcurrentHashMap<>()); Collections.newSetFromMap(new ConcurrentHashMap<Dependency<?>, Boolean>());
public static InternalProvisionException circularDependenciesDisabled(Class<?> expectedType) { public static InternalProvisionException circularDependenciesDisabled(Class<?> expectedType) {
return create( return create(
ErrorId.CIRCULAR_PROXY_DISABLED,
"Found a circular dependency involving %s, and circular dependencies are disabled.", "Found a circular dependency involving %s, and circular dependencies are disabled.",
expectedType); expectedType);
} }
public static InternalProvisionException cannotProxyClass(Class<?> expectedType) { public static InternalProvisionException cannotProxyClass(Class<?> expectedType) {
return create( return create(
ErrorId.CAN_NOT_PROXY_CLASS,
"Tried proxying %s to support a circular dependency, but it is not an interface.", "Tried proxying %s to support a circular dependency, but it is not an interface.",
expectedType); expectedType);
} }
public static InternalProvisionException create(String format, Object... arguments) { public static InternalProvisionException create(
return new InternalProvisionException(Messages.create(format, arguments)); ErrorId errorId, String format, Object... arguments) {
return new InternalProvisionException(Messages.create(errorId, format, arguments));
} }
public static InternalProvisionException errorInUserCode( public static InternalProvisionException errorInUserCode(
Throwable cause, String messageFormat, Object... arguments) { ErrorId errorId, Throwable cause, String messageFormat, Object... arguments) {
Collection<Message> messages = Errors.getMessagesFromThrowable(cause); Collection<Message> messages = Errors.getMessagesFromThrowable(cause);
if (!messages.isEmpty()) { if (!messages.isEmpty()) {
// TODO(lukes): it seems like we are dropping some valuable context here.. // TODO(lukes): it seems like we are dropping some valuable context here..
// consider eliminating this special case // consider eliminating this special case
return new InternalProvisionException(messages); return new InternalProvisionException(messages);
} else { } else {
return new InternalProvisionException(Messages.create(cause, messageFormat, arguments)); return new InternalProvisionException(
Messages.create(errorId, cause, messageFormat, arguments));
} }
} }
public static InternalProvisionException subtypeNotProvided( public static InternalProvisionException subtypeNotProvided(
Class<? extends javax.inject.Provider<?>> providerType, Class<?> type) { Class<? extends javax.inject.Provider<?>> providerType, Class<?> type) {
return create("%s doesn't provide instances of %s.", providerType, type); return create(
ErrorId.SUBTYPE_NOT_PROVIDED, "%s doesn't provide instances of %s.", providerType, type);
} }
public static InternalProvisionException errorInProvider(Throwable cause) { public static InternalProvisionException errorInProvider(Throwable cause) {
return errorInUserCode(cause, "Error in custom provider, %s", cause); return errorInUserCode(ErrorId.ERROR_IN_CUSTOM_PROVIDER, cause, "%s", cause);
} }
public static InternalProvisionException errorInjectingMethod(Throwable cause) { public static InternalProvisionException errorInjectingMethod(Throwable cause) {
return errorInUserCode(cause, "Error injecting method, %s", cause); return errorInUserCode(ErrorId.ERROR_INJECTING_METHOD, cause, "%s", cause);
} }
public static InternalProvisionException errorInjectingConstructor(Throwable cause) { public static InternalProvisionException errorInjectingConstructor(Throwable cause) {
return errorInUserCode(cause, "Error injecting constructor, %s", cause); return errorInUserCode(ErrorId.ERROR_INJECTING_CONSTRUCTOR, cause, "%s", cause);
} }
public static InternalProvisionException errorInUserInjector( public static InternalProvisionException errorInUserInjector(
MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) { MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) {
return errorInUserCode( return errorInUserCode(
cause, "Error injecting %s using %s.%n Reason: %s", type, listener, cause); ErrorId.ERROR_IN_USER_INJECTOR,
cause,
"Error injecting %s using %s.%n Reason: %s",
type,
listener,
cause);
} }
public static InternalProvisionException jitDisabled(Key<?> key) { public static InternalProvisionException jitDisabled(Key<?> key) {
return create("Explicit bindings are required and %s is not explicitly bound.", key); return create(
ErrorId.JIT_DISABLED,
"Explicit bindings are required and %s is not explicitly bound.",
key);
} }
public static InternalProvisionException errorNotifyingInjectionListener( public static InternalProvisionException errorNotifyingInjectionListener(
InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) { InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) {
return errorInUserCode( return errorInUserCode(
cause, "Error notifying InjectionListener %s of %s.%n Reason: %s", listener, type, cause); ErrorId.OTHER,
cause,
"Error notifying InjectionListener %s of %s.%n Reason: %s",
listener,
type,
cause);
} }
/** /**
@ -142,7 +157,8 @@ public final class InternalProvisionException extends Exception {
+ " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ " error.", + " error.",
new Object[] { new Object[] {
Messages.formatParameter(dependency), Messages.convert(dependency.getKey()) SourceFormatter.getParameterName(dependency),
Messages.convert(dependency.getKey())
}); });
} }
return; return;
@ -150,20 +166,26 @@ public final class InternalProvisionException extends Exception {
} }
} }
String parameterName =
(dependency.getParameterIndex() != -1) ? SourceFormatter.getParameterName(dependency) : "";
Object memberStackTraceElement =
StackTraceElements.forMember(dependency.getInjectionPoint().getMember());
Object formattedDependency = Object formattedDependency =
(dependency.getParameterIndex() != -1) parameterName.isEmpty()
? Messages.formatParameter(dependency) ? memberStackTraceElement
: StackTraceElements.forMember(dependency.getInjectionPoint().getMember()); : "the " + parameterName + " of " + memberStackTraceElement;
throw InternalProvisionException.create( throw InternalProvisionException.create(
"null returned by binding at %s%n but %s is not @Nullable", source, formattedDependency) ErrorId.NULL_INJECTED_INTO_NON_NULLABLE,
"null returned by binding at %s%n but %s is not @Nullable",
source,
formattedDependency)
.addSource(source); .addSource(source);
} }
private final List<Object> sourcesToPrepend = new ArrayList<>(); private final List<Object> sourcesToPrepend = new ArrayList<>();
private final ImmutableList<Message> errors; private final ImmutableList<Message> errors;
private InternalProvisionException(Message error) { InternalProvisionException(Message error) {
this(ImmutableList.of(error)); this(ImmutableList.of(error));
} }
@ -211,6 +233,7 @@ public final class InternalProvisionException extends Exception {
/** Returns this exception convered to a ProvisionException. */ /** Returns this exception convered to a ProvisionException. */
public ProvisionException toProvisionException() { public ProvisionException toProvisionException() {
return new ProvisionException(getErrors()); ProvisionException exception = new ProvisionException(getErrors());
return exception;
} }
} }

View file

@ -14,13 +14,13 @@ final class ListenerBindingProcessor extends AbstractProcessor {
@Override @Override
public Boolean visit(TypeListenerBinding binding) { public Boolean visit(TypeListenerBinding binding) {
injector.state.addTypeListener(binding); injector.getBindingData().addTypeListener(binding);
return true; return true;
} }
@Override @Override
public Boolean visit(ProvisionListenerBinding binding) { public Boolean visit(ProvisionListenerBinding binding) {
injector.state.addProvisionListener(binding); injector.getBindingData().addProvisionListener(binding);
return true; return true;
} }
} }

View file

@ -1,39 +1,30 @@
package com.google.inject.internal; package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkArgument; import static java.util.stream.Collectors.joining;
import com.google.common.base.Equivalence; import com.google.common.base.Equivalence;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Classes; import com.google.inject.internal.util.Classes;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ElementSource; import com.google.inject.spi.ElementSource;
import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.ErrorDetail;
import com.google.inject.spi.Message; import com.google.inject.spi.Message;
import java.lang.reflect.Field;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Formatter; import java.util.Formatter;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
/** /** Utility methods for {@link Message} objects */
* Utility methods for {@link Message} objects
*/
public final class Messages { public final class Messages {
private Messages() {}
private Messages() { /** Prepends the list of sources to the given {@link Message} */
}
/**
* Prepends the list of sources to the given {@link Message}
*/
static Message mergeSources(List<Object> sources, Message message) { static Message mergeSources(List<Object> sources, Message message) {
List<Object> messageSources = message.getSources(); List<Object> messageSources = message.getSources();
// It is possible that the end of getSources() and the beginning of message.getSources() are // It is possible that the end of getSources() and the beginning of message.getSources() are
@ -41,12 +32,13 @@ public final class Messages {
// most likely scenario where this would happen is when a scoped binding throws an exception, // most likely scenario where this would happen is when a scoped binding throws an exception,
// due to the fact that InternalFactoryToProviderAdapter applies the binding source when // due to the fact that InternalFactoryToProviderAdapter applies the binding source when
// merging errors. // merging errors.
if (!sources.isEmpty() && !messageSources.isEmpty() && if (!sources.isEmpty()
Objects.equal(messageSources.get(0), sources.get(sources.size() - 1))) { && !messageSources.isEmpty()
&& Objects.equal(messageSources.get(0), sources.get(sources.size() - 1))) {
messageSources = messageSources.subList(1, messageSources.size()); messageSources = messageSources.subList(1, messageSources.size());
} }
return new Message(ImmutableList.builder().addAll(sources).addAll(messageSources).build(), return message.withSource(
message.getMessage(), message.getCause()); ImmutableList.builder().addAll(sources).addAll(messageSources).build());
} }
/** /**
@ -60,30 +52,33 @@ public final class Messages {
return String.format(messageFormat, arguments); return String.format(messageFormat, arguments);
} }
/** /** Returns the formatted message for an exception with the specified messages. */
* Returns the formatted message for an exception with the specified messages.
*/
public static String formatMessages(String heading, Collection<Message> errorMessages) { public static String formatMessages(String heading, Collection<Message> errorMessages) {
Formatter fmt = new Formatter().format(heading).format(":%n%n"); Formatter fmt = new Formatter().format(heading).format(":%n%n");
int index = 1; int index = 1;
boolean displayCauses = getOnlyCause(errorMessages) == null; boolean displayCauses = getOnlyCause(errorMessages) == null;
List<ErrorDetail<?>> remainingErrors =
errorMessages.stream().map(Message::getErrorDetail).collect(Collectors.toList());
Map<Equivalence.Wrapper<Throwable>, Integer> causes = Maps.newHashMap(); Map<Equivalence.Wrapper<Throwable>, Integer> causes = Maps.newHashMap();
for (Message errorMessage : errorMessages) { while (!remainingErrors.isEmpty()) {
int thisIdx = index++; ErrorDetail<?> currentError = remainingErrors.get(0);
fmt.format("%s) %s%n", thisIdx, errorMessage.getMessage()); // Split the remaining errors into 2 groups, one that contains mergeable errors with
// currentError and the other that need to be formatted separately in the next iteration.
Map<Boolean, List<ErrorDetail<?>>> partitionedByMergeable =
remainingErrors.subList(1, remainingErrors.size()).stream()
.collect(Collectors.partitioningBy(currentError::isMergeable));
List<Object> dependencies = errorMessage.getSources(); remainingErrors = partitionedByMergeable.get(false);
for (int i = dependencies.size() - 1; i >= 0; i--) {
Object source = dependencies.get(i);
formatSource(fmt, source);
}
Throwable cause = errorMessage.getCause(); currentError.format(index, partitionedByMergeable.get(true), fmt);
Throwable cause = currentError.getCause();
if (displayCauses && cause != null) { if (displayCauses && cause != null) {
Equivalence.Wrapper<Throwable> causeEquivalence = ThrowableEquivalence.INSTANCE.wrap(cause); Equivalence.Wrapper<Throwable> causeEquivalence = ThrowableEquivalence.INSTANCE.wrap(cause);
if (!causes.containsKey(causeEquivalence)) { if (!causes.containsKey(causeEquivalence)) {
causes.put(causeEquivalence, thisIdx); causes.put(causeEquivalence, index);
fmt.format("Caused by: %s", Throwables.getStackTraceAsString(cause)); fmt.format("Caused by: %s", Throwables.getStackTraceAsString(cause));
} else { } else {
int causeIdx = causes.get(causeEquivalence); int causeIdx = causes.get(causeEquivalence);
@ -92,57 +87,64 @@ public final class Messages {
cause.getClass().getName(), causeIdx); cause.getClass().getName(), causeIdx);
} }
} }
fmt.format("%n"); fmt.format("%n");
index++;
} }
if (errorMessages.size() == 1) { if (index == 2) {
fmt.format("1 error"); fmt.format("1 error");
} else { } else {
fmt.format("%s errors", errorMessages.size()); fmt.format("%s errors", index - 1);
} }
return fmt.toString(); return PackageNameCompressor.compressPackagesInMessage(fmt.toString());
} }
/** /**
* Creates a new Message without a cause. * Creates a new Message without a cause.
* *
* @param errorId The enum id for the error
* @param messageFormat Format string * @param messageFormat Format string
* @param arguments format string arguments * @param arguments format string arguments
*/ */
public static Message create(String messageFormat, Object... arguments) { public static Message create(ErrorId errorId, String messageFormat, Object... arguments) {
return create(null, messageFormat, arguments); return create(errorId, null, messageFormat, arguments);
} }
/** /**
* Creates a new Message with the given cause. * Creates a new Message with the given cause.
* *
* @param errorId The enum id for the error
* @param cause The exception that caused the error * @param cause The exception that caused the error
* @param messageFormat Format string * @param messageFormat Format string
* @param arguments format string arguments * @param arguments format string arguments
*/ */
public static Message create(Throwable cause, String messageFormat, Object... arguments) { public static Message create(
return create(cause, ImmutableList.of(), messageFormat, arguments); ErrorId errorId, Throwable cause, String messageFormat, Object... arguments) {
return create(errorId, cause, ImmutableList.of(), messageFormat, arguments);
} }
/** /**
* Creates a new Message with the given cause and a binding source stack. * Creates a new Message with the given cause and a binding source stack.
* *
* @param errorId The enum id for the error
* @param cause The exception that caused the error * @param cause The exception that caused the error
* @param sources The binding sources for the source stack * @param sources The binding sources for the source stack
* @param messageFormat Format string * @param messageFormat Format string
* @param arguments format string arguments * @param arguments format string arguments
*/ */
public static Message create(Throwable cause, List<Object> sources, String messageFormat, Object... arguments) { public static Message create(
ErrorId errorId,
Throwable cause,
List<Object> sources,
String messageFormat,
Object... arguments) {
String message = format(messageFormat, arguments); String message = format(messageFormat, arguments);
return new Message(sources, message, cause); return new Message(errorId, sources, message, cause);
} }
/** /** Formats an object in a user friendly way. */
* Formats an object in a user friendly way. static Object convert(Object o) {
*/
public static Object convert(Object o) {
ElementSource source = null; ElementSource source = null;
if (o instanceof ElementSource) { if (o instanceof ElementSource) {
source = (ElementSource) o; source = (ElementSource) o;
@ -151,7 +153,7 @@ public final class Messages {
return convert(o, source); return convert(o, source);
} }
public static Object convert(Object o, ElementSource source) { static Object convert(Object o, ElementSource source) {
for (Converter<?> converter : converters) { for (Converter<?> converter : converters) {
if (converter.appliesTo(o)) { if (converter.appliesTo(o)) {
return appendModules(converter.convert(o), source); return appendModules(converter.convert(o), source);
@ -161,145 +163,11 @@ public final class Messages {
} }
private static Object appendModules(Object source, ElementSource elementSource) { private static Object appendModules(Object source, ElementSource elementSource) {
String modules = moduleSourceString(elementSource); String modules = SourceFormatter.getModuleStack(elementSource);
if (modules.length() == 0) { if (modules.length() == 0) {
return source; return source;
} else { } else {
return source + modules; return source + " (installed by: " + modules + ")";
}
}
private static String moduleSourceString(ElementSource elementSource) {
// if we only have one module (or don't know what they are), then don't bother
// reporting it, because the source already is going to report exactly that module.
if (elementSource == null) {
return "";
}
List<String> modules = Lists.newArrayList(elementSource.getModuleClassNames());
// Insert any original element sources w/ module info into the path.
while (elementSource.getOriginalElementSource() != null) {
elementSource = elementSource.getOriginalElementSource();
modules.addAll(0, elementSource.getModuleClassNames());
}
if (modules.size() <= 1) {
return "";
}
// Ideally we'd do:
// return Joiner.on(" -> ")
// .appendTo(new StringBuilder(" (via modules: "), Lists.reverse(modules))
// .append(")").toString();
// ... but for some reason we can't find Lists.reverse, so do it the boring way.
StringBuilder builder = new StringBuilder(" (via modules: ");
for (int i = modules.size() - 1; i >= 0; i--) {
builder.append(modules.get(i));
if (i != 0) {
builder.append(" -> ");
}
}
builder.append(")");
return builder.toString();
}
static void formatSource(Formatter formatter, Object source) {
ElementSource elementSource = null;
if (source instanceof ElementSource) {
elementSource = (ElementSource) source;
source = elementSource.getDeclaringSource();
}
formatSource(formatter, source, elementSource);
}
static void formatSource(Formatter formatter, Object source, ElementSource elementSource) {
String modules = moduleSourceString(elementSource);
if (source instanceof Dependency) {
Dependency<?> dependency = (Dependency<?>) source;
InjectionPoint injectionPoint = dependency.getInjectionPoint();
if (injectionPoint != null) {
formatInjectionPoint(formatter, dependency, injectionPoint, elementSource);
} else {
formatSource(formatter, dependency.getKey(), elementSource);
}
} else if (source instanceof InjectionPoint) {
formatInjectionPoint(formatter, null, (InjectionPoint) source, elementSource);
} else if (source instanceof Class) {
formatter.format(" at %s%s%n", StackTraceElements.forType((Class<?>) source), modules);
} else if (source instanceof Member) {
formatter.format(" at %s%s%n", StackTraceElements.forMember((Member) source), modules);
} else if (source instanceof TypeLiteral) {
formatter.format(" while locating %s%s%n", source, modules);
} else if (source instanceof Key) {
Key<?> key = (Key<?>) source;
formatter.format(" while locating %s%n", convert(key, elementSource));
} else if (source instanceof Thread) {
formatter.format(" in thread %s%n", source);
} else {
formatter.format(" at %s%s%n", source, modules);
}
}
private static void formatInjectionPoint(
Formatter formatter,
Dependency<?> dependency,
InjectionPoint injectionPoint,
ElementSource elementSource) {
Member member = injectionPoint.getMember();
Class<? extends Member> memberType = Classes.memberType(member);
if (memberType == Field.class) {
dependency = injectionPoint.getDependencies().get(0);
formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource));
formatter.format(" for field at %s%n", StackTraceElements.forMember(member));
} else if (dependency != null) {
formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource));
formatter.format(" for %s%n", formatParameter(dependency));
} else {
formatSource(formatter, injectionPoint.getMember());
}
}
static String formatParameter(Dependency<?> dependency) {
int ordinal = dependency.getParameterIndex() + 1;
return String.format(
"the %s%s parameter of %s",
ordinal,
getOrdinalSuffix(ordinal),
StackTraceElements.forMember(dependency.getInjectionPoint().getMember()));
}
/**
* Maps {@code 1} to the string {@code "1st"} ditto for all non-negative numbers
*
* @see <a href="https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers">
* https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers</a>
*/
private static String getOrdinalSuffix(int ordinal) {
// negative ordinals don't make sense, we allow zero though because we are programmers
checkArgument(ordinal >= 0);
if ((ordinal / 10) % 10 == 1) {
// all the 'teens' are weird
return "th";
} else {
// could use a lookup table? any better?
switch (ordinal % 10) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
} }
} }
@ -322,22 +190,22 @@ public final class Messages {
abstract String toString(T t); abstract String toString(T t);
} }
@SuppressWarnings({"rawtypes"}) // rawtypes aren't avoidable @SuppressWarnings({"unchecked", "rawtypes"}) // rawtypes aren't avoidable
private static final Collection<Converter<?>> converters = private static final Collection<Converter<?>> converters =
ImmutableList.of( ImmutableList.of(
new Converter<>(Class.class) { new Converter<Class>(Class.class) {
@Override @Override
public String toString(Class c) { public String toString(Class c) {
return c.getName(); return c.getName();
} }
}, },
new Converter<>(Member.class) { new Converter<Member>(Member.class) {
@Override @Override
public String toString(Member member) { public String toString(Member member) {
return Classes.toString(member); return Classes.toString(member);
} }
}, },
new Converter<>(Key.class) { new Converter<Key>(Key.class) {
@Override @Override
public String toString(Key key) { public String toString(Key key) {
if (key.getAnnotationType() != null) { if (key.getAnnotationType() != null) {
@ -388,4 +256,46 @@ public final class Messages {
return Objects.hashCode(t.getClass().hashCode(), t.getMessage(), hash(t.getCause())); return Objects.hashCode(t.getClass().hashCode(), t.getMessage(), hash(t.getCause()));
} }
} }
private enum FormatOptions {
RED("\u001B[31m"),
BOLD("\u001B[1m"),
FAINT("\u001B[2m"),
ITALIC("\u001B[3m"),
UNDERLINE("\u001B[4m"),
RESET("\u001B[0m");
private final String ansiCode;
FormatOptions(String ansiCode) {
this.ansiCode = ansiCode;
}
}
private static final String formatText(String text, FormatOptions... options) {
if (!InternalFlags.enableColorizeErrorMessages()) {
return text;
}
return String.format(
"%s%s%s",
Arrays.stream(options).map(option -> option.ansiCode).collect(joining()),
text,
FormatOptions.RESET.ansiCode);
}
public static final String bold(String text) {
return formatText(text, FormatOptions.BOLD);
}
public static final String redBold(String text) {
return formatText(text, FormatOptions.RED, FormatOptions.BOLD);
}
public static final String underline(String text) {
return formatText(text, FormatOptions.UNDERLINE);
}
public static final String faint(String text) {
return formatText(text, FormatOptions.FAINT);
}
} }

View file

@ -0,0 +1,82 @@
package com.google.inject.internal;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.ErrorDetail;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
/** Error reported when Guice can't find an useable constructor to create objects. */
final class MissingConstructorError extends InternalErrorDetail<MissingConstructorError> {
private final TypeLiteral<?> type;
private final boolean atInjectRequired;
MissingConstructorError(TypeLiteral<?> type, boolean atInjectRequired, List<Object> sources) {
super(
ErrorId.MISSING_CONSTRUCTOR,
"No injectable constructor for type " + type + ".",
sources,
null);
this.type = type;
this.atInjectRequired = atInjectRequired;
}
@Override
public boolean isMergeable(ErrorDetail<?> other) {
if (other instanceof MissingConstructorError) {
MissingConstructorError otherMissing = (MissingConstructorError) other;
return Objects.equal(type, otherMissing.type)
&& Objects.equal(atInjectRequired, otherMissing.atInjectRequired);
}
return false;
}
@Override
protected void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
formatter.format("%n");
Class<?> rawType = type.getRawType();
if (atInjectRequired) {
formatter.format(
"Injector is configured to require @Inject constructors but %s does not have a @Inject"
+ " annotated constructor.%n",
rawType);
} else {
Constructor<?> noArgConstructor = null;
try {
noArgConstructor = type.getRawType().getDeclaredConstructor();
} catch (NoSuchMethodException e) {
// Ignore
}
if (noArgConstructor == null) {
formatter.format(
"%s does not have a @Inject annotated constructor or a no-arg constructor.%n", rawType);
} else if (Modifier.isPrivate(noArgConstructor.getModifiers())
&& !Modifier.isPrivate(rawType.getModifiers())) {
formatter.format(
"%s has a private no-arg constructor but the class is not private. Guice can only use"
+ " a private no-arg constructor if it is defined in a private class.%n",
rawType);
}
}
formatter.format("%n");
List<List<Object>> sourcesList = new ArrayList<>();
sourcesList.add(getSources());
mergeableErrors.forEach(error -> sourcesList.add(error.getSources()));
formatter.format("%s%n", Messages.bold("Requested by:"));
int sourceListIndex = 1;
for (List<Object> sources : sourcesList) {
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
}
}
@Override
public MissingConstructorError withSources(List<Object> newSources) {
return new MissingConstructorError(type, atInjectRequired, newSources);
}
}

View file

@ -0,0 +1,75 @@
package com.google.inject.internal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.spi.ErrorDetail;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.stream.Collectors;
/** Error reported by Guice when a key is not bound in the injector. */
final class MissingImplementationError<T>
extends InternalErrorDetail<MissingImplementationError<T>> {
private final Key<T> key;
private final ImmutableList<String> suggestions;
public MissingImplementationError(Key<T> key, Injector injector, List<Object> sources) {
this(key, MissingImplementationErrorHints.getSuggestions(key, injector), sources);
}
private MissingImplementationError(
Key<T> key, ImmutableList<String> suggestions, List<Object> sources) {
super(
ErrorId.MISSING_IMPLEMENTATION,
String.format("No implementation for %s was bound.", Messages.convert(key)),
sources,
null);
this.key = key;
this.suggestions = suggestions;
}
@Override
public boolean isMergeable(ErrorDetail<?> otherError) {
return otherError instanceof MissingImplementationError
&& ((MissingImplementationError) otherError).key.equals(this.key);
}
@Override
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
if (!suggestions.isEmpty()) {
suggestions.forEach(formatter::format);
}
List<List<Object>> sourcesList = new ArrayList<>();
sourcesList.add(getSources());
sourcesList.addAll(
mergeableErrors.stream().map(ErrorDetail::getSources).collect(Collectors.toList()));
List<List<Object>> filteredSourcesList =
sourcesList.stream()
.map(this::trimSource)
.filter(sources -> !sources.isEmpty())
.collect(Collectors.toList());
if (!filteredSourcesList.isEmpty()) {
formatter.format("%n%s%n", Messages.bold("Requested by:"));
int sourceListIndex = 1;
for (List<Object> sources : filteredSourcesList) {
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
}
}
}
@Override
public MissingImplementationError<T> withSources(List<Object> newSources) {
return new MissingImplementationError<T>(key, suggestions, newSources);
}
/** Omit the key itself in the source list since the information is redundant. */
private List<Object> trimSource(List<Object> sources) {
return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList());
}
}

View file

@ -0,0 +1,113 @@
package com.google.inject.internal;
import static java.lang.Math.min;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Primitives;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingSourceRestriction;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
// TODO(b/165344346): Migrate to use suggest hints API.
/** Helper class to find hints for {@link MissingImplementationError}. */
final class MissingImplementationErrorHints {
private MissingImplementationErrorHints() {}
/** 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;
/**
* If the key is unknown and it is one of these types, it generally means there is a missing
* annotation.
*/
private static final ImmutableSet<Class<?>> COMMON_AMBIGUOUS_TYPES =
ImmutableSet.<Class<?>>builder()
.add(Object.class)
.add(String.class)
.addAll(Primitives.allWrapperTypes())
.build();
static <T> ImmutableList<String> getSuggestions(Key<T> key, Injector injector) {
ImmutableList.Builder<String> suggestions = ImmutableList.builder();
TypeLiteral<T> type = key.getTypeLiteral();
BindingSourceRestriction.getMissingImplementationSuggestion(GuiceInternal.GUICE_INTERNAL, key)
.ifPresent(suggestions::add);
// Keys which have similar strings as the desired key
List<String> possibleMatches = new ArrayList<>();
List<Binding<T>> sameTypes = injector.findBindingsByType(type);
if (!sameTypes.isEmpty()) {
suggestions.add("%nDid you mean?");
int howMany = 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.
suggestions.add(Messages.format("%n * %s", sameTypes.get(i).getKey()));
}
int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED;
if (remaining > 0) {
String plural = (remaining == 1) ? "" : "s";
suggestions.add(
Messages.format("%n %d more binding%s with other annotations.", remaining, plural));
}
suggestions.add("%n");
} 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<Key<?>, 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();
fmt.format("%s bound ", Messages.convert(bindingKey));
new SourceFormatter(
bindingMap.get(bindingKey).getSource(), fmt, /* omitPreposition= */ false)
.format();
possibleMatches.add(fmt.toString());
// 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.isEmpty() && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) {
suggestions.add("%nDid you mean?");
for (String possibleMatch : possibleMatches) {
suggestions.add(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.
suggestions.add("%nThe key seems very generic, did you forget an annotation?");
}
return suggestions.build();
}
}

View file

@ -4,6 +4,7 @@ import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding;
/** /**
* Handles {@code Binder.scanModulesForAnnotatedMethods} commands. * Handles {@code Binder.scanModulesForAnnotatedMethods} commands.
*
*/ */
final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor { final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor {
@ -13,7 +14,7 @@ final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor {
@Override @Override
public Boolean visit(ModuleAnnotatedMethodScannerBinding command) { public Boolean visit(ModuleAnnotatedMethodScannerBinding command) {
injector.state.addScanner(command); injector.getBindingData().addScanner(command);
return true; return true;
} }
} }

View file

@ -0,0 +1,237 @@
package com.google.inject.internal;
import static java.util.Comparator.comparing;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Munges an error message to remove/shorten package names and adds a legend at the end.
*/
final class PackageNameCompressor {
static final String LEGEND_HEADER =
"\n\n======================\nFull classname legend:\n======================\n";
static final String LEGEND_FOOTER =
"========================\nEnd of classname legend:\n========================\n";
private static final ImmutableSet<String> PACKAGES_SKIPPED_IN_LEGEND = ImmutableSet.of(
"java.lang.",
"java.util.");
private static final Splitter PACKAGE_SPLITTER = Splitter.on('.');
private static final Joiner PACKAGE_JOINER = Joiner.on('.');
// TODO(erichang): Consider validating this regex by also passing in all of the known types from
// keys, module names, component names, etc and checking against that list. This may have some
// extra complications with taking apart types like List<Foo> to get the inner class names.
private static final Pattern CLASSNAME_PATTERN =
// Match lowercase package names with trailing dots. Start with a non-word character so we
// don't match substrings in like Bar.Foo and match the com.foo.Foo. Require at least 2
// package names to avoid matching non package names like a sentence ending with a period and
// starting with an upper case letter without space, for example:
// foo.Must in message "Invalid value for foo.Must not be empty." should not be compressed.
// Start a group to not include the non-word character.
Pattern.compile(
"[\\W](([a-z_0-9]++[.]){2,}+"
// Then match a name starting with an uppercase letter. This is the outer class name.
+ "[A-Z][\\w$]*)");
// Pattern used to filter out quoted strings that should not have their package name compressed.
// Picked '"' here because Guice uses it when including a string literal in an error message. This
// will allow user to include class names in the error message and disable the compressor by
// putting the name in a pair of '"'.
// The pattern without the escapes: ([^"]+)((")?[^"\r\n]*")?
// First group captures non quoted strings
// Second group captures either a single quote or a string with a pair of quotes within a line
// Class names in second group will not be compressed.
private static final Pattern QUOTED_PATTERN =
Pattern.compile("([^\\\"]+)((\\\")?[^\\\"\\r\\n]*\\\")?");
/**
* Compresses an error message by stripping the packages out of class names and adding them
* to a legend at the bottom of the error.
*/
static String compressPackagesInMessage(String input) {
Matcher matcher = CLASSNAME_PATTERN.matcher(input);
Set<String> names = new HashSet<>();
// Find all classnames in the error. Note that if our regex isn't complete, it just means the
// classname is left in the full form, which is a fine fallback.
while (matcher.find()) {
String name = matcher.group(1);
names.add(name);
}
// Now dedupe any conflicts. Use a TreeMap since we're going to need the legend sorted anyway.
// This map is from short name to full name.
Map<String, String> replacementMap = shortenNames(names);
// If we have nothing to replace, just return the original.
if (replacementMap.isEmpty()) {
return input;
}
StringBuilder output = new StringBuilder();
Set<String> replacedShortNames = replaceFullNames(input, replacementMap, output);
if (replacedShortNames.isEmpty()) {
return input;
}
String classNameLegend =
buildClassNameLegend(Maps.filterKeys(replacementMap, replacedShortNames::contains));
return output.append(classNameLegend).toString();
}
/**
* Replaces full class names in {@code input} and append the replaced content to {@code output}
* and then returns a set of short names that were used as replacement.
*
* <p>String literals that are quoted in the {@code input} will be added to the {@code output}
* unchanged. So any full class name that only appear in the string literal will not be included
* in the returned short names set.
*/
private static ImmutableSet<String> replaceFullNames(
String input, Map<String, String> replacementMap, StringBuilder output) {
ImmutableSet.Builder<String> replacedShortNames = ImmutableSet.builder();
// Sort short names in reverse alphabetical order. This is necessary so that a short name that
// has a prefix that is another short name will be replaced first, otherwise the longer name
// will not be collected as one of the replacedShortNames.
List<String> shortNames =
replacementMap.keySet().stream()
.sorted(Ordering.natural().reverse())
.collect(Collectors.toList());
Matcher matcher = QUOTED_PATTERN.matcher(input);
while (matcher.find()) {
String replaced = matcher.group(1);
for (String shortName : shortNames) {
String fullName = replacementMap.get(shortName);
int beforeLen = replaced.length();
replaced = replaced.replace(fullName, shortName);
// If the replacement happened then put the short name in replacedShortNames.
// Only values in replacedShortNames are included in the full class name legend.
if (replaced.length() < beforeLen) {
replacedShortNames.add(shortName);
}
}
output.append(replaced);
String quoted = matcher.group(2);
if (quoted != null) {
output.append(quoted);
}
}
return replacedShortNames.build();
}
private static String buildClassNameLegend(Map<String, String> replacementMap) {
StringBuilder legendBuilder = new StringBuilder();
// Find the longest key for building the legend
int longestKey = replacementMap.keySet().stream().max(comparing(String::length)).get().length();
for (Map.Entry<String, String> entry : replacementMap.entrySet()) {
String shortName = entry.getKey();
String fullName = entry.getValue();
// Skip certain prefixes. We need to check the shortName for a . though in case
// there was some type of conflict like java.util.concurrent.Future and
// java.util.foo.Future that got shortened to concurrent.Future and foo.Future.
// In those cases we do not want to skip the legend. We only skip if the class
// is directly in that package.
String prefix = fullName.substring(0, fullName.length() - shortName.length());
if (PACKAGES_SKIPPED_IN_LEGEND.contains(prefix) && !shortName.contains(".")) {
continue;
}
// Add to the legend
legendBuilder
.append(shortName)
.append(": ")
// Add enough spaces to adjust the columns
.append(Strings.repeat(" ", longestKey - shortName.length()))
// Surround the full class name with quotes to avoid them getting compressed again if
// the error is wrapped inside another Guice error.
.append('"')
.append(fullName)
.append('"')
.append("\n");
}
return legendBuilder.length() == 0
? ""
: Messages.bold(LEGEND_HEADER)
+ Messages.faint(legendBuilder.toString())
+ Messages.bold(LEGEND_FOOTER);
}
/**
* Returns a map from short name to full name after resolving conflicts. This resolves conflicts
* by adding on segments of the package name until they are unique. For example, com.foo.Baz and
* com.bar.Baz will conflict on Baz and then resolve with foo.Baz and bar.Baz as replacements.
*/
private static Map<String, String> shortenNames(Collection<String> names) {
HashMultimap<String, List<String>> shortNameToPartsMap = HashMultimap.create();
for (String name : names) {
List<String> parts = new ArrayList<>(PACKAGE_SPLITTER.splitToList(name));
// Start with the just the class name as the simple name
String className = parts.remove(parts.size() - 1);
shortNameToPartsMap.put(className, parts);
}
// Iterate through looking for conflicts adding the next part of the package until there are no
// more conflicts
while (true) {
// Save the keys with conflicts to avoid concurrent modification issues
List<String> conflictingShortNames = new ArrayList<>();
for (Map.Entry<String, Collection<List<String>>> entry
: shortNameToPartsMap.asMap().entrySet()) {
if (entry.getValue().size() > 1) {
conflictingShortNames.add(entry.getKey());
}
}
if (conflictingShortNames.isEmpty()) {
break;
}
// For all conflicts, add in the next part of the package
for (String conflictingShortName : conflictingShortNames) {
Set<List<String>> partsCollection = shortNameToPartsMap.removeAll(conflictingShortName);
for (List<String> parts : partsCollection) {
String newShortName = parts.remove(parts.size() - 1) + "." + conflictingShortName;
// If we've removed the last part of the package, then just skip it entirely because
// now we're not shortening it at all.
if (!parts.isEmpty()) {
shortNameToPartsMap.put(newShortName, parts);
}
}
}
}
// Turn the multimap into a regular map now that conflicts have been resolved. Use a TreeMap
// since we're going to need the legend sorted anyway. This map is from short name to full name.
Map<String, String> replacementMap = new TreeMap<>();
for (Map.Entry<String, Collection<List<String>>> entry
: shortNameToPartsMap.asMap().entrySet()) {
replacementMap.put(
entry.getKey(),
PACKAGE_JOINER.join(Iterables.getOnlyElement(entry.getValue())) + "." + entry.getKey());
}
return replacementMap;
}
private PackageNameCompressor() {}
}

View file

@ -1,5 +1,9 @@
package com.google.inject.internal; package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -13,7 +17,6 @@ import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.Message; import com.google.inject.spi.Message;
import com.google.inject.spi.ModuleAnnotatedMethodScanner; import com.google.inject.spi.ModuleAnnotatedMethodScanner;
import com.google.inject.util.Modules; import com.google.inject.util.Modules;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -22,49 +25,38 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/** /**
* Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and * Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and
* binding annotations on the provider method to configure the binding. * binding annotations on the provider method to configure the binding.
* *
*/ */
public final class ProviderMethodsModule implements Module { public final class ProviderMethodsModule implements Module {
private final Object delegate; private final Object delegate;
private final TypeLiteral<?> typeLiteral; private final TypeLiteral<?> typeLiteral;
private final boolean skipFastClassGeneration; private final boolean skipFastClassGeneration;
private final ModuleAnnotatedMethodScanner scanner; private final ModuleAnnotatedMethodScanner scanner;
private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration, private ProviderMethodsModule(
ModuleAnnotatedMethodScanner scanner) { Object delegate, boolean skipFastClassGeneration, ModuleAnnotatedMethodScanner scanner) {
this.delegate = checkNotNull(delegate, "delegate"); this.delegate = checkNotNull(delegate, "delegate");
this.typeLiteral = TypeLiteral.get(this.delegate.getClass()); this.typeLiteral = TypeLiteral.get(getDelegateModuleClass());
this.skipFastClassGeneration = skipFastClassGeneration; this.skipFastClassGeneration = skipFastClassGeneration;
this.scanner = scanner; this.scanner = scanner;
} }
/** /** Returns a module which creates bindings for provider methods from the given module. */
* Returns a module which creates bindings for provider methods from the given module.
*/
public static Module forModule(Module module) { public static Module forModule(Module module) {
return forObject(module, false, ProvidesMethodScanner.INSTANCE); return forObject(module, false, ProvidesMethodScanner.INSTANCE);
} }
/** /** Returns a module which creates bindings methods in the module that match the scanner. */
* Returns a module which creates bindings methods in the module that match the scanner.
*/
public static Module forModule(Object module, ModuleAnnotatedMethodScanner scanner) { public static Module forModule(Object module, ModuleAnnotatedMethodScanner scanner) {
return forObject(module, false, scanner); return forObject(module, false, scanner);
} }
/** /**
* Returns a module which creates bindings for provider methods from the given object. * Returns a module which creates bindings for provider methods from the given object. This is
* This is useful notably for <a href="http://code.google.com/p/google-gin/">GIN</a> * useful notably for <a href="http://code.google.com/p/google-gin/">GIN</a>
* *
* <p>This will skip bytecode generation for provider methods, since it is assumed that callers * <p>This will skip bytecode generation for provider methods, since it is assumed that callers
* are only interested in Module metadata. * are only interested in Module metadata.
@ -73,12 +65,13 @@ public final class ProviderMethodsModule implements Module {
return forObject(object, true, ProvidesMethodScanner.INSTANCE); return forObject(object, true, ProvidesMethodScanner.INSTANCE);
} }
private static Module forObject(Object object, boolean skipFastClassGeneration, private static Module forObject(
ModuleAnnotatedMethodScanner scanner) { Object object, boolean skipFastClassGeneration, ModuleAnnotatedMethodScanner scanner) {
// avoid infinite recursion, since installing a module always installs itself // avoid infinite recursion, since installing a module always installs itself
if (object instanceof ProviderMethodsModule) { if (object instanceof ProviderMethodsModule) {
return Modules.EMPTY_MODULE; return Modules.EMPTY_MODULE;
} }
return new ProviderMethodsModule(object, skipFastClassGeneration, scanner); return new ProviderMethodsModule(object, skipFastClassGeneration, scanner);
} }
@ -86,6 +79,10 @@ public final class ProviderMethodsModule implements Module {
return isStaticModule() ? (Class<?>) delegate : delegate.getClass(); return isStaticModule() ? (Class<?>) delegate : delegate.getClass();
} }
private boolean isStaticModule() {
return delegate instanceof Class;
}
@Override @Override
public void configure(Binder binder) { public void configure(Binder binder) {
for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) { for (ProviderMethod<?> providerMethod : getProviderMethods(binder)) {
@ -93,14 +90,6 @@ public final class ProviderMethodsModule implements Module {
} }
} }
private boolean isStaticModule() {
return delegate instanceof Class;
}
public Object getDelegateModule() {
return delegate;
}
public List<ProviderMethod<?>> getProviderMethods(Binder binder) { public List<ProviderMethod<?>> getProviderMethods(Binder binder) {
List<ProviderMethod<?>> result = null; List<ProviderMethod<?>> result = null;
List<MethodAndAnnotation> methodsAndAnnotations = null; List<MethodAndAnnotation> methodsAndAnnotations = null;
@ -196,6 +185,16 @@ public final class ProviderMethodsModule implements Module {
return result; return result;
} }
private static class MethodAndAnnotation {
final Method method;
final Annotation annotation;
MethodAndAnnotation(Method method, Annotation annotation) {
this.method = method;
this.annotation = annotation;
}
}
/** Returns the annotation that is claimed by the scanner, or null if there is none. */ /** Returns the annotation that is claimed by the scanner, or null if there is none. */
private Annotation getAnnotation(Binder binder, Method method) { private Annotation getAnnotation(Binder binder, Method method) {
if (method.isBridge() || method.isSynthetic()) { if (method.isBridge() || method.isSynthetic()) {
@ -218,30 +217,7 @@ public final class ProviderMethodsModule implements Module {
return annotation; return annotation;
} }
@Override private static final class Signature {
public boolean equals(Object o) {
return o instanceof ProviderMethodsModule
&& ((ProviderMethodsModule) o).delegate == delegate
&& ((ProviderMethodsModule) o).scanner == scanner;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
private static class MethodAndAnnotation {
final Method method;
final Annotation annotation;
MethodAndAnnotation(Method method, Annotation annotation) {
this.method = method;
this.annotation = annotation;
}
}
private static class Signature {
final Class<?>[] parameters; final Class<?>[] parameters;
final String name; final String name;
final int hashCode; final int hashCode;
@ -277,7 +253,6 @@ public final class ProviderMethodsModule implements Module {
} }
} }
/** Returns true if a overrides b, assumes that the signatures match */ /** Returns true if a overrides b, assumes that the signatures match */
private static boolean overrides(Method a, Method b) { private static boolean overrides(Method a, Method b) {
// See JLS section 8.4.8.1 // See JLS section 8.4.8.1
@ -302,28 +277,27 @@ public final class ProviderMethodsModule implements Module {
@SuppressWarnings("unchecked") // Define T as the method's return type. @SuppressWarnings("unchecked") // Define T as the method's return type.
TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method); TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
Key<T> key = getKey(errors, returnType, method, method.getAnnotations()); Key<T> key = getKey(errors, returnType, method, method.getAnnotations());
boolean prepareMethodError = false;
try { try {
key = scanner.prepareMethod(binder, annotation, key, point); key = scanner.prepareMethod(binder, annotation, key, point);
} catch (Throwable t) { } catch (Throwable t) {
prepareMethodError = true;
binder.addError(t); binder.addError(t);
} }
if (Modifier.isAbstract(method.getModifiers())) { if (Modifier.isAbstract(method.getModifiers())) {
checkState( checkState(
key == null, prepareMethodError || key == null,
"%s returned a non-null key (%s) for %s. prepareMethod() must return null for abstract" "%s returned a non-null key (%s) for %s. prepareMethod() must return null for abstract"
+ " methods", + " methods",
scanner, scanner,
key, key,
method); method);
return null; return null;
} else { }
checkState(
key != null, if (key == null) { // scanner returned null. Skipping the binding.
"%s returned a null key for %s. prepareMethod() can only return null for abstract" return null;
+ " methods",
scanner,
method);
} }
Class<? extends Annotation> scopeAnnotation = Class<? extends Annotation> scopeAnnotation =
@ -335,7 +309,7 @@ public final class ProviderMethodsModule implements Module {
return ProviderMethod.create( return ProviderMethod.create(
key, key,
method, method,
isStaticModule() ? null : delegate, isStaticModule() || Modifier.isStatic(method.getModifiers()) ? null : delegate,
ImmutableSet.copyOf(point.getDependencies()), ImmutableSet.copyOf(point.getDependencies()),
scopeAnnotation, scopeAnnotation,
skipFastClassGeneration, skipFastClassGeneration,
@ -347,4 +321,24 @@ public final class ProviderMethodsModule implements Module {
return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation); return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation);
} }
@Override
public boolean equals(Object o) {
return o instanceof ProviderMethodsModule
&& ((ProviderMethodsModule) o).delegate == delegate
&& ((ProviderMethodsModule) o).scanner.equals(scanner);
}
@Override
public int hashCode() {
return Objects.hashCode(delegate, scanner);
}
/** Is it scanning the built-in @Provides* methods. */
public boolean isScanningBuiltInProvidesMethods() {
return scanner == ProvidesMethodScanner.INSTANCE;
}
public ModuleAnnotatedMethodScanner getScanner() {
return scanner;
}
} }

View file

@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.inject.Binding; import com.google.inject.Binding;
import com.google.inject.spi.ProvisionListener; import com.google.inject.spi.ProvisionListener;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -23,6 +22,11 @@ final class ProvisionListenerStackCallback<T> {
private final ProvisionListener[] listeners; private final ProvisionListener[] listeners;
private final Binding<T> binding; private final Binding<T> binding;
@SuppressWarnings("unchecked")
public static <T> ProvisionListenerStackCallback<T> emptyListener() {
return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK;
}
public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) { public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) {
this.binding = binding; this.binding = binding;
if (listeners.isEmpty()) { if (listeners.isEmpty()) {
@ -33,18 +37,13 @@ final class ProvisionListenerStackCallback<T> {
} }
} }
@SuppressWarnings("unchecked")
public static <T> ProvisionListenerStackCallback<T> emptyListener() {
return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK;
}
public boolean hasListeners() { public boolean hasListeners() {
return listeners.length > 0; return listeners.length > 0;
} }
public T provision(InternalContext context, ProvisionCallback<T> callable) public T provision(InternalContext context, ProvisionCallback<T> callable)
throws InternalProvisionException { throws InternalProvisionException {
Provision provision = new Provision(context, callable); Provision provision = new Provision(callable);
RuntimeException caught = null; RuntimeException caught = null;
try { try {
provision.provision(); provision.provision();
@ -58,6 +57,7 @@ final class ProvisionListenerStackCallback<T> {
Object listener = Object listener =
provision.erredListener != null ? provision.erredListener.getClass() : "(unknown)"; provision.erredListener != null ? provision.erredListener.getClass() : "(unknown)";
throw InternalProvisionException.errorInUserCode( throw InternalProvisionException.errorInUserCode(
ErrorId.OTHER,
caught, caught,
"Error notifying ProvisionListener %s of %s.%n Reason: %s", "Error notifying ProvisionListener %s of %s.%n Reason: %s",
listener, listener,
@ -70,21 +70,18 @@ final class ProvisionListenerStackCallback<T> {
// TODO(sameb): Can this be more InternalFactory-like? // TODO(sameb): Can this be more InternalFactory-like?
public interface ProvisionCallback<T> { public interface ProvisionCallback<T> {
T call() throws InternalProvisionException; public T call() throws InternalProvisionException;
} }
private class Provision extends ProvisionListener.ProvisionInvocation<T> { private class Provision extends ProvisionListener.ProvisionInvocation<T> {
final InternalContext context;
final ProvisionCallback<T> callable; final ProvisionCallback<T> callable;
int index = -1; int index = -1;
T result; T result;
InternalProvisionException exceptionDuringProvision; InternalProvisionException exceptionDuringProvision;
ProvisionListener erredListener; ProvisionListener erredListener;
public Provision(InternalContext context, ProvisionCallback<T> callable) { public Provision(ProvisionCallback<T> callable) {
this.callable = callable; this.callable = callable;
this.context = context;
} }
@Override @Override

View file

@ -38,6 +38,7 @@ import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types; import com.google.inject.util.Types;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -297,7 +298,7 @@ public final class RealMapBinder<K, V> implements Module {
// Binds a Map<K, Provider<V>> // Binds a Map<K, Provider<V>>
RealProviderMapProvider<K, V> providerMapProvider = RealProviderMapProvider<K, V> providerMapProvider =
new RealProviderMapProvider<K, V>(bindingSelection); new RealProviderMapProvider<>(bindingSelection);
binder.bind(bindingSelection.getProviderMapKey()).toProvider(providerMapProvider); binder.bind(bindingSelection.getProviderMapKey()).toProvider(providerMapProvider);
// The map this exposes is internally an ImmutableMap, so it's OK to massage // The map this exposes is internally an ImmutableMap, so it's OK to massage
@ -309,7 +310,12 @@ public final class RealMapBinder<K, V> implements Module {
binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider); binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider);
RealMapProvider<K, V> mapProvider = new RealMapProvider<>(bindingSelection); RealMapProvider<K, V> mapProvider = new RealMapProvider<>(bindingSelection);
binder.bind(bindingSelection.getMapKey()).toProvider(mapProvider); // Bind Map<K, V> to the provider w/ extension support.
binder
.bind(bindingSelection.getMapKey())
.toProvider(new ExtensionRealMapProvider<>(mapProvider));
// Bind Map<K, ? extends V> to the provider w/o the extension support.
binder.bind(bindingSelection.getMapOfKeyExtendsValueKey()).toProvider(mapProvider);
// The Map.Entries are all ProviderMapEntry instances which do not allow setValue, so it is // The Map.Entries are all ProviderMapEntry instances which do not allow setValue, so it is
// safe to massage the return type like this // safe to massage the return type like this
@ -364,6 +370,7 @@ public final class RealMapBinder<K, V> implements Module {
private Key<Map<K, Collection<Provider<V>>>> providerCollectionMultimapKey; private Key<Map<K, Collection<Provider<V>>>> providerCollectionMultimapKey;
private Key<Map<K, Collection<javax.inject.Provider<V>>>> javaxProviderCollectionMultimapKey; private Key<Map<K, Collection<javax.inject.Provider<V>>>> javaxProviderCollectionMultimapKey;
private Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> entrySetJavaxProviderKey; private Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> entrySetJavaxProviderKey;
private Key<Map<K, ? extends V>> mapOfKeyExtendsValueKey;
private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder; private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;
@ -485,7 +492,7 @@ public final class RealMapBinder<K, V> implements Module {
// we don't build up this data structure // we don't build up this data structure
if (duplicates != null) { if (duplicates != null) {
initializationState = InitializationState.HAS_ERRORS; initializationState = InitializationState.HAS_ERRORS;
reportDuplicateKeysError(duplicates, errors); reportDuplicateKeysError(mapKey, duplicates, errors);
return false; return false;
} }
@ -509,26 +516,9 @@ public final class RealMapBinder<K, V> implements Module {
} }
private static <K, V> void reportDuplicateKeysError( private static <K, V> void reportDuplicateKeysError(
Multimap<K, Binding<V>> duplicates, Errors errors) { Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates, Errors errors) {
StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key "); errors.duplicateMapKey(mapKey, duplicates);
boolean first = true; return;
for (Map.Entry<K, Collection<Binding<V>>> entry : duplicates.asMap().entrySet()) {
K dupKey = entry.getKey();
if (first) {
first = false;
sb.append("\"").append(dupKey).append("\", from bindings:\n");
} else {
sb.append("\n and key: \"").append(dupKey).append("\", from bindings:\n");
}
for (Binding<V> dup : entry.getValue()) {
sb.append("\t at ").append(Messages.convert(dup.getSource())).append("\n");
}
}
// TODO(user): Add a different error for every duplicated key
errors.addMessage(sb.toString());
} }
private boolean containsElement(Element element) { private boolean containsElement(Element element) {
@ -553,6 +543,7 @@ public final class RealMapBinder<K, V> implements Module {
|| key.equals(getJavaxProviderCollectionMultimapKey()) || key.equals(getJavaxProviderCollectionMultimapKey())
|| key.equals(entrySetBinder.getSetKey()) || key.equals(entrySetBinder.getSetKey())
|| key.equals(getEntrySetJavaxProviderKey()) || key.equals(getEntrySetJavaxProviderKey())
|| key.equals(getMapOfKeyExtendsValueKey())
|| matchesValueKey(key); || matchesValueKey(key);
} }
@ -638,6 +629,19 @@ public final class RealMapBinder<K, V> implements Module {
return local; return local;
} }
@SuppressWarnings("unchecked")
private Key<Map<K, ? extends V>> getMapOfKeyExtendsValueKey() {
Key<Map<K, ? extends V>> local = mapOfKeyExtendsValueKey;
if (local == null) {
Type extendsValue = Types.subtypeOf(valueType.getType());
Type mapOfKeyAndExtendsValue = Types.mapOf(keyType.getType(), extendsValue);
local =
mapOfKeyExtendsValueKey =
(Key<Map<K, ? extends V>>) mapKey.ofType(mapOfKeyAndExtendsValue);
}
return local;
}
private ImmutableMap<K, Binding<V>> getMapBindings() { private ImmutableMap<K, Binding<V>> getMapBindings() {
checkConfiguration(isInitialized(), "MapBinder has not yet been initialized"); checkConfiguration(isInitialized(), "MapBinder has not yet been initialized");
return mapBindings; return mapBindings;
@ -727,24 +731,23 @@ public final class RealMapBinder<K, V> implements Module {
} }
private static final class RealMapProvider<K, V> private static final class RealMapProvider<K, V>
extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>> extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>> {
implements ProviderWithExtensionVisitor<Map<K, V>>, MapBinderBinding<Map<K, V>> { Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;
private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;
/** /**
* An array of all the injectors. * An array of all the injectors.
* *
* <p>This is parallel to array of keys below * <p>This is parallel to array of keys below
*/ */
private SingleParameterInjector<V>[] injectors; SingleParameterInjector<V>[] injectors;
private K[] keys; K[] keys;
private RealMapProvider(BindingSelection<K, V> bindingSelection) { RealMapProvider(BindingSelection<K, V> bindingSelection) {
super(bindingSelection); super(bindingSelection);
} }
private BindingSelection<K, V> getBindingSelection() { BindingSelection<K, V> getBindingSelection() {
return bindingSelection; return bindingSelection;
} }
@ -805,6 +808,42 @@ public final class RealMapBinder<K, V> implements Module {
public Set<Dependency<?>> getDependencies() { public Set<Dependency<?>> getDependencies() {
return dependencies; return dependencies;
} }
}
/**
* Implementation of a provider instance for the map that also exposes details about the MapBinder
* using the extension SPI, delegating to another provider instance for non-extension (e.g, the
* actual provider instance info) data.
*/
private static final class ExtensionRealMapProvider<K, V>
extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>>
implements ProviderWithExtensionVisitor<Map<K, V>>, MapBinderBinding<Map<K, V>> {
final RealMapProvider<K, V> delegate;
ExtensionRealMapProvider(RealMapProvider<K, V> delegate) {
super(delegate.bindingSelection);
this.delegate = delegate;
}
BindingSelection<K, V> getBindingSelection() {
return bindingSelection;
}
@Override
protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException {
delegate.doInitialize(injector, errors);
}
@Override
protected Map<K, V> doProvision(InternalContext context, Dependency<?> dependency)
throws InternalProvisionException {
return delegate.doProvision(context, dependency);
}
@Override
public Set<Dependency<?>> getDependencies() {
return delegate.getDependencies();
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -831,7 +870,8 @@ public final class RealMapBinder<K, V> implements Module {
(Key<?>) bindingSelection.getJavaxProviderSetMultimapKey(), (Key<?>) bindingSelection.getJavaxProviderSetMultimapKey(),
(Key<?>) bindingSelection.getProviderCollectionMultimapKey(), (Key<?>) bindingSelection.getProviderCollectionMultimapKey(),
(Key<?>) bindingSelection.getJavaxProviderCollectionMultimapKey(), (Key<?>) bindingSelection.getJavaxProviderCollectionMultimapKey(),
(Key<?>) bindingSelection.getMultimapKey()); (Key<?>) bindingSelection.getMultimapKey(),
(Key<?>) bindingSelection.getMapOfKeyExtendsValueKey());
} }
@Override @Override
@ -925,18 +965,18 @@ public final class RealMapBinder<K, V> implements Module {
if (!keysOnlyFromBindings.isEmpty()) { if (!keysOnlyFromBindings.isEmpty()) {
sb.append( sb.append(
Messages.format("%nFound these Bindings that were missing an associated entry:%n")); Errors.format("%nFound these Bindings that were missing an associated entry:%n"));
for (Key<V> key : keysOnlyFromBindings) { for (Key<V> key : keysOnlyFromBindings) {
sb.append( sb.append(
Messages.format(" %s bound at: %s%n", key, valueKeyToBinding.get(key).getSource())); Errors.format(" %s bound at: %s%n", key, valueKeyToBinding.get(key).getSource()));
} }
} }
if (!keysOnlyFromProviderMapEntrys.isEmpty()) { if (!keysOnlyFromProviderMapEntrys.isEmpty()) {
sb.append(Messages.format("%nFound these map keys without a corresponding value:%n")); sb.append(Errors.format("%nFound these map keys without a corresponding value:%n"));
for (Key<V> key : keysOnlyFromProviderMapEntrys) { for (Key<V> key : keysOnlyFromProviderMapEntrys) {
sb.append( sb.append(
Messages.format( Errors.format(
" '%s' bound at: %s%n", " '%s' bound at: %s%n",
valueKeyToKey.get(key), valueKeyToEntryBinding.get(key).getSource())); valueKeyToKey.get(key), valueKeyToEntryBinding.get(key).getSource()));
} }
@ -1314,8 +1354,8 @@ public final class RealMapBinder<K, V> implements Module {
ProviderInstanceBinding<Map<K, V>> providerInstanceBinding = ProviderInstanceBinding<Map<K, V>> providerInstanceBinding =
(ProviderInstanceBinding<Map<K, V>>) mapBinding; (ProviderInstanceBinding<Map<K, V>>) mapBinding;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
RealMapProvider<K, V> mapProvider = ExtensionRealMapProvider<K, V> mapProvider =
(RealMapProvider<K, V>) providerInstanceBinding.getUserSuppliedProvider(); (ExtensionRealMapProvider<K, V>) providerInstanceBinding.getUserSuppliedProvider();
this.bindingSelection = mapProvider.getBindingSelection(); this.bindingSelection = mapProvider.getBindingSelection();
@ -1346,7 +1386,9 @@ public final class RealMapBinder<K, V> implements Module {
private static <K, V> InternalProvisionException createNullValueException( private static <K, V> InternalProvisionException createNullValueException(
K key, Binding<V> binding) { K key, Binding<V> binding) {
return InternalProvisionException.create( return InternalProvisionException.create(
ErrorId.NULL_VALUE_IN_MAP,
"Map injection failed due to null value for key \"%s\", bound at: %s", "Map injection failed due to null value for key \"%s\", bound at: %s",
key, binding.getSource()); key,
binding.getSource());
} }
} }

View file

@ -5,7 +5,6 @@ import static com.google.inject.internal.Errors.checkConfiguration;
import static com.google.inject.internal.Errors.checkNotNull; import static com.google.inject.internal.Errors.checkNotNull;
import static com.google.inject.name.Names.named; import static com.google.inject.name.Names.named;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -24,6 +23,7 @@ import com.google.inject.multibindings.MultibinderBinding;
import com.google.inject.multibindings.MultibindingsTargetVisitor; import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency; import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types; import com.google.inject.util.Types;
@ -31,6 +31,7 @@ import java.lang.reflect.Type;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
/** /**
* The actual multibinder plays several roles: * The actual multibinder plays several roles:
@ -81,6 +82,13 @@ public final class RealMultibinder<T> implements Module {
return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type); return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type);
} }
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Set<? extends T>> setOfExtendsOf(TypeLiteral<T> elementType) {
Type extendsType = Types.subtypeOf(elementType.getType());
Type setOfExtendsType = Types.setOf(extendsType);
return (TypeLiteral<Set<? extends T>>) TypeLiteral.get(setOfExtendsType);
}
private final BindingSelection<T> bindingSelection; private final BindingSelection<T> bindingSelection;
private final Binder binder; private final Binder binder;
@ -92,9 +100,17 @@ public final class RealMultibinder<T> implements Module {
@Override @Override
public void configure(Binder binder) { public void configure(Binder binder) {
checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized"); checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized");
RealMultibinderProvider<T> setProvider = new RealMultibinderProvider<T>(bindingSelection);
// Bind the setKey to the provider wrapped w/ extension support.
binder binder
.bind(bindingSelection.getSetKey()) .bind(bindingSelection.getSetKey())
.toProvider(new RealMultibinderProvider<T>(bindingSelection)); .toProvider(new ExtensionRealMultibinderProvider<>(setProvider));
// Bind the <? extends T> to the provider w/o extension support.
// It's important the exactly one binding implement the extension support and show
// the other keys as aliases, to adhere to the extension contract.
binder.bind(bindingSelection.getSetOfExtendsKey()).toProvider(setProvider);
Provider<Collection<Provider<T>>> collectionOfProvidersProvider = Provider<Collection<Provider<T>>> collectionOfProvidersProvider =
new RealMultibinderCollectionOfProvidersProvider<T>(bindingSelection); new RealMultibinderCollectionOfProvidersProvider<T>(bindingSelection);
binder binder
@ -148,15 +164,19 @@ public final class RealMultibinder<T> implements Module {
return bindingSelection.containsElement(element); return bindingSelection.containsElement(element);
} }
private static final class RealMultibinderProvider<T> /**
extends InternalProviderInstanceBindingImpl.Factory<Set<T>> * Base implement of {@link InternalProviderInstanceBindingImpl.Factory} that works based on a
implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> { * {@link BindingSelection}, allowing provider instances for various bindings to be implemented
private final BindingSelection<T> bindingSelection; * with less duplication.
private List<Binding<T>> bindings; */
private SingleParameterInjector<T>[] injectors; private abstract static class BaseFactory<ValueT, ProvidedT>
private boolean permitDuplicates; extends InternalProviderInstanceBindingImpl.Factory<ProvidedT> {
final Function<BindingSelection<ValueT>, ImmutableSet<Dependency<?>>> dependenciesFn;
final BindingSelection<ValueT> bindingSelection;
RealMultibinderProvider(BindingSelection<T> bindingSelection) { BaseFactory(
BindingSelection<ValueT> bindingSelection,
Function<BindingSelection<ValueT>, ImmutableSet<Dependency<?>>> dependenciesFn) {
// While Multibinders only depend on bindings created in modules so we could theoretically // While Multibinders only depend on bindings created in modules so we could theoretically
// initialize eagerly, they also depend on // initialize eagerly, they also depend on
// 1. findBindingsByType returning results // 1. findBindingsByType returning results
@ -164,23 +184,58 @@ public final class RealMultibinder<T> implements Module {
// neither of those is available during eager initialization, so we use DELAYED // neither of those is available during eager initialization, so we use DELAYED
super(InitializationTiming.DELAYED); super(InitializationTiming.DELAYED);
this.bindingSelection = bindingSelection; this.bindingSelection = bindingSelection;
} this.dependenciesFn = dependenciesFn;
@Override
public Set<Dependency<?>> getDependencies() {
return bindingSelection.getDependencies();
} }
@Override @Override
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
bindingSelection.initialize(injector, errors); bindingSelection.initialize(injector, errors);
this.bindings = bindingSelection.getBindings(); doInitialize();
this.injectors = bindingSelection.getParameterInjectors(); }
this.permitDuplicates = bindingSelection.permitsDuplicates();
abstract void doInitialize();
@Override
public Set<Dependency<?>> getDependencies() {
return dependenciesFn.apply(bindingSelection);
} }
@Override @Override
protected Set<T> doProvision(InternalContext context, Dependency<?> dependency) public boolean equals(Object obj) {
return getClass().isInstance(obj)
&& bindingSelection.equals(((BaseFactory<?, ?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
}
/**
* Provider instance implementation that provides the actual set of values. This is parameterized
* so it can be used to supply a Set<T> and Set<? extends T>, the latter being useful for Kotlin
* support.
*/
private static final class RealMultibinderProvider<T> extends BaseFactory<T, Set<T>> {
List<Binding<T>> bindings;
SingleParameterInjector<T>[] injectors;
boolean permitDuplicates;
RealMultibinderProvider(BindingSelection<T> bindingSelection) {
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
super(bindingSelection, bs -> bs.getDependencies());
}
@Override
protected void doInitialize() {
bindings = bindingSelection.getBindings();
injectors = bindingSelection.getParameterInjectors();
permitDuplicates = bindingSelection.permitsDuplicates();
}
@Override
protected ImmutableSet<T> doProvision(InternalContext context, Dependency<?> dependency)
throws InternalProvisionException { throws InternalProvisionException {
SingleParameterInjector<T>[] localInjectors = injectors; SingleParameterInjector<T>[] localInjectors = injectors;
if (localInjectors == null) { if (localInjectors == null) {
@ -204,14 +259,52 @@ public final class RealMultibinder<T> implements Module {
ImmutableSet<T> set = ImmutableSet.copyOf(values); ImmutableSet<T> set = ImmutableSet.copyOf(values);
// There are fewer items in the set than the array. Figure out which one got dropped. // There are fewer items in the set than the array. Figure out which one got dropped.
if (!permitDuplicates && set.size() < values.length) { if (!permitDuplicates && set.size() < values.length) {
throw newDuplicateValuesException(set, values); throw newDuplicateValuesException(values);
} }
return set; return set;
} }
private InternalProvisionException newNullEntryException(int i) { private InternalProvisionException newNullEntryException(int i) {
return InternalProvisionException.create( return InternalProvisionException.create(
"Set injection failed due to null element bound at: %s", bindings.get(i).getSource()); ErrorId.NULL_ELEMENT_IN_SET,
"Set injection failed due to null element bound at: %s",
bindings.get(i).getSource());
}
private InternalProvisionException newDuplicateValuesException(T[] values) {
Message message =
new Message(
GuiceInternal.GUICE_INTERNAL,
ErrorId.DUPLICATE_ELEMENT,
new DuplicateElementError<T>(
bindingSelection.getSetKey(), bindings, values, ImmutableList.of(getSource())));
return new InternalProvisionException(message);
}
}
/**
* Implementation of BaseFactory that exposes details about the multibinder through the extension
* SPI.
*/
private static final class ExtensionRealMultibinderProvider<T> extends BaseFactory<T, Set<T>>
implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> {
final RealMultibinderProvider<T> delegate;
ExtensionRealMultibinderProvider(RealMultibinderProvider<T> delegate) {
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
super(delegate.bindingSelection, bs -> bs.getDependencies());
this.delegate = delegate;
}
@Override
protected void doInitialize() {
delegate.doInitialize();
}
@Override
protected ImmutableSet<T> doProvision(InternalContext context, Dependency<?> dependency)
throws InternalProvisionException {
return delegate.doProvision(context, dependency);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -225,69 +318,17 @@ public final class RealMultibinder<T> implements Module {
} }
} }
private InternalProvisionException newDuplicateValuesException(
ImmutableSet<T> set, T[] values) {
// TODO(lukes): consider reporting all duplicate values, the easiest way would be to rebuild
// a new set and detect dupes as we go
// Find the duplicate binding
// To do this we take advantage of the fact that set, values and bindings all have the same
// ordering for a non-empty prefix of the set.
// First we scan for the first item dropped from the set.
int newBindingIndex = 0;
for (T item : set) {
if (item != values[newBindingIndex]) {
break;
}
newBindingIndex++;
}
// once we exit the loop newBindingIndex will point at the first item in values that was
// dropped.
Binding<T> newBinding = bindings.get(newBindingIndex);
T newValue = values[newBindingIndex];
// Now we scan again to find the index of the value, we are guaranteed to find it.
int oldBindingIndex = set.asList().indexOf(newValue);
T oldValue = values[oldBindingIndex];
Binding<T> duplicateBinding = bindings.get(oldBindingIndex);
String oldString = oldValue.toString();
String newString = newValue.toString();
if (Objects.equal(oldString, newString)) {
// When the value strings match, just show the source of the bindings
return InternalProvisionException.create(
"Set injection failed due to duplicated element \"%s\""
+ "\n Bound at %s\n Bound at %s",
newValue, duplicateBinding.getSource(), newBinding.getSource());
} else {
// When the value strings don't match, include them both as they may be useful for debugging
return InternalProvisionException.create(
"Set injection failed due to multiple elements comparing equal:"
+ "\n \"%s\"\n bound at %s"
+ "\n \"%s\"\n bound at %s",
oldValue, duplicateBinding.getSource(), newValue, newBinding.getSource());
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof RealMultibinderProvider
&& bindingSelection.equals(((RealMultibinderProvider<?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
@Override @Override
public Key<Set<T>> getSetKey() { public Key<Set<T>> getSetKey() {
return bindingSelection.getSetKey(); return bindingSelection.getSetKey();
} }
@Override @Override
public Set<Key<?>> getAlternateSetKeys() { public ImmutableSet<Key<?>> getAlternateSetKeys() {
return ImmutableSet.of( return ImmutableSet.of(
(Key<?>) bindingSelection.getCollectionOfProvidersKey(), (Key<?>) bindingSelection.getCollectionOfProvidersKey(),
(Key<?>) bindingSelection.getCollectionOfJavaxProvidersKey()); (Key<?>) bindingSelection.getCollectionOfJavaxProvidersKey(),
(Key<?>) bindingSelection.getSetOfExtendsKey());
} }
@Override @Override
@ -311,6 +352,34 @@ public final class RealMultibinder<T> implements Module {
} }
} }
/**
* Implementation of BaseFactory that exposes a collection of providers of the values in the set.
*/
private static final class RealMultibinderCollectionOfProvidersProvider<T>
extends BaseFactory<T, Collection<Provider<T>>> {
ImmutableList<Provider<T>> providers;
RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) {
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
super(bindingSelection, bs -> bs.getProviderDependencies());
}
@Override
protected void doInitialize() {
ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder();
for (Binding<T> binding : bindingSelection.getBindings()) {
providers.add(binding.getProvider());
}
this.providers = providers.build();
}
@Override
protected ImmutableList<Provider<T>> doProvision(
InternalContext context, Dependency<?> dependency) {
return providers;
}
}
private static final class BindingSelection<T> { private static final class BindingSelection<T> {
// prior to initialization we declare just a dependency on the injector, but as soon as we are // prior to initialization we declare just a dependency on the injector, but as soon as we are
// initialized we swap to dependencies on the elements. // initialized we swap to dependencies on the elements.
@ -323,6 +392,7 @@ public final class RealMultibinder<T> implements Module {
private String setName; private String setName;
private Key<Collection<Provider<T>>> collectionOfProvidersKey; private Key<Collection<Provider<T>>> collectionOfProvidersKey;
private Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey; private Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey;
private Key<Set<? extends T>> setOfExtendsKey;
private Key<Boolean> permitDuplicatesKey; private Key<Boolean> permitDuplicatesKey;
private boolean isInitialized; private boolean isInitialized;
@ -448,6 +518,14 @@ public final class RealMultibinder<T> implements Module {
return local; return local;
} }
Key<Set<? extends T>> getSetOfExtendsKey() {
Key<Set<? extends T>> local = setOfExtendsKey;
if (local == null) {
local = setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType));
}
return local;
}
boolean isInitialized() { boolean isInitialized() {
return isInitialized; return isInitialized;
} }
@ -487,7 +565,8 @@ public final class RealMultibinder<T> implements Module {
|| binding.getKey().equals(getPermitDuplicatesKey()) || binding.getKey().equals(getPermitDuplicatesKey())
|| binding.getKey().equals(setKey) || binding.getKey().equals(setKey)
|| binding.getKey().equals(collectionOfProvidersKey) || binding.getKey().equals(collectionOfProvidersKey)
|| binding.getKey().equals(collectionOfJavaxProvidersKey); || binding.getKey().equals(collectionOfJavaxProvidersKey)
|| binding.getKey().equals(setOfExtendsKey);
} else { } else {
return false; return false;
} }
@ -533,51 +612,6 @@ public final class RealMultibinder<T> implements Module {
return bindingSelection.hashCode(); return bindingSelection.hashCode();
} }
private static final class RealMultibinderCollectionOfProvidersProvider<T>
extends InternalProviderInstanceBindingImpl.Factory<Collection<Provider<T>>> {
private final BindingSelection<T> bindingSelection;
private ImmutableList<Provider<T>> collectionOfProviders;
RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) {
super(InitializationTiming.DELAYED); // See comment in RealMultibinderProvider
this.bindingSelection = bindingSelection;
}
@Override
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
bindingSelection.initialize(injector, errors);
ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder();
for (Binding<T> binding : bindingSelection.getBindings()) {
providers.add(binding.getProvider());
}
this.collectionOfProviders = providers.build();
}
@Override
protected Collection<Provider<T>> doProvision(
InternalContext context, Dependency<?> dependency) {
return collectionOfProviders;
}
@Override
public Set<Dependency<?>> getDependencies() {
return bindingSelection.getProviderDependencies();
}
@Override
public boolean equals(Object obj) {
return obj instanceof RealMultibinderCollectionOfProvidersProvider
&& bindingSelection.equals(
((RealMultibinderCollectionOfProvidersProvider<?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
}
/** /**
* We install the permit duplicates configuration as its own binding, all by itself. This way, if * We install the permit duplicates configuration as its own binding, all by itself. This way, if
* only one of a multibinder's users remember to call permitDuplicates(), they're still permitted. * only one of a multibinder's users remember to call permitDuplicates(), they're still permitted.

View file

@ -1,12 +1,11 @@
package com.google.inject.internal; package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.inject.Scope; import com.google.inject.Scope;
import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.ScopeBinding;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Handles {@code Binder.bindScope} commands. * Handles {@code Binder.bindScope} commands.
* *
@ -20,7 +19,8 @@ final class ScopeBindingProcessor extends AbstractProcessor {
@Override @Override
public Boolean visit(ScopeBinding command) { public Boolean visit(ScopeBinding command) {
Scope scope = checkNotNull(command.getScope(), "scope"); Scope scope = checkNotNull(command.getScope(), "scope");
Class<? extends Annotation> annotationType = checkNotNull(command.getAnnotationType(), "annotation type"); Class<? extends Annotation> annotationType =
checkNotNull(command.getAnnotationType(), "annotation type");
if (!Annotations.isScopeAnnotation(annotationType)) { if (!Annotations.isScopeAnnotation(annotationType)) {
errors.missingScopeAnnotation(annotationType); errors.missingScopeAnnotation(annotationType);
@ -32,13 +32,13 @@ final class ScopeBindingProcessor extends AbstractProcessor {
// Go ahead and bind anyway so we don't get collateral errors. // Go ahead and bind anyway so we don't get collateral errors.
} }
ScopeBinding existing = injector.state.getScopeBinding(annotationType); ScopeBinding existing = injector.getBindingData().getScopeBinding(annotationType);
if (existing != null) { if (existing != null) {
if (!scope.equals(existing.getScope())) { if (!scope.equals(existing.getScope())) {
errors.duplicateScopes(existing, annotationType, scope); errors.duplicateScopes(existing, annotationType, scope);
} }
} else { } else {
injector.state.putScopeBinding(annotationType, command); injector.getBindingData().putScopeBinding(annotationType, command);
} }
return true; return true;

View file

@ -0,0 +1,47 @@
package com.google.inject.internal;
import com.google.common.collect.Lists;
import com.google.inject.spi.ErrorDetail;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
/** Error reported by Guice when a scope annotation is not bound to any scope implementation. */
final class ScopeNotFoundError extends InternalErrorDetail<ScopeNotFoundError> {
private final Class<? extends Annotation> scopeAnnotation;
ScopeNotFoundError(Class<? extends Annotation> scopeAnnotation, List<Object> sources) {
super(
ErrorId.SCOPE_NOT_FOUND,
String.format("No scope is bound to %s.", Messages.convert(scopeAnnotation)),
sources,
null);
this.scopeAnnotation = scopeAnnotation;
}
@Override
public boolean isMergeable(ErrorDetail<?> other) {
return other instanceof ScopeNotFoundError
&& ((ScopeNotFoundError) other).scopeAnnotation.equals(scopeAnnotation);
}
@Override
protected void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
List<List<Object>> sourcesSet = new ArrayList<>();
sourcesSet.add(getSources());
mergeableErrors.stream().map(ErrorDetail::getSources).forEach(sourcesSet::add);
formatter.format("%n%s%n", "Used at:");
int sourceListIndex = 1;
for (List<Object> sources : sourcesSet) {
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
}
}
@Override
public ScopeNotFoundError withSources(List<Object> newSources) {
return new ScopeNotFoundError(scopeAnnotation, newSources);
}
}

View file

@ -10,7 +10,6 @@ import com.google.inject.Stage;
import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.BindingScopingVisitor; import com.google.inject.spi.BindingScopingVisitor;
import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.ScopeBinding;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
/** /**
@ -24,7 +23,8 @@ public abstract class Scoping {
* No scoping annotation has been applied. Note that this is different from {@code * No scoping annotation has been applied. Note that this is different from {@code
* in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied. * in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied.
*/ */
public static final Scoping UNSCOPED = new Scoping() { public static final Scoping UNSCOPED =
new Scoping() {
@Override @Override
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) { public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitNoScoping(); return visitor.visitNoScoping();
@ -46,7 +46,35 @@ public abstract class Scoping {
} }
}; };
public static final Scoping SINGLETON_ANNOTATION = new Scoping() { /**
* No scoping annotation has been applied explicitly. Note that this is is the same as {@code
* in(Scopes.NO_SCOPE)}.
*/
private static final Scoping EXPLICITLY_UNSCOPED =
new Scoping() {
@Override
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitNoScoping();
}
@Override
public Scope getScopeInstance() {
return Scopes.NO_SCOPE;
}
@Override
public String toString() {
return Scopes.NO_SCOPE.toString();
}
@Override
public void applyTo(ScopedBindingBuilder scopedBindingBuilder) {
scopedBindingBuilder.in(Scopes.NO_SCOPE);
}
};
public static final Scoping SINGLETON_ANNOTATION =
new Scoping() {
@Override @Override
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) { public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitScopeAnnotation(Singleton.class); return visitor.visitScopeAnnotation(Singleton.class);
@ -68,7 +96,8 @@ public abstract class Scoping {
} }
}; };
public static final Scoping SINGLETON_INSTANCE = new Scoping() { public static final Scoping SINGLETON_INSTANCE =
new Scoping() {
@Override @Override
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) { public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitScope(Scopes.SINGLETON); return visitor.visitScope(Scopes.SINGLETON);
@ -90,7 +119,8 @@ public abstract class Scoping {
} }
}; };
public static final Scoping EAGER_SINGLETON = new Scoping() { public static final Scoping EAGER_SINGLETON =
new Scoping() {
@Override @Override
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) { public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
return visitor.visitEagerSingleton(); return visitor.visitEagerSingleton();
@ -112,12 +142,8 @@ public abstract class Scoping {
} }
}; };
private Scoping() {
}
public static Scoping forAnnotation(final Class<? extends Annotation> scopingAnnotation) { public static Scoping forAnnotation(final Class<? extends Annotation> scopingAnnotation) {
if (scopingAnnotation == Singleton.class if (scopingAnnotation == Singleton.class || scopingAnnotation == javax.inject.Singleton.class) {
|| scopingAnnotation == javax.inject.Singleton.class) {
return SINGLETON_ANNOTATION; return SINGLETON_ANNOTATION;
} }
@ -147,6 +173,8 @@ public abstract class Scoping {
public static Scoping forInstance(final Scope scope) { public static Scoping forInstance(final Scope scope) {
if (scope == Scopes.SINGLETON) { if (scope == Scopes.SINGLETON) {
return SINGLETON_INSTANCE; return SINGLETON_INSTANCE;
} else if (scope == Scopes.NO_SCOPE) {
return EXPLICITLY_UNSCOPED;
} }
return new Scoping() { return new Scoping() {
@ -172,42 +200,6 @@ public abstract class Scoping {
}; };
} }
/**
* Scopes an internal factory.
*/
static <T> InternalFactory<? extends T> scope(Key<T> key, InjectorImpl injector,
InternalFactory<? extends T> creator, Object source, Scoping scoping) {
if (scoping.isNoScope()) {
return creator;
}
Scope scope = scoping.getScopeInstance();
Provider<T> scoped = scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
return new InternalFactoryToProviderAdapter<T>(scoped, source);
}
/**
* Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance
* map. If the scope annotation has no corresponding instance, an error will be added and unscoped
* will be retuned.
*/
static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) {
Class<? extends Annotation> scopeAnnotation = scoping.getScopeAnnotation();
if (scopeAnnotation == null) {
return scoping;
}
ScopeBinding scope = injector.state.getScopeBinding(scopeAnnotation);
if (scope != null) {
return forInstance(scope.getScope());
}
errors.scopeNotFound(scopeAnnotation);
return UNSCOPED;
}
/** /**
* Returns true if this scope was explicitly applied. If no scope was explicitly applied then the * Returns true if this scope was explicitly applied. If no scope was explicitly applied then the
* scoping annotation will be used. * scoping annotation will be used.
@ -224,28 +216,25 @@ public abstract class Scoping {
return getScopeInstance() == Scopes.NO_SCOPE; return getScopeInstance() == Scopes.NO_SCOPE;
} }
/** /** Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}. */
* Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}.
*/
public boolean isEagerSingleton(Stage stage) { public boolean isEagerSingleton(Stage stage) {
if (this == EAGER_SINGLETON) { if (this == EAGER_SINGLETON) {
return true; return true;
} }
return stage == Stage.PRODUCTION && (this == SINGLETON_ANNOTATION || this == SINGLETON_INSTANCE); if (stage == Stage.PRODUCTION) {
return this == SINGLETON_ANNOTATION || this == SINGLETON_INSTANCE;
} }
/** return false;
* Returns the scope instance, or {@code null} if that isn't known for this instance. }
*/
/** Returns the scope instance, or {@code null} if that isn't known for this instance. */
public Scope getScopeInstance() { public Scope getScopeInstance() {
return null; return null;
} }
/** /** Returns the scope annotation, or {@code null} if that isn't known for this instance. */
* Returns the scope annotation, or {@code null} if that isn't known for this instance.
*/
public Class<? extends Annotation> getScopeAnnotation() { public Class<? extends Annotation> getScopeAnnotation() {
return null; return null;
} }
@ -269,4 +258,48 @@ public abstract class Scoping {
public abstract <V> V acceptVisitor(BindingScopingVisitor<V> visitor); public abstract <V> V acceptVisitor(BindingScopingVisitor<V> visitor);
public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder); public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder);
private Scoping() {}
/** Scopes an internal factory. */
static <T> InternalFactory<? extends T> scope(
Key<T> key,
InjectorImpl injector,
InternalFactory<? extends T> creator,
Object source,
Scoping scoping) {
if (scoping.isNoScope()) {
return creator;
}
Scope scope = scoping.getScopeInstance();
// NOTE: SingletonScope relies on the fact that we are passing a
// ProviderToInternalFactoryAdapter here. If you change the type make sure to update
// SingletonScope as well.
Provider<T> scoped =
scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
return new InternalFactoryToProviderAdapter<T>(scoped, source);
}
/**
* Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance
* map. If the scope annotation has no corresponding instance, an error will be added and unscoped
* will be retuned.
*/
static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) {
Class<? extends Annotation> scopeAnnotation = scoping.getScopeAnnotation();
if (scopeAnnotation == null) {
return scoping;
}
ScopeBinding scope = injector.getBindingData().getScopeBinding(scopeAnnotation);
if (scope != null) {
return forInstance(scope.getScope());
}
errors.scopeNotFound(scopeAnnotation);
return UNSCOPED;
}
} }

View file

@ -0,0 +1,169 @@
package com.google.inject.internal;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.Classes;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ElementSource;
import com.google.inject.spi.InjectionPoint;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Formatter;
import java.util.List;
/** Formatting a single source in Guice error message. */
final class SourceFormatter {
static final String INDENT = Strings.repeat(" ", 5);
private final Object source;
private final Formatter formatter;
private final boolean omitPreposition;
private final String moduleStack;
SourceFormatter(Object source, Formatter formatter, boolean omitPreposition) {
if (source instanceof ElementSource) {
ElementSource elementSource = (ElementSource) source;
this.source = elementSource.getDeclaringSource();
this.moduleStack = getModuleStack(elementSource);
} else {
this.source = source;
this.moduleStack = "";
}
this.formatter = formatter;
this.omitPreposition = omitPreposition;
}
void format() {
boolean appendModuleSource = !moduleStack.isEmpty();
if (source instanceof Dependency) {
formatDependency((Dependency<?>) source);
} else if (source instanceof InjectionPoint) {
formatInjectionPoint(null, (InjectionPoint) source);
} else if (source instanceof Class) {
formatter.format("%s%s%n", preposition("at "), StackTraceElements.forType((Class<?>) source));
} else if (source instanceof Member) {
formatMember((Member) source);
} else if (source instanceof TypeLiteral) {
formatter.format("%s%s%n", preposition("while locating "), source);
} else if (source instanceof Key) {
formatKey((Key<?>) source);
} else if (source instanceof Thread) {
appendModuleSource = false;
formatter.format("%s%s%n", preposition("in thread "), source);
} else {
formatter.format("%s%s%n", preposition("at "), source);
}
if (appendModuleSource) {
formatter.format("%s \\_ installed by: %s%n", INDENT, moduleStack);
}
}
private String preposition(String prepostition) {
if (omitPreposition) {
return "";
}
return prepostition;
}
private void formatDependency(Dependency<?> dependency) {
InjectionPoint injectionPoint = dependency.getInjectionPoint();
if (injectionPoint != null) {
formatInjectionPoint(dependency, injectionPoint);
} else {
formatKey(dependency.getKey());
}
}
private void formatKey(Key<?> key) {
formatter.format("%s%s%n", preposition("while locating "), Messages.convert(key));
}
private void formatMember(Member member) {
formatter.format("%s%s%n", preposition("at "), StackTraceElements.forMember(member));
}
private void formatInjectionPoint(Dependency<?> dependency, InjectionPoint injectionPoint) {
Member member = injectionPoint.getMember();
Class<? extends Member> memberType = Classes.memberType(member);
formatMember(injectionPoint.getMember());
if (memberType == Field.class) {
formatter.format("%s \\_ for field %s%n", INDENT, Messages.redBold(member.getName()));
} else if (dependency != null) {
formatter.format("%s \\_ for %s%n", INDENT, getParameterName(dependency));
}
}
static String getModuleStack(ElementSource elementSource) {
if (elementSource == null) {
return "";
}
List<String> modules = Lists.newArrayList(elementSource.getModuleClassNames());
// Insert any original element sources w/ module info into the path.
while (elementSource.getOriginalElementSource() != null) {
elementSource = elementSource.getOriginalElementSource();
modules.addAll(0, elementSource.getModuleClassNames());
}
if (modules.size() <= 1) {
return "";
}
return String.join(" -> ", Lists.reverse(modules));
}
static String getParameterName(Dependency<?> dependency) {
int parameterIndex = dependency.getParameterIndex();
int ordinal = parameterIndex + 1;
Member member = dependency.getInjectionPoint().getMember();
Parameter parameter = null;
if (member instanceof Constructor) {
parameter = ((Constructor<?>) member).getParameters()[parameterIndex];
} else if (member instanceof Method) {
parameter = ((Method) member).getParameters()[parameterIndex];
}
String parameterName = "";
if (parameter != null && parameter.isNamePresent()) {
parameterName = parameter.getName();
}
return String.format(
"%s%s parameter%s",
ordinal,
getOrdinalSuffix(ordinal),
parameterName.isEmpty() ? "" : " " + Messages.redBold(parameterName));
}
/**
* Maps {@code 1} to the string {@code "1st"} ditto for all non-negative numbers
*
* @see <a href="https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers">
* https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers</a>
*/
private static String getOrdinalSuffix(int ordinal) {
// negative ordinals don't make sense, we allow zero though because we are programmers
checkArgument(ordinal >= 0);
if ((ordinal / 10) % 10 == 1) {
// all the 'teens' are weird
return "th";
} else {
// could use a lookup table? any better?
switch (ordinal % 10) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
}
}

View file

@ -7,7 +7,6 @@ import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers; import com.google.inject.matcher.Matchers;
import com.google.inject.spi.TypeConverter; import com.google.inject.spi.TypeConverter;
import com.google.inject.spi.TypeConverterBinding; import com.google.inject.spi.TypeConverterBinding;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -22,9 +21,7 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
super(errors); super(errors);
} }
/** /** Installs default converters for primitives, enums, and class literals. */
* Installs default converters for primitives, enums, and class literals.
*/
static void prepareBuiltInConverters(InjectorImpl injector) { static void prepareBuiltInConverters(InjectorImpl injector) {
// Configure type converters. // Configure type converters.
convertToPrimitiveType(injector, int.class, Integer.class); convertToPrimitiveType(injector, int.class, Integer.class);
@ -35,7 +32,11 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
convertToPrimitiveType(injector, float.class, Float.class); convertToPrimitiveType(injector, float.class, Float.class);
convertToPrimitiveType(injector, double.class, Double.class); convertToPrimitiveType(injector, double.class, Double.class);
convertToClass(injector, Character.class, new TypeConverter() { convertToClass(
injector,
Character.class,
new TypeConverter() {
@Override
public Object convert(String value, TypeLiteral<?> toType) { public Object convert(String value, TypeLiteral<?> toType) {
value = value.trim(); value = value.trim();
if (value.length() != 1) { if (value.length() != 1) {
@ -50,8 +51,12 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
} }
}); });
convertToClasses(injector, Matchers.subclassesOf(Enum.class), new TypeConverter() { convertToClasses(
@SuppressWarnings("unchecked") injector,
Matchers.subclassesOf(Enum.class),
new TypeConverter() {
@SuppressWarnings("rawtypes") // Unavoidable, only way to use Enum.valueOf
@Override
public Object convert(String value, TypeLiteral<?> toType) { public Object convert(String value, TypeLiteral<?> toType) {
return Enum.valueOf((Class) toType.getRawType(), value); return Enum.valueOf((Class) toType.getRawType(), value);
} }
@ -62,7 +67,9 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
} }
}); });
internalConvertToTypes(injector, new AbstractMatcher<>() { internalConvertToTypes(
injector,
new AbstractMatcher<TypeLiteral<?>>() {
@Override @Override
public boolean matches(TypeLiteral<?> typeLiteral) { public boolean matches(TypeLiteral<?> typeLiteral) {
return typeLiteral.getRawType() == Class.class; return typeLiteral.getRawType() == Class.class;
@ -74,6 +81,7 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
} }
}, },
new TypeConverter() { new TypeConverter() {
@Override
public Object convert(String value, TypeLiteral<?> toType) { public Object convert(String value, TypeLiteral<?> toType) {
try { try {
return Class.forName(value); return Class.forName(value);
@ -86,17 +94,18 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
public String toString() { public String toString() {
return "TypeConverter<Class<?>>"; return "TypeConverter<Class<?>>";
} }
} });
);
} }
private static <T> void convertToPrimitiveType(InjectorImpl injector, Class<T> primitiveType, private static <T> void convertToPrimitiveType(
final Class<T> wrapperType) { InjectorImpl injector, Class<T> primitiveType, final Class<T> wrapperType) {
try { try {
final Method parser = wrapperType.getMethod( final Method parser =
"parse" + capitalize(primitiveType.getName()), String.class); wrapperType.getMethod("parse" + capitalize(primitiveType.getName()), String.class);
TypeConverter typeConverter = new TypeConverter() { TypeConverter typeConverter =
new TypeConverter() {
@Override
public Object convert(String value, TypeLiteral<?> toType) { public Object convert(String value, TypeLiteral<?> toType) {
try { try {
return parser.invoke(null, value); return parser.invoke(null, value);
@ -119,14 +128,17 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
} }
} }
private static <T> void convertToClass(InjectorImpl injector, Class<T> type, private static <T> void convertToClass(
TypeConverter converter) { InjectorImpl injector, Class<T> type, TypeConverter converter) {
convertToClasses(injector, Matchers.identicalTo(type), converter); convertToClasses(injector, Matchers.identicalTo(type), converter);
} }
private static void convertToClasses(InjectorImpl injector, private static void convertToClasses(
final Matcher<? super Class<?>> typeMatcher, TypeConverter converter) { InjectorImpl injector, final Matcher<? super Class<?>> typeMatcher, TypeConverter converter) {
internalConvertToTypes(injector, new AbstractMatcher<>() { internalConvertToTypes(
injector,
new AbstractMatcher<TypeLiteral<?>>() {
@Override
public boolean matches(TypeLiteral<?> typeLiteral) { public boolean matches(TypeLiteral<?> typeLiteral) {
Type type = typeLiteral.getType(); Type type = typeLiteral.getType();
if (!(type instanceof Class)) { if (!(type instanceof Class)) {
@ -140,31 +152,34 @@ final class TypeConverterBindingProcessor extends AbstractProcessor {
public String toString() { public String toString() {
return typeMatcher.toString(); return typeMatcher.toString();
} }
}, converter); },
converter);
}
private static void internalConvertToTypes(
InjectorImpl injector, Matcher<? super TypeLiteral<?>> typeMatcher, TypeConverter converter) {
injector
.getBindingData()
.addConverter(
new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter));
} }
@Override @Override
public Boolean visit(TypeConverterBinding command) { public Boolean visit(TypeConverterBinding command) {
injector.state.addConverter(new TypeConverterBinding( injector
.getBindingData()
.addConverter(
new TypeConverterBinding(
command.getSource(), command.getTypeMatcher(), command.getTypeConverter())); command.getSource(), command.getTypeMatcher(), command.getTypeConverter()));
return true; return true;
} }
private static void internalConvertToTypes(InjectorImpl injector,
Matcher<? super TypeLiteral<?>> typeMatcher,
TypeConverter converter) {
injector.state.addConverter(
new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter));
}
private static String capitalize(String s) { private static String capitalize(String s) {
if (s.length() == 0) { if (s.length() == 0) {
return s; return s;
} }
char first = s.charAt(0); char first = s.charAt(0);
char capitalized = Character.toUpperCase(first); char capitalized = Character.toUpperCase(first);
return (first == capitalized) return (first == capitalized) ? s : capitalized + s.substring(1);
? s
: capitalized + s.substring(1);
} }
} }

View file

@ -5,14 +5,13 @@ import com.google.common.base.Preconditions;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification;
import com.google.common.collect.LinkedHashMultiset; import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.internal.util.SourceProvider; import com.google.inject.internal.util.SourceProvider;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -21,38 +20,29 @@ import java.util.Set;
*/ */
final class WeakKeySet { final class WeakKeySet {
private Map<Key<?>, Multiset<Object>> backingMap;
/** /**
* This is already locked externally on add and getSources but we need it to handle clean up in * This is already locked externally on add and getSources but we need it to handle clean up in
* the evictionCache's RemovalListener. * the evictionCache's RemovalListener.
*/ */
private final Object lock; private final Object lock;
private Map<Key<?>, Multiset<Object>> backingMap;
/** /**
* Tracks child injector lifetimes and evicts blacklisted keys/sources after the child injector is * Tracks child injector lifetimes and evicts banned keys/sources after the child injector is
* garbage collected. * garbage collected.
*/ */
private final Cache<State, Set<KeyAndSource>> evictionCache = CacheBuilder.newBuilder() private final Cache<InjectorBindingData, Set<KeyAndSource>> evictionCache =
.weakKeys() CacheBuilder.newBuilder().weakKeys().removalListener(this::cleanupOnRemoval).build();
.removalListener(
(RemovalListener<State, Set<KeyAndSource>>) notification -> { private void cleanupOnRemoval(
RemovalNotification<InjectorBindingData, Set<KeyAndSource>> notification) {
Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause())); Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause()));
cleanUpForCollectedState(notification.getValue()); // There may be multiple child injectors banning a certain key so only remove the source
}) // that's relevant.
.build();
WeakKeySet(Object lock) {
this.lock = lock;
}
/**
* There may be multiple child injectors blacklisting a certain key so only remove the source
* that's relevant.
*/
private void cleanUpForCollectedState(Set<KeyAndSource> keysAndSources) {
synchronized (lock) { synchronized (lock) {
for (KeyAndSource keyAndSource : keysAndSources) { for (KeyAndSource keyAndSource : notification.getValue()) {
Multiset<Object> set = backingMap.get(keyAndSource.key); Multiset<Object> set = backingMap.get(keyAndSource.key);
if (set != null) { if (set != null) {
set.remove(keyAndSource.source); set.remove(keyAndSource.source);
@ -64,7 +54,11 @@ final class WeakKeySet {
} }
} }
public void add(Key<?> key, State state, Object source) { WeakKeySet(Object lock) {
this.lock = lock;
}
public void add(Key<?> key, InjectorBindingData state, Object source) {
if (backingMap == null) { if (backingMap == null) {
backingMap = Maps.newHashMap(); backingMap = Maps.newHashMap();
} }
@ -73,11 +67,11 @@ final class WeakKeySet {
if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) { if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) {
source = null; source = null;
} }
Object convertedSource = Messages.convert(source); Object convertedSource = Errors.convert(source);
backingMap.computeIfAbsent(key, k -> LinkedHashMultiset.create()).add(convertedSource); backingMap.computeIfAbsent(key, k -> LinkedHashMultiset.create()).add(convertedSource);
// Avoid all the extra work if we can. // Avoid all the extra work if we can.
if (state.parent() != State.NONE) { if (state.parent().isPresent()) {
Set<KeyAndSource> keyAndSources = evictionCache.getIfPresent(state); Set<KeyAndSource> keyAndSources = evictionCache.getIfPresent(state);
if (keyAndSources == null) { if (keyAndSources == null) {
evictionCache.put(state, keyAndSources = Sets.newHashSet()); evictionCache.put(state, keyAndSources = Sets.newHashSet());
@ -122,8 +116,7 @@ final class WeakKeySet {
} }
KeyAndSource other = (KeyAndSource) obj; KeyAndSource other = (KeyAndSource) obj;
return Objects.equal(key, other.key) return Objects.equal(key, other.key) && Objects.equal(source, other.source);
&& Objects.equal(source, other.source);
} }
} }
} }

View file

@ -0,0 +1,37 @@
package com.google.inject.internal.util;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.google.common.base.Stopwatch;
import java.util.logging.Logger;
/**
* A continuously timing stopwatch that is used for simple performance monitoring.
*/
public final class ContinuousStopwatch {
private final Logger logger = Logger.getLogger(ContinuousStopwatch.class.getName());
private final Stopwatch stopwatch;
/**
* Constructs a ContinuousStopwatch, which will start timing immediately after construction.
*
* @param stopwatch the internal stopwatch used by ContinuousStopwatch
*/
public ContinuousStopwatch(Stopwatch stopwatch) {
this.stopwatch = stopwatch;
reset();
}
/** Resets and returns elapsed time in milliseconds. */
public long reset() {
long elapsedTimeMs = stopwatch.elapsed(MILLISECONDS);
stopwatch.reset();
stopwatch.start();
return elapsedTimeMs;
}
/** Resets and logs elapsed time in milliseconds. */
public void resetAndLog(String label) {
logger.fine(label + ": " + reset() + "ms");
}
}

View file

@ -0,0 +1,350 @@
package com.google.inject.spi;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.RestrictedBindingSource;
import com.google.inject.RestrictedBindingSource.RestrictionLevel;
import com.google.inject.internal.Errors;
import com.google.inject.internal.GuiceInternal;
import java.util.regex.Pattern;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Contains abstractions for enforcing {@link RestrictedBindingSource}.
*
* <p>Enforcement happens in two phases:
*
* <ol>
* <li>Data structures for enforcement are built during Binder configuration. {@link
* PermitMapConstruction} encapsulates this process, and the {@link PermitMap} is the end
* result.
* <li>Restrictions are enforced by checking each binding for violations with {@link #check},
* which uses the {@link PermitMap}(s) built during Binder configuration.
* </ol>
*
* <p>Note: None of this is thread-safe because it's only used while the Injector is being built,
* which happens on a single thread.
*
*/
public final class BindingSourceRestriction {
private BindingSourceRestriction() {}
private static final Logger logger = Logger.getLogger(RestrictedBindingSource.class.getName());
/** Mapping between an element source and its permit annotations. */
interface PermitMap {
ImmutableSet<Class<? extends Annotation>> getPermits(ElementSource elementSource);
void clear();
}
/** Returns a suggestion for how a restricted binding should be created in case it's missing. */
public static Optional<String> getMissingImplementationSuggestion(
GuiceInternal guiceInternal, Key<?> key) {
checkNotNull(guiceInternal);
RestrictedBindingSource restriction = getRestriction(key);
if (restriction == null) {
return Optional.empty();
}
return Optional.of(
String.format(
"%nHint: This key is restricted and cannot be bound directly. Restriction explanation:"
+ " %s",
restriction.explanation()));
}
/**
* Returns all the restriction violations found on the given Module Elements, as error messages.
*
* <p>Note: Intended to be used on Module Elements, not Injector Elements, ie. the result of
* {@link Elements#getElements} not {@code Injector.getElements}. The Module Elements this check
* cares about are:
*
* <ul>
* <li>Module Bindings, which are always explicit and always have an {@link ElementSource} (with
* a Module Stack), unlike Injector Bindings, which may be implicit and bereft of an
* ElementSource.
* <li>{@link PrivateElements}, which represent the recursive case of this check. They contain a
* list of elements that this check is recursively called on.
* </ul>
*/
public static ImmutableList<Message> check(GuiceInternal guiceInternal, List<Element> elements) {
checkNotNull(guiceInternal);
ImmutableList<Message> errorMessages = check(elements);
// Clear all the permit maps after the checks are done.
elements.forEach(BindingSourceRestriction::clear);
return errorMessages;
}
private static ImmutableList<Message> check(List<Element> elements) {
ImmutableList.Builder<Message> errorMessagesBuilder = ImmutableList.builder();
for (Element element : elements) {
errorMessagesBuilder.addAll(check(element));
}
return errorMessagesBuilder.build();
}
private static ImmutableList<Message> check(Element element) {
return element.acceptVisitor(
new DefaultElementVisitor<ImmutableList<Message>>() {
// Base case.
@Override
protected ImmutableList<Message> visitOther(Element element) {
return ImmutableList.of();
}
// Base case.
@Override
public <T> ImmutableList<Message> visit(Binding<T> binding) {
Optional<Message> errorMessage = check(binding);
if (errorMessage.isPresent()) {
return ImmutableList.of(errorMessage.get());
}
return ImmutableList.of();
}
// Recursive case.
@Override
public ImmutableList<Message> visit(PrivateElements privateElements) {
return check(privateElements.getElements());
}
});
}
private static Optional<Message> check(Binding<?> binding) {
Key<?> key = binding.getKey();
// Module Bindings are all explicit and have an ElementSource.
ElementSource elementSource = (ElementSource) binding.getSource();
RestrictedBindingSource restriction = getRestriction(key);
if (restriction == null) {
return Optional.empty();
}
ImmutableSet<Class<? extends Annotation>> permits = getAllPermits(elementSource);
ImmutableSet<Class<? extends Annotation>> acceptablePermits =
ImmutableSet.copyOf(restriction.permits());
boolean bindingPermitted = permits.stream().anyMatch(acceptablePermits::contains);
if (bindingPermitted || isExempt(elementSource, restriction.exemptModules())) {
return Optional.empty();
}
String violationMessage =
getViolationMessage(
key, restriction.explanation(), acceptablePermits, key.getAnnotationType() != null);
if (restriction.restrictionLevel() == RestrictionLevel.WARNING) {
Formatter sourceFormatter = new Formatter();
Errors.formatSource(sourceFormatter, elementSource);
logger.log(Level.WARNING, violationMessage + "\n" + sourceFormatter);
return Optional.empty();
}
return Optional.of(new Message(elementSource, violationMessage));
}
private static String getViolationMessage(
Key<?> key,
String explanation,
ImmutableSet<Class<? extends Annotation>> acceptablePermits,
boolean annotationRestricted) {
return String.format(
"Unable to bind key: %s. One of the modules that created this binding has to be annotated"
+ " with one of %s, because the key's %s is annotated with @RestrictedBindingSource."
+ " %s",
key,
acceptablePermits.stream().map(a -> "@" + a.getName()).collect(toList()),
annotationRestricted ? "annotation" : "type",
explanation);
}
/** Get all permits on the element source chain. */
private static ImmutableSet<Class<? extends Annotation>> getAllPermits(
ElementSource elementSource) {
ImmutableSet.Builder<Class<? extends Annotation>> permitsBuilder = ImmutableSet.builder();
permitsBuilder.addAll(elementSource.moduleSource.getPermitMap().getPermits(elementSource));
if (elementSource.scanner != null) {
getPermits(elementSource.scanner.getClass()).forEach(permitsBuilder::add);
}
if (elementSource.getOriginalElementSource() != null
&& elementSource.trustedOriginalElementSource) {
permitsBuilder.addAll(getAllPermits(elementSource.getOriginalElementSource()));
}
return permitsBuilder.build();
}
private static boolean isExempt(ElementSource elementSource, String exemptModulesRegex) {
if (exemptModulesRegex.isEmpty()) {
return false;
}
Pattern exemptModulePattern = Pattern.compile(exemptModulesRegex);
// TODO(b/156759807): Switch to Streams.stream (instead of inlining it).
return StreamSupport.stream(getAllModules(elementSource).spliterator(), false)
.anyMatch(moduleName -> exemptModulePattern.matcher(moduleName).matches());
}
private static Iterable<String> getAllModules(ElementSource elementSource) {
List<String> modules = elementSource.getModuleClassNames();
if (elementSource.getOriginalElementSource() == null
|| !elementSource.trustedOriginalElementSource) {
return modules;
}
return Iterables.concat(modules, getAllModules(elementSource.getOriginalElementSource()));
}
private static void clear(Element element) {
element.acceptVisitor(
new DefaultElementVisitor<Void>() {
// Base case.
@Override
protected Void visitOther(Element element) {
Object source = element.getSource();
// Some Module Elements, like Message, don't always have an ElementSource.
if (source instanceof ElementSource) {
clear((ElementSource) source);
}
return null;
}
// Recursive case.
@Override
public Void visit(PrivateElements privateElements) {
privateElements.getElements().forEach(BindingSourceRestriction::clear);
return null;
}
});
}
private static void clear(ElementSource elementSource) {
while (elementSource != null) {
elementSource.moduleSource.getPermitMap().clear();
elementSource = elementSource.getOriginalElementSource();
}
}
/*
* Returns the restriction on the given key (null if there is none).
*
* If the key is annotated then only the annotation restriction matters, the type restriction is
* ignored (an annotated type is essentially a new type).
**/
private static RestrictedBindingSource getRestriction(Key<?> key) {
return key.getAnnotationType() == null
? key.getTypeLiteral().getRawType().getAnnotation(RestrictedBindingSource.class)
: key.getAnnotationType().getAnnotation(RestrictedBindingSource.class);
}
/**
* Builds the map from each module to all the permit annotations on its module stack.
*
* <p>Bindings refer to the module that created them via a {@link ModuleSource}. The map built
* here maps a module's {@link ModuleSource} to all the {@link RestrictedBindingSource.Permit}
* annotations found on the path from the root of the module hierarchy to it. This path contains
* all the modules that transitively install the module (including the module itself). This path
* is also known as the module stack.
*
* <p>The map is built by piggybacking on the depth-first traversal of the module hierarchy during
* Binder configuration.
*/
static final class PermitMapConstruction {
private static final class PermitMapImpl implements PermitMap {
Map<ModuleSource, ImmutableSet<Class<? extends Annotation>>> modulePermits;
@Override
public ImmutableSet<Class<? extends Annotation>> getPermits(ElementSource elementSource) {
return modulePermits.get(elementSource.moduleSource);
}
@Override
public void clear() {
modulePermits = null;
}
}
final Map<ModuleSource, ImmutableSet<Class<? extends Annotation>>> modulePermits =
new HashMap<>();
// Maintains the permits on the current module installation path.
ImmutableSet<Class<? extends Annotation>> currentModulePermits = ImmutableSet.of();
// Stack tracking the currentModulePermits during module traversal.
final Deque<ImmutableSet<Class<? extends Annotation>>> modulePermitsStack = new ArrayDeque<>();
final PermitMapImpl permitMap = new PermitMapImpl();
/**
* Returns a possibly unfinished map. The map should only be used after the construction is
* finished.
*/
PermitMap getPermitMap() {
return permitMap;
}
/**
* Sets the permits on the current module installation path to the permits on the given module
* source so that subsequently installed modules may inherit them. Used only for method
* scanning, so that modules installed by scanners inherit permits from the method's module.
*/
void restoreCurrentModulePermits(ModuleSource moduleSource) {
currentModulePermits = modulePermits.get(moduleSource);
}
/** Called by the Binder prior to entering a module's configure method. */
void pushModule(Class<?> module, ModuleSource moduleSource) {
List<Class<? extends Annotation>> newModulePermits =
getPermits(module)
.filter(permit -> !currentModulePermits.contains(permit))
.collect(toList());
// Save the parent module's permits so that they can be restored when the Binder exits this
// new (child) module's configure method.
modulePermitsStack.push(currentModulePermits);
if (!newModulePermits.isEmpty()) {
currentModulePermits =
ImmutableSet.<Class<? extends Annotation>>builder()
.addAll(currentModulePermits)
.addAll(newModulePermits)
.build();
}
modulePermits.put(moduleSource, currentModulePermits);
}
/** Called by the Binder when it exits a module's configure method. */
void popModule() {
// Restore the parent module's permits.
currentModulePermits = modulePermitsStack.pop();
}
/** Finishes the {@link PermitMap}. Called by the Binder when all modules are installed. */
void finish() {
permitMap.modulePermits = modulePermits;
}
@VisibleForTesting
static boolean isElementSourceCleared(ElementSource elementSource) {
PermitMapImpl permitMap = (PermitMapImpl) elementSource.moduleSource.getPermitMap();
return permitMap.modulePermits == null;
}
}
private static Stream<? extends Class<? extends Annotation>> getPermits(Class<?> clazz) {
Stream<Annotation> annotations = Arrays.stream(clazz.getAnnotations());
// Pick up annotations on anonymous classes (e.g. new @Bar Foo() { ... }):
if (clazz.getAnnotatedSuperclass() != null) {
annotations =
Stream.concat(
annotations, Arrays.stream(clazz.getAnnotatedSuperclass().getAnnotations()));
}
return annotations
.map(Annotation::annotationType)
.filter(a -> a.isAnnotationPresent(RestrictedBindingSource.Permit.class));
}
}

View file

@ -4,41 +4,35 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.inject.internal.util.StackTraceElements; import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement;
import java.util.List; import java.util.List;
/** /**
* Contains information about where and how an {@link Element element} was * Contains information about where and how an {@link Element element} was bound.
* bound.
* <p>
* The {@link #getDeclaringSource() declaring source} refers to a location in
* source code that defines the Guice {@link Element element}. For example, if
* the element is created from a method annotated by {@literal @Provides}, the
* declaring source of element would be the method itself.
* <p>
* The {@link #getStackTrace()} refers to the sequence of calls ends at one of
* {@link com.google.inject.Binder} {@code bindXXX()} methods and eventually
* defines the element. Note that {@link #getStackTrace()} lists
* {@link StackTraceElement StackTraceElements} in reverse chronological order.
* The first element (index zero) is the last method call and the last element
* is the first method invocation. By default, the stack trace is not collected.
* The default behavior can be changed by setting the
* {@code guice_include_stack_traces} flag value. The value can be either
* {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that
* collecting stack traces for every binding can cause a performance hit when
* the injector is created.
* <p>
* The sequence of class names of {@link com.google.inject.Module modules}
* involved in the element creation can be retrieved by
* {@link #getModuleClassNames()}. Similar to {@link #getStackTrace()}, the
* order is reverse chronological. The first module (index 0) is the module that
* installs the {@link Element element}. The last module is the root module.
* <p>
* In order to support the cases where a Guice {@link Element element} is
* created from another Guice {@link Element element} (original) (e.g., by
* {@link Element#applyTo}), it also provides a reference to the original
* element source ({@link #getOriginalElementSource()}).
* *
* <p>The {@link #getDeclaringSource() declaring source} refers to a location in source code that
* defines the Guice {@link Element element}. For example, if the element is created from a method
* annotated by {@literal @Provides}, the declaring source of element would be the method itself.
*
* <p>The {@link #getStackTrace()} refers to the sequence of calls ends at one of {@link
* com.google.inject.Binder} {@code bindXXX()} methods and eventually defines the element. Note that
* {@link #getStackTrace()} lists {@link StackTraceElement StackTraceElements} in reverse
* chronological order. The first element (index zero) is the last method call and the last element
* is the first method invocation. By default, the stack trace is not collected. The default
* behavior can be changed by setting the {@code guice_include_stack_traces} flag value. The value
* can be either {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that
* collecting stack traces for every binding can cause a performance hit when the injector is
* created.
*
* <p>The sequence of class names of {@link com.google.inject.Module modules} involved in the
* element creation can be retrieved by {@link #getModuleClassNames()}. Similar to {@link
* #getStackTrace()}, the order is reverse chronological. The first module (index 0) is the module
* that installs the {@link Element element}. The last module is the root module.
*
* <p>In order to support the cases where a Guice {@link Element element} is created from another
* Guice {@link Element element} (original) (e.g., by {@link Element#applyTo}), it also provides a
* reference to the original element source ({@link #getOriginalElementSource()}).
*
* @since 4.0
*/ */
public final class ElementSource { public final class ElementSource {
@ -46,36 +40,63 @@ public final class ElementSource {
* The {@link ElementSource source} of element that this element created from (if there is any), * The {@link ElementSource source} of element that this element created from (if there is any),
* otherwise {@code null}. * otherwise {@code null}.
*/ */
private final ElementSource originalElementSource; final ElementSource originalElementSource;
/** /**
* The {@link ModuleSource source} of module creates the element. * Wheather the originalElementSource was set externaly (untrusted) or by Guice internals
*/ * (trusted).
private final ModuleSource moduleSource;
private final InMemoryStackTraceElement[] partialCallStack;
private final Object declaringSource;
/**
* Creates a new {@link ElementSource} from the given parameters.
* *
* @param originalSource The source of element that this element created from (if there is * <p>External code can set the originalElementSource to an arbitrary ElementSource via
* Binder.withSource(ElementSource), thereby spoofing the element origin.
*/
final boolean trustedOriginalElementSource;
/** The {@link ModuleSource source} of module creates the element. */
final ModuleSource moduleSource;
/**
* The partial call stack that starts at the last module {@link Module#Configure(Binder)
* configure(Binder)} call. The value is empty if stack trace collection is off.
*/
final InMemoryStackTraceElement[] partialCallStack;
/**
* Refers to a single location in source code that causes the element creation. It can be any
* object such as {@link Constructor}, {@link Method}, {@link Field}, {@link StackTraceElement},
* etc. For example, if the element is created from a method annotated by {@literal @Provides},
* the declaring source of element would be the method itself.
*/
final Object declaringSource;
/** The scanner that created this binding (if it was created by a scanner). */
final ModuleAnnotatedMethodScanner scanner;
/**
* Creates a new {@ElementSource} from the given parameters.
*
* @param originalSource The source of element that this element was created from (if there is
* any), otherwise {@code null}. * any), otherwise {@code null}.
* @param declaringSource the source (in)directly declared the element. * @param declaringSource the source (in)directly declared the element.
* @param moduleSource the moduleSource when the element is bound * @param moduleSource the moduleSource when the element is bound
* @param partialCallStack the partial call stack from the top module to where the element is * @param partialCallStack the partial call stack from the top module to where the element is
* bound * bound
*/ */
ElementSource(ElementSource originalSource, Object declaringSource, ElementSource(
ModuleSource moduleSource, StackTraceElement[] partialCallStack) { ElementSource originalSource,
boolean trustedOriginalSource,
Object declaringSource,
ModuleSource moduleSource,
StackTraceElement[] partialCallStack,
ModuleAnnotatedMethodScanner scanner) {
Preconditions.checkNotNull(declaringSource, "declaringSource cannot be null."); Preconditions.checkNotNull(declaringSource, "declaringSource cannot be null.");
Preconditions.checkNotNull(moduleSource, "moduleSource cannot be null."); Preconditions.checkNotNull(moduleSource, "moduleSource cannot be null.");
Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null.");
this.originalElementSource = originalSource; this.originalElementSource = originalSource;
this.trustedOriginalElementSource = trustedOriginalSource;
this.declaringSource = declaringSource; this.declaringSource = declaringSource;
this.moduleSource = moduleSource; this.moduleSource = moduleSource;
this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack);
this.scanner = scanner;
} }
/** /**
@ -87,20 +108,20 @@ public final class ElementSource {
} }
/** /**
* Returns a single location in source code that defines the element. It can be any object * Returns a single location in source code that defines the element. It can be any object such as
* such as {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method}, * {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method}, {@link
* {@link java.lang.reflect.Field}, {@link StackTraceElement}, etc. For * java.lang.reflect.Field}, {@link StackTraceElement}, etc. For example, if the element is
* example, if the element is created from a method annotated by {@literal @Provides}, the * created from a method annotated by {@literal @Provides}, the declaring source of element would
* declaring source of element would be the method itself. * be the method itself.
*/ */
public Object getDeclaringSource() { public Object getDeclaringSource() {
return declaringSource; return declaringSource;
} }
/** /**
* Returns the class names of modules involved in creating this {@link Element}. The first * Returns the class names of modules involved in creating this {@link Element}. The first element
* element (index 0) is the class name of module that defined the element, and the last element * (index 0) is the class name of module that defined the element, and the last element is the
* is the class name of root module. * class name of root module.
*/ */
public List<String> getModuleClassNames() { public List<String> getModuleClassNames() {
return moduleSource.getModuleClassNames(); return moduleSource.getModuleClassNames();
@ -110,18 +131,18 @@ public final class ElementSource {
* Returns the position of {@link com.google.inject.Module#configure configure(Binder)} method * Returns the position of {@link com.google.inject.Module#configure configure(Binder)} method
* call in the {@link #getStackTrace stack trace} for modules that their classes returned by * call in the {@link #getStackTrace stack trace} for modules that their classes returned by
* {@link #getModuleClassNames}. For example, if the stack trace looks like the following: * {@link #getModuleClassNames}. For example, if the stack trace looks like the following:
* <p> *
* {@code * <ol>
* 0 - Binder.bind(), * <li>{@code Binder.bind()}
* 1 - ModuleTwo.configure(), * <li>{@code ModuleTwo.configure()}
* 2 - Binder.install(), * <li>{@code Binder.install()}
* 3 - ModuleOne.configure(), * <li>{@code ModuleOne.configure()}
* 4 - theRest(). * <li>{@code theRest().
* } * </ol>
* <p> *
* 1 and 3 are returned. * <p>1 and 3 are returned.
* <p> *
* In the cases where stack trace is not available (i.e., the stack trace was not collected), * <p>In the cases where stack trace is not available (i.e., the stack trace was not collected),
* it returns -1 for all module positions. * it returns -1 for all module positions.
*/ */
public List<Integer> getModuleConfigurePositionsInStackTrace() { public List<Integer> getModuleConfigurePositionsInStackTrace() {
@ -140,11 +161,11 @@ public final class ElementSource {
/** /**
* Returns the sequence of method calls that ends at one of {@link com.google.inject.Binder} * Returns the sequence of method calls that ends at one of {@link com.google.inject.Binder}
* {@code bindXXX()} methods and eventually defines the element. Note that * {@code bindXXX()} methods and eventually defines the element. Note that {@link #getStackTrace}
* this method lists {@link StackTraceElement StackTraceElements} in reverse * lists {@link StackTraceElement StackTraceElements} in reverse chronological order. The first
* chronological order. The first element (index zero) is the last method call and the last * element (index zero) is the last method call and the last element is the first method
* element is the first method invocation. In the cases where stack trace is not available * invocation. In the cases where stack trace is not available (i.e.,the stack trace was not
* (i.e.,the stack trace was not collected), it returns an empty array. * collected), it returns an empty array.
*/ */
public StackTraceElement[] getStackTrace() { public StackTraceElement[] getStackTrace() {
int modulesCallStackSize = moduleSource.getStackTraceSize(); int modulesCallStackSize = moduleSource.getStackTraceSize();
@ -152,15 +173,16 @@ public final class ElementSource {
int size = moduleSource.getStackTraceSize() + chunkSize; int size = moduleSource.getStackTraceSize() + chunkSize;
StackTraceElement[] callStack = new StackTraceElement[size]; StackTraceElement[] callStack = new StackTraceElement[size];
System.arraycopy( System.arraycopy(
StackTraceElements.convertToStackTraceElement(partialCallStack), 0, callStack, 0, StackTraceElements.convertToStackTraceElement(partialCallStack),
0,
callStack,
0,
chunkSize); chunkSize);
System.arraycopy(moduleSource.getStackTrace(), 0, callStack, chunkSize, modulesCallStackSize); System.arraycopy(moduleSource.getStackTrace(), 0, callStack, chunkSize, modulesCallStackSize);
return callStack; return callStack;
} }
/** /** Returns {@code getDeclaringSource().toString()} value. */
* Returns {@code getDeclaringSource().toString()} value.
*/
@Override @Override
public String toString() { public String toString() {
return getDeclaringSource().toString(); return getDeclaringSource().toString();

View file

@ -1,6 +1,11 @@
package com.google.inject.spi; package com.google.inject.spi;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -24,15 +29,15 @@ import com.google.inject.internal.BindingBuilder;
import com.google.inject.internal.ConstantBindingBuilderImpl; import com.google.inject.internal.ConstantBindingBuilderImpl;
import com.google.inject.internal.Errors; import com.google.inject.internal.Errors;
import com.google.inject.internal.ExposureBuilder; import com.google.inject.internal.ExposureBuilder;
import com.google.inject.internal.GuiceInternal;
import com.google.inject.internal.InternalFlags.IncludeStackTraceOption; import com.google.inject.internal.InternalFlags.IncludeStackTraceOption;
import com.google.inject.internal.Messages;
import com.google.inject.internal.MoreTypes; import com.google.inject.internal.MoreTypes;
import com.google.inject.internal.PrivateElementsImpl; import com.google.inject.internal.PrivateElementsImpl;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.internal.ProviderMethodsModule; import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.internal.util.SourceProvider; import com.google.inject.internal.util.SourceProvider;
import com.google.inject.internal.util.StackTraceElements; import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matcher;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -41,9 +46,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption;
/** /**
* Exposes elements of a module so they can be inspected, validated or {@link * Exposes elements of a module so they can be inspected, validated or {@link
* Element#applyTo(Binder) rewritten}. * Element#applyTo(Binder) rewritten}.
@ -52,7 +54,7 @@ import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOptio
public final class Elements { public final class Elements {
private static final BindingTargetVisitor<Object, Object> GET_INSTANCE_VISITOR = private static final BindingTargetVisitor<Object, Object> GET_INSTANCE_VISITOR =
new DefaultBindingTargetVisitor<>() { new DefaultBindingTargetVisitor<Object, Object>() {
@Override @Override
public Object visit(InstanceBinding<?> binding) { public Object visit(InstanceBinding<?> binding) {
return binding.getInstance(); return binding.getInstance();
@ -64,54 +66,56 @@ public final class Elements {
} }
}; };
/** /** Records the elements executed by {@code modules}. */
* Records the elements executed by {@code modules}.
*/
public static List<Element> getElements(Module... modules) { public static List<Element> getElements(Module... modules) {
return getElements(Stage.DEVELOPMENT, Arrays.asList(modules)); return getElements(Stage.DEVELOPMENT, Arrays.asList(modules));
} }
/** /** Records the elements executed by {@code modules}. */
* Records the elements executed by {@code modules}.
*/
public static List<Element> getElements(Stage stage, Module... modules) { public static List<Element> getElements(Stage stage, Module... modules) {
return getElements(stage, Arrays.asList(modules)); return getElements(stage, Arrays.asList(modules));
} }
/** /** Records the elements executed by {@code modules}. */
* Records the elements executed by {@code modules}.
*/
public static List<Element> getElements(Iterable<? extends Module> modules) { public static List<Element> getElements(Iterable<? extends Module> modules) {
return getElements(Stage.DEVELOPMENT, modules); return getElements(Stage.DEVELOPMENT, modules);
} }
/** /** Records the elements executed by {@code modules}. */
* Records the elements executed by {@code modules}.
*/
public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) { public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) {
RecordingBinder binder = new RecordingBinder(stage); RecordingBinder binder = new RecordingBinder(stage);
for (Module module : modules) { for (Module module : modules) {
binder.install(module); binder.install(module);
} }
binder.scanForAnnotatedMethods(); binder.scanForAnnotatedMethods();
for (RecordingBinder child : binder.privateBinders) { for (RecordingBinder child : binder.privateBindersForScanning) {
child.scanForAnnotatedMethods(); child.scanForAnnotatedMethods();
} }
binder.permitMapConstruction.finish();
// Free the memory consumed by the stack trace elements cache // Free the memory consumed by the stack trace elements cache
StackTraceElements.clearCache(); StackTraceElements.clearCache();
return Collections.unmodifiableList(binder.elements); return Collections.unmodifiableList(binder.elements);
} }
// TODO(user): Consider moving the RecordingBinder to com.google.inject.internal and removing these
// internal 'friend' methods.
/** /**
* Returns the module composed of {@code elements}. * Internal version of Binder.withSource for establishing a trusted ElementSource chain for
* source-restricting bindings that are re-written using {@link Element#applyTo}.
*
* <p>Using Binder.withSource is not trustworthy because it's a public API that external users can
* use to spoof the original ElementSource of a binding by calling withSource(bogusElementSource).
*
* @since 5.0
*/ */
public static Module getModule(final Iterable<? extends Element> elements) { public static Binder withTrustedSource(
return new ElementsAsModule(elements); GuiceInternal guiceInternal, Binder binder, Object source) {
checkNotNull(guiceInternal);
if (binder instanceof RecordingBinder) {
return ((RecordingBinder) binder).withTrustedSource(source);
} }
// Preserve existing (untrusted) behavior for non-standard Binder implementations.
@SuppressWarnings("unchecked") return binder.withSource(source);
static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR;
} }
private static class ElementsAsModule implements Module { private static class ElementsAsModule implements Module {
@ -129,13 +133,21 @@ public final class Elements {
} }
} }
/** Returns the module composed of {@code elements}. */
public static Module getModule(final Iterable<? extends Element> elements) {
return new ElementsAsModule(elements);
}
@SuppressWarnings("unchecked")
static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR;
}
private static class ModuleInfo { private static class ModuleInfo {
private final Binder binder;
private final ModuleSource moduleSource; private final ModuleSource moduleSource;
private final boolean skipScanning; private final boolean skipScanning;
private ModuleInfo(Binder binder, ModuleSource moduleSource, boolean skipScanning) { private ModuleInfo(ModuleSource moduleSource, boolean skipScanning) {
this.binder = binder;
this.moduleSource = moduleSource; this.moduleSource = moduleSource;
this.skipScanning = skipScanning; this.skipScanning = skipScanning;
} }
@ -148,19 +160,28 @@ public final class Elements {
private final Object source; private final Object source;
private final SourceProvider sourceProvider; private final SourceProvider sourceProvider;
private final Set<ModuleAnnotatedMethodScanner> scanners; private final Set<ModuleAnnotatedMethodScanner> scanners;
/** /** The binder where exposed bindings will be created */
* The binder where exposed bindings will be created
*/
private final RecordingBinder parent; private final RecordingBinder parent;
private final PrivateElementsImpl privateElements; private final PrivateElementsImpl privateElements;
/** /** All children private binders, so we can scan through them. */
* All children private binders, so we can scan through them. private final List<RecordingBinder> privateBindersForScanning;
*/
private final List<RecordingBinder> privateBinders; private final BindingSourceRestriction.PermitMapConstruction permitMapConstruction;
/**
* The current modules stack /** The current modules stack */
*/
private ModuleSource moduleSource = null; private ModuleSource moduleSource = null;
/**
* The current scanner.
*
* <p>Note that scanners cannot nest, ie. a scanner cannot install a module that requires
* scanning - except the built-in @Provides* methods. The built-in scanner isn't tracked by this
* variable, only custom scanners are.
*/
private ModuleAnnotatedMethodScanner scannerSource = null;
private ModuleAnnotatedMethodScanner currentScanner = null;
private boolean trustedSource = false;
private RecordingBinder(Stage stage) { private RecordingBinder(Stage stage) {
this.stage = stage; this.stage = stage;
@ -168,47 +189,59 @@ public final class Elements {
this.scanners = Sets.newLinkedHashSet(); this.scanners = Sets.newLinkedHashSet();
this.elements = Lists.newArrayList(); this.elements = Lists.newArrayList();
this.source = null; this.source = null;
this.sourceProvider = SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses( this.sourceProvider =
Elements.class, RecordingBinder.class, AbstractModule.class, SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses(
ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class); Elements.class,
RecordingBinder.class,
AbstractModule.class,
ConstantBindingBuilderImpl.class,
AbstractBindingBuilder.class,
BindingBuilder.class);
this.parent = null; this.parent = null;
this.privateElements = null; this.privateElements = null;
this.privateBinders = Lists.newArrayList(); this.privateBindersForScanning = Lists.newArrayList();
this.permitMapConstruction = new BindingSourceRestriction.PermitMapConstruction();
} }
/** /** Creates a recording binder that's backed by {@code prototype}. */
* Creates a recording binder that's backed by {@code prototype}.
*/
private RecordingBinder( private RecordingBinder(
RecordingBinder prototype, Object source, SourceProvider sourceProvider) { RecordingBinder prototype,
Object source,
SourceProvider sourceProvider,
boolean trustedSource) {
checkArgument(source == null ^ sourceProvider == null); checkArgument(source == null ^ sourceProvider == null);
this.stage = prototype.stage; this.stage = prototype.stage;
this.modules = prototype.modules; this.modules = prototype.modules;
this.elements = prototype.elements; this.elements = prototype.elements;
this.scanners = prototype.scanners; this.scanners = prototype.scanners;
this.currentScanner = prototype.currentScanner;
this.source = source; this.source = source;
this.trustedSource = trustedSource;
this.moduleSource = prototype.moduleSource; this.moduleSource = prototype.moduleSource;
this.sourceProvider = sourceProvider; this.sourceProvider = sourceProvider;
this.parent = prototype.parent; this.parent = prototype.parent;
this.privateElements = prototype.privateElements; this.privateElements = prototype.privateElements;
this.privateBinders = prototype.privateBinders; this.privateBindersForScanning = prototype.privateBindersForScanning;
this.permitMapConstruction = prototype.permitMapConstruction;
this.scannerSource = prototype.scannerSource;
} }
/** /** Creates a private recording binder. */
* Creates a private recording binder.
*/
private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) { private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) {
this.stage = parent.stage; this.stage = parent.stage;
this.modules = Maps.newLinkedHashMap(); this.modules = Maps.newLinkedHashMap();
this.scanners = Sets.newLinkedHashSet(parent.scanners); this.scanners = Sets.newLinkedHashSet();
this.currentScanner = parent.currentScanner;
this.elements = privateElements.getElementsMutable(); this.elements = privateElements.getElementsMutable();
this.source = parent.source; this.source = parent.source;
this.moduleSource = parent.moduleSource; this.moduleSource = parent.moduleSource;
this.sourceProvider = parent.sourceProvider; this.sourceProvider = parent.sourceProvider;
this.parent = parent; this.parent = parent;
this.privateElements = privateElements; this.privateElements = privateElements;
this.privateBinders = parent.privateBinders; this.privateBindersForScanning = parent.privateBindersForScanning;
this.permitMapConstruction = parent.permitMapConstruction;
this.scannerSource = parent.scannerSource;
} }
@Override @Override
@ -219,19 +252,23 @@ public final class Elements {
@Override @Override
@SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type @SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type
public void requestInjection(Object instance) { public void requestInjection(Object instance) {
checkNotNull(instance, "instance");
requestInjection((TypeLiteral<Object>) TypeLiteral.get(instance.getClass()), instance); requestInjection((TypeLiteral<Object>) TypeLiteral.get(instance.getClass()), instance);
} }
@Override @Override
public <T> void requestInjection(TypeLiteral<T> type, T instance) { public <T> void requestInjection(TypeLiteral<T> type, T instance) {
elements.add(new InjectionRequest<>(getElementSource(), MoreTypes.canonicalizeForKey(type), checkNotNull(instance, "instance");
instance)); elements.add(
new InjectionRequest<T>(
getElementSource(), MoreTypes.canonicalizeForKey(type), instance));
} }
@Override @Override
public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> typeLiteral) { public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> typeLiteral) {
final MembersInjectorLookup<T> element = new MembersInjectorLookup<>(getElementSource(), final MembersInjectorLookup<T> element =
MoreTypes.canonicalizeForKey(typeLiteral)); new MembersInjectorLookup<T>(
getElementSource(), MoreTypes.canonicalizeForKey(typeLiteral));
elements.add(element); elements.add(element);
return element.getMembersInjector(); return element.getMembersInjector();
} }
@ -247,8 +284,8 @@ public final class Elements {
} }
@Override @Override
public void bindListener(Matcher<? super Binding<?>> bindingMatcher, public void bindListener(
ProvisionListener... listeners) { Matcher<? super Binding<?>> bindingMatcher, ProvisionListener... listeners) {
elements.add(new ProvisionListenerBinding(getElementSource(), bindingMatcher, listeners)); elements.add(new ProvisionListenerBinding(getElementSource(), bindingMatcher, listeners));
} }
@ -260,13 +297,12 @@ public final class Elements {
} }
/** /**
* Applies all scanners to the modules we've installed. We skip certain * Applies all scanners to the modules we've installed. We skip certain PrivateModules because
* PrivateModules because store them in more than one Modules map and only * store them in more than one Modules map and only want to process them through one of the
* want to process them through one of the maps. (They're stored in both * maps. (They're stored in both maps to prevent a module from being installed more than once.)
* maps to prevent a module from being installed more than once.)
*/ */
void scanForAnnotatedMethods() { void scanForAnnotatedMethods() {
for (ModuleAnnotatedMethodScanner scanner : scanners) { Iterable<ModuleAnnotatedMethodScanner> scanners = getAllScanners();
// Note: we must iterate over a copy of the modules because calling install(..) // Note: we must iterate over a copy of the modules because calling install(..)
// will mutate modules, otherwise causing a ConcurrentModificationException. // will mutate modules, otherwise causing a ConcurrentModificationException.
for (Map.Entry<Module, ModuleInfo> entry : Maps.newLinkedHashMap(modules).entrySet()) { for (Map.Entry<Module, ModuleInfo> entry : Maps.newLinkedHashMap(modules).entrySet()) {
@ -275,9 +311,12 @@ public final class Elements {
if (info.skipScanning) { if (info.skipScanning) {
continue; continue;
} }
for (ModuleAnnotatedMethodScanner scanner : scanners) {
currentScanner = scanner;
moduleSource = entry.getValue().moduleSource; moduleSource = entry.getValue().moduleSource;
permitMapConstruction.restoreCurrentModulePermits(moduleSource);
try { try {
info.binder.install(ProviderMethodsModule.forModule(module, scanner)); install(ProviderMethodsModule.forModule(module, scanner));
} catch (RuntimeException e) { } catch (RuntimeException e) {
Collection<Message> messages = Errors.getMessagesFromThrowable(e); Collection<Message> messages = Errors.getMessagesFromThrowable(e);
if (!messages.isEmpty()) { if (!messages.isEmpty()) {
@ -293,35 +332,50 @@ public final class Elements {
@Override @Override
public void install(Module module) { public void install(Module module) {
if (!modules.containsKey(module)) { // Ignore duplicate installations of the same module instance.
if (modules.containsKey(module)) {
return;
}
// Whether the module installed is a ProviderMethodModule for a custom scanner.
boolean customScanner = false;
Class<?> newModuleClass = null;
RecordingBinder binder = this; RecordingBinder binder = this;
boolean unwrapModuleSource = false;
// Update the module source for the new module // Update the module source for the new module
if (module instanceof ProviderMethodsModule) { if (module instanceof ProviderMethodsModule) {
ProviderMethodsModule providerMethodsModule = (ProviderMethodsModule) module;
if (!providerMethodsModule.isScanningBuiltInProvidesMethods()) {
scannerSource = providerMethodsModule.getScanner();
customScanner = true;
}
// There are two reason's we'd want to get the module source in a ProviderMethodsModule. // There are two reason's we'd want to get the module source in a ProviderMethodsModule.
// ModuleAnnotatedMethodScanner lets users scan their own modules for @Provides-like // ModuleAnnotatedMethodScanner lets users scan their own modules for @Provides-like
// bindings. If they install the module at a top-level, then moduleSource can be null. // bindings. If they install the module at a top-level, then moduleSource can be null.
// Also, if they pass something other than 'this' to it, we'd have the wrong source. // Also, if they pass something other than 'this' to it, we'd have the wrong source.
Object delegate = ((ProviderMethodsModule) module).getDelegateModule(); Class<?> delegateClass = providerMethodsModule.getDelegateModuleClass();
if (moduleSource == null if (moduleSource == null
|| !moduleSource.getModuleClassName().equals(delegate.getClass().getName())) { || !moduleSource.getModuleClassName().equals(delegateClass.getName())) {
moduleSource = getModuleSource(delegate); newModuleClass = delegateClass;
unwrapModuleSource = true;
} }
} else { } else {
moduleSource = getModuleSource(module); if (moduleScanning()) {
unwrapModuleSource = true; forbidNestedScannerMethods(module);
}
newModuleClass = module.getClass();
}
if (newModuleClass != null) {
moduleSource = getModuleSource(newModuleClass);
permitMapConstruction.pushModule(newModuleClass, moduleSource);
} }
boolean skipScanning = false; boolean skipScanning = false;
if (module instanceof PrivateModule) { if (module instanceof PrivateModule) {
binder = (RecordingBinder) binder.newPrivateBinder(); binder = (RecordingBinder) binder.newPrivateBinder();
// Store the module in the private binder too so we scan for it. // Store the module in the private binder too so we scan for it.
binder.modules.put(module, new ModuleInfo(binder, moduleSource, false)); binder.modules.put(module, new ModuleInfo(moduleSource, false));
skipScanning = true; // don't scan this module in the parent's module set. skipScanning = true; // don't scan this module in the parent's module set.
} }
// Always store this in the parent binder (even if it was a private module) // Always store this in the parent binder (even if it was a private module)
// so that we know not to process it again, and so that scanners inherit down. // so that we know not to process it again, and so that scanners inherit down.
modules.put(module, new ModuleInfo(binder, moduleSource, skipScanning)); modules.put(module, new ModuleInfo(moduleSource, skipScanning));
try { try {
module.configure(binder); module.configure(binder);
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -334,10 +388,44 @@ public final class Elements {
} }
binder.install(ProviderMethodsModule.forModule(module)); binder.install(ProviderMethodsModule.forModule(module));
// We are done with this module, so undo module source change // We are done with this module, so undo module source change
if (unwrapModuleSource) { if (newModuleClass != null) {
moduleSource = moduleSource.getParent(); moduleSource = moduleSource.getParent();
permitMapConstruction.popModule();
}
// Only wipe the scannerSource once custom scanner installation is finished. This way all
// bindings created by the custom scanner will have it as their scanner source, including
// bindings created by the built-in scanner scanning @Provides* methods in modules installed
// by the custom scanner.
if (customScanner) {
scannerSource = null;
} }
} }
private void forbidNestedScannerMethods(Module module) {
for (ModuleAnnotatedMethodScanner scanner : getAllScanners()) {
ProviderMethodsModule providerMethodsModule =
(ProviderMethodsModule) ProviderMethodsModule.forModule(module, scanner);
for (ProviderMethod<?> method : providerMethodsModule.getProviderMethods(this)) {
addError(
"Scanner %s is installing a module with %s method. Installing modules with custom "
+ "provides methods from a ModuleAnnotatedMethodScanner is not supported.",
currentScanner, method.getAnnotation().annotationType().getCanonicalName());
}
}
}
/**
* Get all scanners registered in this binder and its ancestors.
*
* <p>Should only be called during module scanning, because at that point registering new
* scanners is forbidden.
*/
private Iterable<ModuleAnnotatedMethodScanner> getAllScanners() {
if (privateElements == null) {
return scanners;
}
// Private binders have their own set of scanners and they inherit from their parent.
return Iterables.concat(scanners, parent.getAllScanners());
} }
@Override @Override
@ -347,13 +435,13 @@ public final class Elements {
@Override @Override
public void addError(String message, Object... arguments) { public void addError(String message, Object... arguments) {
elements.add(new Message(getElementSource(), Messages.format(message, arguments))); elements.add(new Message(getElementSource(), Errors.format(message, arguments)));
} }
@Override @Override
public void addError(Throwable t) { public void addError(Throwable t) {
String message = "An exception was caught and reported. Message: " + t.getMessage(); String message = "An exception was caught and reported. Message: " + t.getMessage();
elements.add(new Message(ImmutableList.of(getElementSource()), message, t)); elements.add(new Message(ImmutableList.of((Object) getElementSource()), message, t));
} }
@Override @Override
@ -363,7 +451,9 @@ public final class Elements {
@Override @Override
public <T> AnnotatedBindingBuilder<T> bind(Key<T> key) { public <T> AnnotatedBindingBuilder<T> bind(Key<T> key) {
return new BindingBuilder<>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key)); BindingBuilder<T> builder =
new BindingBuilder<T>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key));
return builder;
} }
@Override @Override
@ -399,14 +489,24 @@ public final class Elements {
} }
@Override @Override
public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher, public void convertToTypes(
TypeConverter converter) { Matcher<? super TypeLiteral<?>> typeMatcher, TypeConverter converter) {
elements.add(new TypeConverterBinding(getElementSource(), typeMatcher, converter)); elements.add(new TypeConverterBinding(getElementSource(), typeMatcher, converter));
} }
@Override @Override
public RecordingBinder withSource(final Object source) { public RecordingBinder withSource(final Object source) {
return source == this.source ? this : new RecordingBinder(this, source, null); return source == this.source
? this
: new RecordingBinder(
this, source, /* sourceProvider = */ null, /* trustedSource = */ false);
}
public RecordingBinder withTrustedSource(final Object source) {
return source == this.source
? this
: new RecordingBinder(
this, source, /* sourceProvider = */ null, /* trustedSource = */ true);
} }
@Override @Override
@ -417,15 +517,19 @@ public final class Elements {
} }
SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip); SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip);
return new RecordingBinder(this, null, newSourceProvider); return new RecordingBinder(
this, /* source = */ null, newSourceProvider, /* trustedSource = */ false);
} }
@Override @Override
public PrivateBinder newPrivateBinder() { public PrivateBinder newPrivateBinder() {
PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource()); PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource());
RecordingBinder binder = new RecordingBinder(this, privateElements); RecordingBinder binder = new RecordingBinder(this, privateElements);
privateBinders.add(binder);
elements.add(privateElements); elements.add(privateElements);
// Don't want to scan private modules installed by scanners.
if (!moduleScanning()) {
privateBindersForScanning.add(binder);
}
return binder; return binder;
} }
@ -451,6 +555,13 @@ public final class Elements {
@Override @Override
public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) { public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) {
if (moduleScanning()) {
addError(
"Attempting to register ModuleAnnotatedMethodScanner %s from scanner %s. Scanners are"
+ " not allowed to register other scanners.",
currentScanner, scanner);
return;
}
scanners.add(scanner); scanners.add(scanner);
elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner)); elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner));
} }
@ -472,26 +583,26 @@ public final class Elements {
private <T> AnnotatedElementBuilder exposeInternal(Key<T> key) { private <T> AnnotatedElementBuilder exposeInternal(Key<T> key) {
if (privateElements == null) { if (privateElements == null) {
addError("Cannot expose %s on a standard binder. " addError(
+ "Exposed bindings are only applicable to private binders.", key); "Cannot expose %s on a standard binder. "
+ "Exposed bindings are only applicable to private binders.",
key);
return new AnnotatedElementBuilder() { return new AnnotatedElementBuilder() {
@Override @Override
public void annotatedWith(Class<? extends Annotation> annotationType) { public void annotatedWith(Class<? extends Annotation> annotationType) {}
}
@Override @Override
public void annotatedWith(Annotation annotation) { public void annotatedWith(Annotation annotation) {}
}
}; };
} }
ExposureBuilder<T> builder = ExposureBuilder<T> builder =
new ExposureBuilder<>(this, getElementSource(), MoreTypes.canonicalizeKey(key)); new ExposureBuilder<T>(this, getElementSource(), MoreTypes.canonicalizeKey(key));
privateElements.addExposureBuilder(builder); privateElements.addExposureBuilder(builder);
return builder; return builder;
} }
private ModuleSource getModuleSource(Object module) { private ModuleSource getModuleSource(Class<?> module) {
StackTraceElement[] partialCallStack; StackTraceElement[] partialCallStack;
if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) { if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) {
partialCallStack = getPartialCallStack(new Throwable().getStackTrace()); partialCallStack = getPartialCallStack(new Throwable().getStackTrace());
@ -499,7 +610,7 @@ public final class Elements {
partialCallStack = new StackTraceElement[0]; partialCallStack = new StackTraceElement[0];
} }
if (moduleSource == null) { if (moduleSource == null) {
return new ModuleSource(module, partialCallStack); return new ModuleSource(module, partialCallStack, permitMapConstruction.getPermitMap());
} }
return moduleSource.createChild(module, partialCallStack); return moduleSource.createChild(module, partialCallStack);
} }
@ -518,8 +629,8 @@ public final class Elements {
declaringSource = originalSource.getDeclaringSource(); declaringSource = originalSource.getDeclaringSource();
} }
IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption(); IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption();
if (stackTraceOption == IncludeStackTraceOption.COMPLETE || if (stackTraceOption == IncludeStackTraceOption.COMPLETE
(stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE || (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE
&& declaringSource == null)) { && declaringSource == null)) {
callStack = new Throwable().getStackTrace(); callStack = new Throwable().getStackTrace();
} }
@ -528,10 +639,20 @@ public final class Elements {
} }
if (declaringSource == null) { if (declaringSource == null) {
// So 'source' and 'originalSource' are null otherwise declaringSource has some value // So 'source' and 'originalSource' are null otherwise declaringSource has some value
if (stackTraceOption == IncludeStackTraceOption.COMPLETE || if (stackTraceOption == IncludeStackTraceOption.COMPLETE
stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) { || stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) {
// With the above conditions and assignments 'callStack' is non-null // With the above conditions and assignments 'callStack' is non-null
declaringSource = sourceProvider.get(callStack); StackTraceElement callingSource = sourceProvider.get(callStack);
// If we've traversed past all reasonable sources and into our internal code, then we
// don't know the source.
if (callingSource
.getClassName()
.equals("com.google.inject.internal.InjectorShell$Builder")
&& callingSource.getMethodName().equals("build")) {
declaringSource = SourceProvider.UNKNOWN_SOURCE;
} else {
declaringSource = callingSource;
}
} else { // or if (stackTraceOption == IncludeStackTraceOptions.OFF) } else { // or if (stackTraceOption == IncludeStackTraceOptions.OFF)
// As neither 'declaring source' nor 'call stack' is available use 'module source' // As neither 'declaring source' nor 'call stack' is available use 'module source'
declaringSource = sourceProvider.getFromClassNames(moduleSource.getModuleClassNames()); declaringSource = sourceProvider.getFromClassNames(moduleSource.getModuleClassNames());
@ -539,13 +660,18 @@ public final class Elements {
} }
// Build the binding call stack // Build the binding call stack
return new ElementSource( return new ElementSource(
originalSource, declaringSource, moduleSource, partialCallStack); originalSource,
trustedSource,
declaringSource,
moduleSource,
partialCallStack,
scannerSource);
} }
/** /**
* Removes the {@link #moduleSource} call stack from the beginning of current call stack. It * Removes the {@link #moduleSource} call stack from the beginning of current call stack. It
* also removes the last two elements in order to make {@link #install(Module)} the last call * also removes the last two elements in order to make {@link #install(Module)} the last call in
* in the call stack. * the call stack.
*/ */
private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) { private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) {
int toSkip = 0; int toSkip = 0;
@ -560,6 +686,11 @@ public final class Elements {
return partialCallStack; return partialCallStack;
} }
/** Returns if the binder is in the module scanning phase. */
private boolean moduleScanning() {
return currentScanner != null;
}
@Override @Override
public String toString() { public String toString() {
return "Binder"; return "Binder";

View file

@ -0,0 +1,127 @@
package com.google.inject.spi;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.inject.internal.Messages;
import java.io.Serializable;
import java.util.Formatter;
import java.util.List;
import java.util.Optional;
/**
* Details about a single Guice error and supports formatting itself in the context of other Guice
* errors.
*
* <p>WARNING: The class and its APIs are still experimental and subject to change.
*
* @since 5.0
*/
public abstract class ErrorDetail<SelfT extends ErrorDetail<SelfT>> implements Serializable {
private final String message;
private final ImmutableList<Object> sources;
private final Throwable cause;
protected ErrorDetail(String message, List<Object> sources, Throwable cause) {
this.message = message;
this.sources = ImmutableList.copyOf(sources);
this.cause = cause;
}
/**
* Returns true if this error can be merged with the {@code otherError} and formatted together.
*
* <p>By default this return false and implementations that support merging with other errors
* should override this method.
*/
public boolean isMergeable(ErrorDetail<?> otherError) {
return false;
}
/**
* Formats this error along with other errors that are mergeable with this error.
*
* <p>{@code mergeableErrors} is a list that contains all other errors that are reported in the
* same exception that are considered to be mergable with this error base on result of calling
* {@link #isMergeable}. The list will be empty if non of the other errors are mergable with this
* error.
*
* <p>Formatted error has the following structure:
*
* <ul>
* <li>Summary of the error
* <li>Details about the error such as the source of the error
* <li>Hints for fixing the error if available
* <li>Link to the documentation on this error in greater detail
*
* @param index index for this error
* @param mergeableErrors list of errors that are mergeable with this error
* @param formatter for printing the error message
*/
public final void format(int index, List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
String id = getErrorIdentifier().map(s -> "[" + Messages.redBold(s) + "]: ").orElse("");
formatter.format("%s) %s%s%n", index, id, getMessage());
formatDetail(mergeableErrors, formatter);
// TODO(b/151482394): Output potiential fixes for the error
Optional<String> learnMoreLink = getLearnMoreLink();
if (learnMoreLink.isPresent()) {
formatter.format("%n%s%n", Messages.bold("Learn more:"));
formatter.format(" %s%n", Messages.underline(learnMoreLink.get()));
}
}
/**
* Formats the detail of this error message along with other errors that are mergeable with this
* error. This is called from {@link #format}.
*
* <p>{@code mergeableErrors} is a list that contains all other errors that are reported in the
* same exception that are considered to be mergable with this error base on result of calling
* {@link #isMergeable}. The list will be empty if non of the other errors are mergable with this
* error.
*
* @param mergeableErrors list of errors that are mergeable with this error
* @param formatter for printing the error message
*/
protected abstract void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter);
/**
* Returns an optional link to additional documentation about this error to be included in the
* formatted error message.
*/
protected Optional<String> getLearnMoreLink() {
return Optional.empty();
}
/** Returns an optional string identifier for this error. */
protected Optional<String> getErrorIdentifier() {
return Optional.empty();
}
public String getMessage() {
return message;
}
public List<Object> getSources() {
return sources;
}
public Throwable getCause() {
return cause;
}
@Override
public int hashCode() {
return Objects.hashCode(message, cause, sources);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ErrorDetail)) {
return false;
}
ErrorDetail<?> e = (ErrorDetail<?>) o;
return message.equals(e.message) && Objects.equal(cause, e.cause) && sources.equals(e.sources);
}
/** Returns a new instance of the same {@link ErrorDetail} with updated sources. */
public abstract SelfT withSources(List<Object> newSources);
}

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,40 @@
/*
* 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.spi; package com.google.inject.spi;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.internal.Messages;
import com.google.inject.internal.util.SourceProvider;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.Binder;
import com.google.inject.internal.ErrorId;
import com.google.inject.internal.Errors;
import com.google.inject.internal.GenericErrorDetail;
import com.google.inject.internal.GuiceInternal;
import com.google.inject.internal.util.SourceProvider;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.List;
/** /**
* An error message and the context in which it occured. Messages are usually created internally by * An error message and the context in which it occured. Messages are usually created internally by
* Guice and its extensions. Messages can be created explicitly in a module using {@link * Guice and its extensions. Messages can be created explicitly in a module using {@link
* com.google.inject.Binder#addError(Throwable) addError()} statements: * com.google.inject.Binder#addError(Throwable) addError()} statements:
*
* <pre> * <pre>
* try { * try {
* bindPropertiesFromFile(); * bindPropertiesFromFile();
@ -21,21 +42,36 @@ import static com.google.common.base.Preconditions.checkNotNull;
* addError(e); * addError(e);
* }</pre> * }</pre>
* *
* @author crazybob@google.com (Bob Lee)
*/ */
public final class Message implements Element { public final class Message implements Serializable, Element {
private final ErrorId errorId;
private final ErrorDetail<?> errorDetail;
private final String message; /** @since 5.0 */
public Message(GuiceInternal internalOnly, ErrorId errorId, ErrorDetail<?> errorDetail) {
private final Throwable cause; checkNotNull(internalOnly);
this.errorId = errorId;
private final List<Object> sources; this.errorDetail = errorDetail;
public Message(List<Object> sources, String message, Throwable cause) {
this.sources = ImmutableList.copyOf(sources);
this.message = checkNotNull(message, "message");
this.cause = cause;
} }
private Message(ErrorId errorId, ErrorDetail<?> errorDetail) {
this.errorId = errorId;
this.errorDetail = errorDetail;
}
/** @since 2.0 */
public Message(ErrorId errorId, List<Object> sources, String message, Throwable cause) {
this.errorId = errorId;
this.errorDetail = new GenericErrorDetail(errorId, message, sources, cause);
}
/** @since 2.0 */
public Message(List<Object> sources, String message, Throwable cause) {
this(ErrorId.OTHER, sources, message, cause);
}
/** @since 4.0 */
public Message(String message, Throwable cause) { public Message(String message, Throwable cause) {
this(ImmutableList.of(), message, cause); this(ImmutableList.of(), message, cause);
} }
@ -48,45 +84,57 @@ public final class Message implements Element {
this(ImmutableList.of(), message, null); this(ImmutableList.of(), message, null);
} }
/**
* Returns details about this error message.
*
* @since 5.0
*/
public ErrorDetail<?> getErrorDetail() {
return errorDetail;
}
@Override @Override
public String getSource() { public String getSource() {
List<Object> sources = errorDetail.getSources();
return sources.isEmpty() return sources.isEmpty()
? SourceProvider.UNKNOWN_SOURCE.toString() ? SourceProvider.UNKNOWN_SOURCE.toString()
: Messages.convert(sources.get(sources.size() - 1)).toString(); : Errors.convert(Iterables.getLast(sources)).toString();
} }
/** @since 2.0 */
public List<Object> getSources() { public List<Object> getSources() {
return sources; return errorDetail.getSources();
} }
/** /** Gets the error message text. */
* Gets the error message text.
*/
public String getMessage() { public String getMessage() {
return message; return errorDetail.getMessage();
} }
/** @since 2.0 */
@Override @Override
public <T> T acceptVisitor(ElementVisitor<T> visitor) { public <T> T acceptVisitor(ElementVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
/** /**
* Returns the throwable that caused this message, or {@code null} if this * Returns the throwable that caused this message, or {@code null} if this message was not caused
* message was not caused by a throwable. * by a throwable.
*
* @since 2.0
*/ */
public Throwable getCause() { public Throwable getCause() {
return cause; return errorDetail.getCause();
} }
@Override @Override
public String toString() { public String toString() {
return message; return errorDetail.getMessage();
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(message, cause, sources); return errorDetail.hashCode();
} }
@Override @Override
@ -95,12 +143,39 @@ public final class Message implements Element {
return false; return false;
} }
Message e = (Message) o; Message e = (Message) o;
return Objects.equal(message, e.message) && Objects.equal(cause, e.cause) && Objects.equal(sources, e.sources); return errorDetail.equals(e.errorDetail);
} }
/** @since 2.0 */
@Override @Override
public void applyTo(Binder binder) { public void applyTo(Binder binder) {
binder.withSource(getSource()).addError(this); binder.withSource(getSource()).addError(this);
} }
/**
* Returns a copy of this {@link Message} with its sources replaced.
*
* @since 5.0
*/
public Message withSource(List<Object> newSources) {
return new Message(errorId, errorDetail.withSources(newSources));
}
/**
* When serialized, we convert the error detail to a {@link GenericErrorDetail} with string
* sources. This hurts our formatting, but it guarantees that the receiving end will be able to
* read the message.
*/
private Object writeReplace() throws ObjectStreamException {
Object[] sourcesAsStrings = getSources().toArray();
for (int i = 0; i < sourcesAsStrings.length; i++) {
sourcesAsStrings[i] = Errors.convert(sourcesAsStrings[i]).toString();
}
return new Message(
errorId,
new GenericErrorDetail(
errorId, getMessage(), ImmutableList.copyOf(sourcesAsStrings), getCause()));
}
private static final long serialVersionUID = 0;
} }

View file

@ -1,10 +1,9 @@
package com.google.inject.spi; package com.google.inject.spi;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.internal.Errors; import com.google.inject.internal.Errors;
import com.google.inject.internal.Messages;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Represents a call to {@link Binder#scanModulesForAnnotatedMethods} in a module. * Represents a call to {@link Binder#scanModulesForAnnotatedMethods} in a module.
@ -18,6 +17,7 @@ public final class ModuleAnnotatedMethodScannerBinding implements Element {
this.scanner = checkNotNull(scanner, "scanner"); this.scanner = checkNotNull(scanner, "scanner");
} }
@Override
public Object getSource() { public Object getSource() {
return source; return source;
} }
@ -26,17 +26,23 @@ public final class ModuleAnnotatedMethodScannerBinding implements Element {
return scanner; return scanner;
} }
@Override
public <T> T acceptVisitor(ElementVisitor<T> visitor) { public <T> T acceptVisitor(ElementVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
@Override
public void applyTo(Binder binder) { public void applyTo(Binder binder) {
binder.withSource(getSource()).scanModulesForAnnotatedMethods(scanner); binder.withSource(getSource()).scanModulesForAnnotatedMethods(scanner);
} }
@Override @Override
public String toString() { public String toString() {
return scanner + " which scans for " + scanner.annotationClasses() return scanner
+ " (bound at " + Messages.convert(source) + ")"; + " which scans for "
+ scanner.annotationClasses()
+ " (bound at "
+ Errors.convert(source)
+ ")";
} }
} }

View file

@ -2,30 +2,33 @@ package com.google.inject.spi;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.internal.util.StackTraceElements; import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement;
import java.util.List; import java.util.List;
/** /**
* Associated to a {@link Module module}, provides the module class name, the parent module {@link * Associated to a {@link Module module}, provides the module class name, the parent module {@link
* ModuleSource source}, and the call stack that ends just before the module {@link * ModuleSource source}, and the call stack that ends just before the module {@link
* Module#configure(Binder)} method invocation. * Module#configure(Binder) configure(Binder)} method invocation.
*/ */
final class ModuleSource { final class ModuleSource {
/** /** The class name of module that this {@link ModuleSource} associated to. */
* The class name of module that this {@link ModuleSource} associated to.
*/
private final String moduleClassName; private final String moduleClassName;
/** /** The parent {@link ModuleSource module source}. */
* The parent {@link ModuleSource module source}.
*/
private final ModuleSource parent; private final ModuleSource parent;
/**
* Permit map created by the binder that installed this module.
*
* <p>The permit map is a binder-scoped object, but it's saved here because these maps have to
* outlive the binders that created them in order to be used at injector creation, and there isn't
* a 'BinderSource' object.
*/
private final BindingSourceRestriction.PermitMap permitMap;
/** /**
* The chunk of call stack that starts from the parent module {@link Module#configure(Binder) * The chunk of call stack that starts from the parent module {@link Module#configure(Binder)
* configure(Binder)} call and ends just before the module {@link Module#configure(Binder) * configure(Binder)} call and ends just before the module {@link Module#configure(Binder)
@ -37,31 +40,38 @@ final class ModuleSource {
/** /**
* Creates a new {@link ModuleSource} with a {@literal null} parent. * Creates a new {@link ModuleSource} with a {@literal null} parent.
* *
* @param module the corresponding module * @param moduleClass the corresponding module
* @param partialCallStack the chunk of call stack that starts from the parent module {@link * @param partialCallStack the chunk of call stack that starts from the parent module {@link
* Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link
* Module#configure(Binder) configure(Binder)} method invocation * Module#configure(Binder) configure(Binder)} method invocation
*/ */
ModuleSource(Object module, StackTraceElement[] partialCallStack) { ModuleSource(
this(null, module, partialCallStack); Class<?> moduleClass,
StackTraceElement[] partialCallStack,
BindingSourceRestriction.PermitMap permitMap) {
this(null, moduleClass, partialCallStack, permitMap);
} }
/** /**
* Creates a new {@link ModuleSource} Object. * Creates a new {@link ModuleSource} Object.
* *
* @param parent the parent module {@link ModuleSource source} * @param parent the parent module {@link ModuleSource source}
* @param module the corresponding module * @param moduleClass the corresponding module
* @param partialCallStack the chunk of call stack that starts from the parent module {@link * @param partialCallStack the chunk of call stack that starts from the parent module {@link
* Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link
* Module#configure(Binder) configure(Binder)} method invocation * Module#configure(Binder) configure(Binder)} method invocation
*/ */
private ModuleSource( private ModuleSource(
/* @Nullable */ ModuleSource parent, Object module, StackTraceElement[] partialCallStack) { ModuleSource parent,
Preconditions.checkNotNull(module, "module cannot be null."); Class<?> moduleClass,
StackTraceElement[] partialCallStack,
BindingSourceRestriction.PermitMap permitMap) {
Preconditions.checkNotNull(moduleClass, "module cannot be null.");
Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null.");
this.parent = parent; this.parent = parent;
this.moduleClassName = module.getClass().getName(); this.moduleClassName = moduleClass.getName();
this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack);
this.permitMap = permitMap;
} }
/** /**
@ -83,9 +93,7 @@ final class ModuleSource {
return StackTraceElements.convertToStackTraceElement(partialCallStack); return StackTraceElements.convertToStackTraceElement(partialCallStack);
} }
/** /** Returns the size of partial call stack if stack trace collection is on otherwise zero. */
* Returns the size of partial call stack if stack trace collection is on otherwise zero.
*/
int getPartialCallStackSize() { int getPartialCallStackSize() {
return partialCallStack.length; return partialCallStack.length;
} }
@ -93,18 +101,16 @@ final class ModuleSource {
/** /**
* Creates and returns a child {@link ModuleSource} corresponding to the {@link Module module}. * Creates and returns a child {@link ModuleSource} corresponding to the {@link Module module}.
* *
* @param module the corresponding module * @param moduleClass the corresponding module
* @param partialCallStack the chunk of call stack that starts from the parent module {@link * @param partialCallStack the chunk of call stack that starts from the parent module {@link
* Module#configure(Binder) configure(Binder)} call and ends just before the module {@link * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link
* Module#configure(Binder) configure(Binder)} method invocation * Module#configure(Binder) configure(Binder)} method invocation
*/ */
ModuleSource createChild(Object module, StackTraceElement[] partialCallStack) { ModuleSource createChild(Class<?> moduleClass, StackTraceElement[] partialCallStack) {
return new ModuleSource(this, module, partialCallStack); return new ModuleSource(this, moduleClass, partialCallStack, permitMap);
} }
/** /** Returns the parent module {@link ModuleSource source}. */
* Returns the parent module {@link ModuleSource source}.
*/
ModuleSource getParent() { ModuleSource getParent() {
return parent; return parent;
} }
@ -167,4 +173,9 @@ final class ModuleSource {
} }
return callStack; return callStack;
} }
/** Returns the permit map created by the binder that installed this module. */
BindingSourceRestriction.PermitMap getPermitMap() {
return permitMap;
}
} }

View file

@ -1,18 +1,20 @@
package com.google.inject.spi; package com.google.inject.spi;
import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Messages;
import com.google.inject.matcher.Matcher;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Errors;
import com.google.inject.matcher.Matcher;
/** /**
* Registration of type converters for matching target types. Instances are created * Registration of type converters for matching target types. Instances are created explicitly in a
* explicitly in a module using {@link com.google.inject.Binder#convertToTypes(Matcher, * module using {@link com.google.inject.Binder#convertToTypes(Matcher, TypeConverter)
* TypeConverter) convertToTypes()} statements: * convertToTypes()} statements:
*
* <pre> * <pre>
* convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());</pre> * convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());
* </pre>
* *
*/ */
public final class TypeConverterBinding implements Element { public final class TypeConverterBinding implements Element {
@ -20,13 +22,15 @@ public final class TypeConverterBinding implements Element {
private final Matcher<? super TypeLiteral<?>> typeMatcher; private final Matcher<? super TypeLiteral<?>> typeMatcher;
private final TypeConverter typeConverter; private final TypeConverter typeConverter;
public TypeConverterBinding(Object source, Matcher<? super TypeLiteral<?>> typeMatcher, /** @since 3.0 */
TypeConverter typeConverter) { public TypeConverterBinding(
Object source, Matcher<? super TypeLiteral<?>> typeMatcher, TypeConverter typeConverter) {
this.source = checkNotNull(source, "source"); this.source = checkNotNull(source, "source");
this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher"); this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher");
this.typeConverter = checkNotNull(typeConverter, "typeConverter"); this.typeConverter = checkNotNull(typeConverter, "typeConverter");
} }
@Override
public Object getSource() { public Object getSource() {
return source; return source;
} }
@ -39,17 +43,23 @@ public final class TypeConverterBinding implements Element {
return typeConverter; return typeConverter;
} }
@Override
public <T> T acceptVisitor(ElementVisitor<T> visitor) { public <T> T acceptVisitor(ElementVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }
@Override
public void applyTo(Binder binder) { public void applyTo(Binder binder) {
binder.withSource(getSource()).convertToTypes(typeMatcher, typeConverter); binder.withSource(getSource()).convertToTypes(typeMatcher, typeConverter);
} }
@Override @Override
public String toString() { public String toString() {
return typeConverter + " which matches " + typeMatcher return typeConverter
+ " (bound at " + Messages.convert(source) + ")"; + " which matches "
+ typeMatcher
+ " (bound at "
+ Errors.convert(source)
+ ")";
} }
} }

View file

@ -9,13 +9,12 @@ import com.google.common.collect.Sets;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Binding; import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.PrivateBinder; import com.google.inject.PrivateBinder;
import com.google.inject.PrivateModule; import com.google.inject.PrivateModule;
import com.google.inject.Scope; import com.google.inject.Scope;
import com.google.inject.internal.Messages; import com.google.inject.internal.Errors;
import com.google.inject.spi.DefaultBindingScopingVisitor; import com.google.inject.spi.DefaultBindingScopingVisitor;
import com.google.inject.spi.DefaultElementVisitor; import com.google.inject.spi.DefaultElementVisitor;
import com.google.inject.spi.Element; import com.google.inject.spi.Element;
@ -24,7 +23,6 @@ import com.google.inject.spi.Elements;
import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding;
import com.google.inject.spi.PrivateElements; import com.google.inject.spi.PrivateElements;
import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.ScopeBinding;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -37,18 +35,23 @@ import java.util.Set;
* *
*/ */
public final class Modules { public final class Modules {
private Modules() {}
public static final Module EMPTY_MODULE = new EmptyModule(); public static final Module EMPTY_MODULE = new EmptyModule();
private Modules() { private static class EmptyModule implements Module {
@Override
public void configure(Binder binder) {}
} }
/** /**
* Returns a builder that creates a module that overlays override modules over the given * Returns a builder that creates a module that overlays override modules over the given modules.
* modules. If a key is bound in both sets of modules, only the binding from the override modules * If a key is bound in both sets of modules, only the binding from the override modules is kept.
* is kept. If a single {@link PrivateModule} is supplied or all elements are from * If a single {@link PrivateModule} is supplied or all elements are from a single {@link
* a single {@link PrivateBinder}, then this will overwrite the private bindings. * PrivateBinder}, then this will overwrite the private bindings. Otherwise, private bindings will
* Otherwise, private bindings will not be overwritten unless they are exposed. * not be overwritten unless they are exposed. This can be used to replace the bindings of a
* This can be used to replace the bindings of a production module with test bindings: * production module with test bindings:
*
* <pre> * <pre>
* Module functionalTestModule * Module functionalTestModule
* = Modules.override(new ProductionModule()).with(new TestModule()); * = Modules.override(new ProductionModule()).with(new TestModule());
@ -62,13 +65,20 @@ public final class Modules {
return override(Arrays.asList(modules)); return override(Arrays.asList(modules));
} }
/** @deprecated there's no reason to use {@code Modules.override()} without any arguments. */
@Deprecated
public static OverriddenModuleBuilder override() {
return override(Arrays.asList());
}
/** /**
* Returns a builder that creates a module that overlays override modules over the given * Returns a builder that creates a module that overlays override modules over the given modules.
* modules. If a key is bound in both sets of modules, only the binding from the override modules * If a key is bound in both sets of modules, only the binding from the override modules is kept.
* is kept. If a single {@link PrivateModule} is supplied or all elements are from * If a single {@link PrivateModule} is supplied or all elements are from a single {@link
* a single {@link PrivateBinder}, then this will overwrite the private bindings. * PrivateBinder}, then this will overwrite the private bindings. Otherwise, private bindings will
* Otherwise, private bindings will not be overwritten unless they are exposed. * not be overwritten unless they are exposed. This can be used to replace the bindings of a
* This can be used to replace the bindings of a production module with test bindings: * production module with test bindings:
*
* <pre> * <pre>
* Module functionalTestModule * Module functionalTestModule
* = Modules.override(getProductionModules()).with(getTestModules()); * = Modules.override(getProductionModules()).with(getTestModules());
@ -84,61 +94,38 @@ public final class Modules {
/** /**
* Returns a new module that installs all of {@code modules}. * Returns a new module that installs all of {@code modules}.
*
* <p>Although sometimes helpful, this method is rarely necessary. Most Guice APIs accept multiple
* arguments or (like {@code install()}) can be called repeatedly. Where possible, external APIs
* that require a single module should similarly be adapted to permit multiple modules.
*/ */
public static Module combine(Module... modules) { public static Module combine(Module... modules) {
return combine(ImmutableSet.copyOf(modules)); return combine(ImmutableSet.copyOf(modules));
} }
/** @deprecated there's no need to "combine" one module; just install it directly. */
@Deprecated
public static Module combine(Module module) {
return module;
}
/** @deprecated this method call is effectively a no-op, just remove it. */
@Deprecated
public static Module combine() {
return EMPTY_MODULE;
}
/** /**
* Returns a new module that installs all of {@code modules}. * Returns a new module that installs all of {@code modules}.
*
* <p>Although sometimes helpful, this method is rarely necessary. Most Guice APIs accept multiple
* arguments or (like {@code install()}) can be called repeatedly. Where possible, external APIs
* that require a single module should similarly be adapted to permit multiple modules.
*/ */
public static Module combine(Iterable<? extends Module> modules) { public static Module combine(Iterable<? extends Module> modules) {
return new CombinedModule(modules); return new CombinedModule(modules);
} }
private static Module extractScanners(Iterable<Element> elements) {
final List<ModuleAnnotatedMethodScannerBinding> scanners = Lists.newArrayList();
ElementVisitor<Void> visitor = new DefaultElementVisitor<>() {
@Override
public Void visit(ModuleAnnotatedMethodScannerBinding binding) {
scanners.add(binding);
return null;
}
};
for (Element element : elements) {
element.acceptVisitor(visitor);
}
return new AbstractModule() {
@Override
protected void configure() {
for (ModuleAnnotatedMethodScannerBinding scanner : scanners) {
scanner.applyTo(binder());
}
}
};
}
/**
* See the EDSL example at {@link Modules#override(Module[]) override()}.
*/
public interface OverriddenModuleBuilder {
/**
* See the EDSL example at {@link Modules#override(Module[]) override()}.
*/
Module with(Module... overrides);
/**
* See the EDSL example at {@link Modules#override(Module[]) override()}.
*/
Module with(Iterable<? extends Module> overrides);
}
private static class EmptyModule implements Module {
public void configure(Binder binder) {
}
}
private static class CombinedModule implements Module { private static class CombinedModule implements Module {
final Set<Module> modulesSet; final Set<Module> modulesSet;
@ -146,6 +133,7 @@ public final class Modules {
this.modulesSet = ImmutableSet.copyOf(modules); this.modulesSet = ImmutableSet.copyOf(modules);
} }
@Override
public void configure(Binder binder) { public void configure(Binder binder) {
binder = binder.skipSources(getClass()); binder = binder.skipSources(getClass());
for (Module module : modulesSet) { for (Module module : modulesSet) {
@ -154,17 +142,39 @@ public final class Modules {
} }
} }
/** See the EDSL example at {@link Modules#override(Module[]) override()}. */
public interface OverriddenModuleBuilder {
/** See the EDSL example at {@link Modules#override(Module[]) override()}. */
Module with(Module... overrides);
/** @deprecated there's no reason to use {@code .with()} without any arguments. */
@Deprecated
public Module with();
/** See the EDSL example at {@link Modules#override(Module[]) override()}. */
Module with(Iterable<? extends Module> overrides);
}
private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder { private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder {
private final ImmutableSet<Module> baseModules; private final ImmutableSet<Module> baseModules;
// TODO(diamondm) checkArgument(!baseModules.isEmpty())?
private RealOverriddenModuleBuilder(Iterable<? extends Module> baseModules) { private RealOverriddenModuleBuilder(Iterable<? extends Module> baseModules) {
this.baseModules = ImmutableSet.copyOf(baseModules); this.baseModules = ImmutableSet.copyOf(baseModules);
} }
@Override
public Module with(Module... overrides) { public Module with(Module... overrides) {
return with(Arrays.asList(overrides)); return with(Arrays.asList(overrides));
} }
@Override
public Module with() {
return with(Arrays.asList());
}
@Override
public Module with(Iterable<? extends Module> overrides) { public Module with(Iterable<? extends Module> overrides) {
return new OverrideModule(overrides, baseModules); return new OverrideModule(overrides, baseModules);
} }
@ -174,6 +184,7 @@ public final class Modules {
private final ImmutableSet<Module> overrides; private final ImmutableSet<Module> overrides;
private final ImmutableSet<Module> baseModules; private final ImmutableSet<Module> baseModules;
// TODO(diamondm) checkArgument(!overrides.isEmpty())?
OverrideModule(Iterable<? extends Module> overrides, ImmutableSet<Module> baseModules) { OverrideModule(Iterable<? extends Module> overrides, ImmutableSet<Module> baseModules) {
this.overrides = ImmutableSet.copyOf(overrides); this.overrides = ImmutableSet.copyOf(overrides);
this.baseModules = baseModules; this.baseModules = baseModules;
@ -191,7 +202,8 @@ public final class Modules {
Element element = Iterables.getOnlyElement(baseElements); Element element = Iterables.getOnlyElement(baseElements);
if (element instanceof PrivateElements) { if (element instanceof PrivateElements) {
PrivateElements privateElements = (PrivateElements) element; PrivateElements privateElements = (PrivateElements) element;
PrivateBinder privateBinder = baseBinder.newPrivateBinder().withSource(privateElements.getSource()); PrivateBinder privateBinder =
baseBinder.newPrivateBinder().withSource(privateElements.getSource());
for (Key<?> exposed : privateElements.getExposedKeys()) { for (Key<?> exposed : privateElements.getExposedKeys()) {
privateBinder.withSource(privateElements.getExposedSource(exposed)).expose(exposed); privateBinder.withSource(privateElements.getExposedSource(exposed)).expose(exposed);
} }
@ -203,7 +215,9 @@ public final class Modules {
final Binder binder = baseBinder.skipSources(this.getClass()); final Binder binder = baseBinder.skipSources(this.getClass());
final LinkedHashSet<Element> elements = new LinkedHashSet<>(baseElements); final LinkedHashSet<Element> elements = new LinkedHashSet<>(baseElements);
final Module scannersModule = extractScanners(elements); final Module scannersModule = extractScanners(elements);
final List<Element> overrideElements = Elements.getElements(currentStage(), final List<Element> overrideElements =
Elements.getElements(
currentStage(),
ImmutableList.<Module>builder().addAll(overrides).add(scannersModule).build()); ImmutableList.<Module>builder().addAll(overrides).add(scannersModule).build());
final Set<Key<?>> overriddenKeys = Sets.newHashSet(); final Set<Key<?>> overriddenKeys = Sets.newHashSet();
@ -245,7 +259,9 @@ public final class Modules {
// Record when a scope instance is used in a binding // Record when a scope instance is used in a binding
Scope scope = getScopeInstanceOrNull(binding); Scope scope = getScopeInstanceOrNull(binding);
if (scope != null) { if (scope != null) {
scopeInstancesInUse.computeIfAbsent(scope, k -> Lists.newArrayList()).add(binding.getSource()); scopeInstancesInUse
.computeIfAbsent(scope, k -> Lists.newArrayList())
.add(binding.getSource());
} }
} }
@ -253,8 +269,8 @@ public final class Modules {
} }
void rewrite(Binder binder, PrivateElements privateElements, Set<Key<?>> keysToSkip) { void rewrite(Binder binder, PrivateElements privateElements, Set<Key<?>> keysToSkip) {
PrivateBinder privateBinder = binder.withSource(privateElements.getSource()) PrivateBinder privateBinder =
.newPrivateBinder(); binder.withSource(privateElements.getSource()).newPrivateBinder();
Set<Key<?>> skippedExposes = Sets.newHashSet(); Set<Key<?>> skippedExposes = Sets.newHashSet();
@ -267,8 +283,7 @@ public final class Modules {
} }
for (Element element : privateElements.getElements()) { for (Element element : privateElements.getElements()) {
if (element instanceof Binding if (element instanceof Binding && skippedExposes.remove(((Binding) element).getKey())) {
&& skippedExposes.remove(((Binding) element).getKey())) {
continue; continue;
} }
if (element instanceof PrivateElements) { if (element instanceof PrivateElements) {
@ -304,13 +319,15 @@ public final class Modules {
} else { } else {
List<Object> usedSources = scopeInstancesInUse.get(scopeBinding.getScope()); List<Object> usedSources = scopeInstancesInUse.get(scopeBinding.getScope());
if (usedSources != null) { if (usedSources != null) {
StringBuilder sb = new StringBuilder( StringBuilder sb =
new StringBuilder(
"The scope for @%s is bound directly and cannot be overridden."); "The scope for @%s is bound directly and cannot be overridden.");
sb.append("%n original binding at ").append(Messages.convert(scopeBinding.getSource())); sb.append("%n original binding at " + Errors.convert(scopeBinding.getSource()));
for (Object usedSource : usedSources) { for (Object usedSource : usedSources) {
sb.append("%n bound directly at ").append(Messages.convert(usedSource)); sb.append("%n bound directly at " + Errors.convert(usedSource) + "");
} }
binder.withSource(overideBinding.getSource()) binder
.withSource(overideBinding.getSource())
.addError(sb.toString(), scopeBinding.getAnnotationType().getSimpleName()); .addError(sb.toString(), scopeBinding.getAnnotationType().getSimpleName());
} }
} }
@ -320,7 +337,8 @@ public final class Modules {
} }
private Scope getScopeInstanceOrNull(Binding<?> binding) { private Scope getScopeInstanceOrNull(Binding<?> binding) {
return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<>() { return binding.acceptScopingVisitor(
new DefaultBindingScopingVisitor<Scope>() {
@Override @Override
public Scope visitScope(Scope scope) { public Scope visitScope(Scope scope) {
return scope; return scope;
@ -349,7 +367,34 @@ public final class Modules {
} }
} }
/** Returns a module that will configure the injector to require explicit bindings. */ private static Module extractScanners(Iterable<Element> elements) {
final List<ModuleAnnotatedMethodScannerBinding> scanners = Lists.newArrayList();
ElementVisitor<Void> visitor =
new DefaultElementVisitor<Void>() {
@Override
public Void visit(ModuleAnnotatedMethodScannerBinding binding) {
scanners.add(binding);
return null;
}
};
for (Element element : elements) {
element.acceptVisitor(visitor);
}
return new AbstractModule() {
@Override
protected void configure() {
for (ModuleAnnotatedMethodScannerBinding scanner : scanners) {
scanner.applyTo(binder());
}
}
};
}
/**
* Returns a module that will configure the injector to require explicit bindings.
*
* @since 4.2.3
*/
public static Module requireExplicitBindingsModule() { public static Module requireExplicitBindingsModule() {
return new RequireExplicitBindingsModule(); return new RequireExplicitBindingsModule();
} }
@ -362,8 +407,8 @@ public final class Modules {
} }
/** /**
* Returns a module that will configure the injector to require * Returns a module that will configure the injector to require {@literal @}{@link Inject} on
* {@link Inject} on constructors. * constructors.
* *
* @see Binder#requireAtInjectOnConstructors * @see Binder#requireAtInjectOnConstructors
*/ */
@ -382,6 +427,7 @@ public final class Modules {
* Returns a module that will configure the injector to require an exactly matching binding * Returns a module that will configure the injector to require an exactly matching binding
* annotation. * annotation.
* *
* @since 4.2.3
* @see Binder#requireExactBindingAnnotations * @see Binder#requireExactBindingAnnotations
*/ */
public static Module requireExactBindingAnnotationsModule() { public static Module requireExactBindingAnnotationsModule() {
@ -395,7 +441,9 @@ public final class Modules {
} }
} }
/** Returns a module that will configure the injector to disable circular proxies. */ /**
* Returns a module that will configure the injector to disable circular proxies.
*/
public static Module disableCircularProxiesModule() { public static Module disableCircularProxiesModule() {
return new DisableCircularProxiesModule(); return new DisableCircularProxiesModule();
} }