diff --git a/gradle.properties b/gradle.properties index d4246d9..41c09b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = guice -version = 4.4.2.0 +version = 5.0.1.0 org.gradle.warning.mode = ALL gradle.wrapper.version = 6.6.1 @@ -8,5 +8,5 @@ javax-inject.version = 1 guava.version = 30.1 # test junit.version = 5.7.2 -junit4.version = 4.13 +junit4.version = 4.13.2 log4j.version = 2.14.1 diff --git a/src/main/java/com/google/inject/Key.java b/src/main/java/com/google/inject/Key.java index e6ce3a8..bca885c 100644 --- a/src/main/java/com/google/inject/Key.java +++ b/src/main/java/com/google/inject/Key.java @@ -1,24 +1,20 @@ 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.checkNotNull; import static com.google.inject.internal.Annotations.generateAnnotation; 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. - * Matches the type and annotation at a point of injection. + * Guice uses Key objects to identify a dependency that can be resolved by the Guice {@link + * Injector}. A Guice key consists of an injection type and an optional annotation. * - *

For example, {@code Key.get(Service.class, Transactional.class)} will - * match: + *

For example, {@code Key.get(Service.class, Transactional.class)} will match: * *

  *   {@literal @}Inject
@@ -27,21 +23,20 @@ import static com.google.inject.internal.Annotations.isAllDefaultMethods;
  *   }
  * 
* - *

{@code Key} supports generic types via subclassing just like {@link - * TypeLiteral}. + *

{@code Key} supports generic types via subclassing just like {@link TypeLiteral}. * - *

Keys do not differentiate between primitive types (int, char, etc.) and - * their corresponding wrapper types (Integer, Character, etc.). Primitive - * types will be replaced with their wrapper types when keys are created. + *

Keys do not differentiate between primitive types (int, char, etc.) and their corresponding + * wrapper types (Integer, Character, etc.). Primitive types will be replaced with their wrapper + * types when keys are created. + * + * @author crazybob@google.com (Bob Lee) */ public class Key { private final AnnotationStrategy annotationStrategy; private final TypeLiteral typeLiteral; - private final int hashCode; - // 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 // explanation. @@ -50,32 +45,29 @@ public class Key { /** * Constructs a new key. Derives the type from this class's type parameter. * - *

Clients create an empty anonymous subclass. Doing so embeds the type - * parameter in the anonymous class's type hierarchy so we can reconstitute it - * at runtime despite erasure. + *

Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. * - *

Example usage for a binding of type {@code Foo} annotated with - * {@code @Bar}: + *

Example usage for a binding of type {@code Foo} annotated with {@code @Bar}: * *

{@code new Key(Bar.class) {}}. */ @SuppressWarnings("unchecked") protected Key(Class annotationType) { this.annotationStrategy = strategyFor(annotationType); - this.typeLiteral = MoreTypes.canonicalizeForKey( - (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.typeLiteral = + MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); this.hashCode = computeHashCode(); } /** * Constructs a new key. Derives the type from this class's type parameter. * - *

Clients create an empty anonymous subclass. Doing so embeds the type - * parameter in the anonymous class's type hierarchy so we can reconstitute it - * at runtime despite erasure. + *

Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. * - *

Example usage for a binding of type {@code Foo} annotated with - * {@code @Bar}: + *

Example usage for a binding of type {@code Foo} annotated with {@code @Bar}: * *

{@code new Key(new Bar()) {}}. */ @@ -83,17 +75,17 @@ public class Key { protected Key(Annotation annotation) { // no usages, not test-covered this.annotationStrategy = strategyFor(annotation); - this.typeLiteral = MoreTypes.canonicalizeForKey( - (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.typeLiteral = + MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); this.hashCode = computeHashCode(); } /** * Constructs a new key. Derives the type from this class's type parameter. * - *

Clients create an empty anonymous subclass. Doing so embeds the type - * parameter in the anonymous class's type hierarchy so we can reconstitute it - * at runtime despite erasure. + *

Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. * *

Example usage for a binding of type {@code Foo}: * @@ -102,14 +94,13 @@ public class Key { @SuppressWarnings("unchecked") protected Key() { this.annotationStrategy = NullAnnotationStrategy.INSTANCE; - this.typeLiteral = MoreTypes.canonicalizeForKey( - (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); + this.typeLiteral = + MoreTypes.canonicalizeForKey( + (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); this.hashCode = computeHashCode(); } - /** - * Unsafe. Constructs a key from a manually specified type. - */ + /** Unsafe. Constructs a key from a manually specified type. */ @SuppressWarnings("unchecked") private Key(Type type, AnnotationStrategy annotationStrategy) { this.annotationStrategy = annotationStrategy; @@ -117,154 +108,37 @@ public class Key { this.hashCode = computeHashCode(); } - /** - * Constructs a key from a manually specified type. - */ + /** Constructs a key from a manually specified type. */ private Key(TypeLiteral typeLiteral, AnnotationStrategy annotationStrategy) { this.annotationStrategy = annotationStrategy; this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral); this.hashCode = computeHashCode(); } - /** - * Gets a key for an injection type and an annotation strategy. - */ - static Key get(Class type, AnnotationStrategy annotationStrategy) { - return new Key(type, annotationStrategy); - } - - /** - * Gets a key for an injection type. - */ - public static Key get(Class type) { - return new Key(type, NullAnnotationStrategy.INSTANCE); - } - - /** - * Gets a key for an injection type and an annotation type. - */ - public static Key get(Class type, Class annotationType) { - return new Key(type, strategyFor(annotationType)); - } - - /** - * Gets a key for an injection type and an annotation. - */ - public static Key get(Class type, Annotation annotation) { - return new Key(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 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 Key get(TypeLiteral typeLiteral) { - return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); - } - - /** - * Gets a key for an injection type and an annotation type. - */ - public static Key get(TypeLiteral typeLiteral, Class annotationType) { - return new Key(typeLiteral, strategyFor(annotationType)); - } - - /** - * Gets a key for an injection type and an annotation. - */ - public static Key get(TypeLiteral typeLiteral, Annotation annotation) { - return new Key(typeLiteral, strategyFor(annotation)); - } - - /** - * Gets the strategy for an annotation. - */ - static AnnotationStrategy strategyFor(Annotation annotation) { - checkNotNull(annotation, "annotation"); - Class 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 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 annotationType) { - checkArgument(Annotations.isRetainedAtRuntime(annotationType), - "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", - annotationType.getName()); - } - - private static void ensureIsBindingAnnotation(Class 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. - */ + /** Computes the hash code for this key. */ private int computeHashCode() { return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); } - /** - * Gets the key type. - */ + /** Gets the key type. */ public final TypeLiteral getTypeLiteral() { return typeLiteral; } - /** - * Gets the annotation type. - */ + /** Gets the annotation type. Will be {@code null} if this key lacks an annotation. */ public final Class getAnnotationType() { return annotationStrategy.getAnnotationType(); } /** - * Gets the annotation. + * Gets the annotation instance if available. Will be {@code null} if this key lacks an annotation + * or the key was constructed with a {@code Class}. + * + *

Warning: 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() { return annotationStrategy.getAnnotation(); } @@ -287,9 +161,7 @@ public class Key { return typeLiteral.getRawType(); } - /** - * Gets the key of this key's provider. - */ + /** Gets the key of this key's provider. */ Key> providerKey() { return ofType(typeLiteral.providerType()); } @@ -324,46 +196,178 @@ public class Key { return local; } - /** - * Returns a new key of the specified type with the same annotation as this - * key. - */ - public Key ofType(Class type) { + /** Gets a key for an injection type and an annotation strategy. */ + static Key get(Class type, AnnotationStrategy annotationStrategy) { return new Key(type, annotationStrategy); } + /** Gets a key for an injection type. */ + public static Key get(Class type) { + return new Key(type, NullAnnotationStrategy.INSTANCE); + } + + /** Gets a key for an injection type and an annotation type. */ + public static Key get(Class type, Class annotationType) { + return new Key(type, strategyFor(annotationType)); + } + + /** Gets a key for an injection type and an annotation. */ + public static Key get(Class type, Annotation annotation) { + return new Key(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 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 Key get(TypeLiteral typeLiteral) { + return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); + } + + /** Gets a key for an injection type and an annotation type. */ + public static Key get( + TypeLiteral typeLiteral, Class annotationType) { + return new Key(typeLiteral, strategyFor(annotationType)); + } + + /** Gets a key for an injection type and an annotation. */ + public static Key get(TypeLiteral typeLiteral, Annotation annotation) { + return new Key(typeLiteral, strategyFor(annotation)); + } + /** - * Returns a new key of the specified type with the same annotation as this - * key. + * Returns a new key of the specified type with the same annotation as this key. + * + * @since 3.0 + */ + public Key ofType(Class 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) { - return new Key(type, annotationStrategy); + return new Key<>(type, annotationStrategy); } /** - * Returns a new key of the specified type with the same annotation as this - * key. + * Returns a new key of the specified type with the same annotation as this key. + * + * @since 3.0 */ - public Key ofType(TypeLiteral type) { - return new Key(type, annotationStrategy); + public Key ofType(TypeLiteral type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns a new key of the same type with the specified annotation. + * + *

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 withAnnotation(Class annotationType) { + return new Key(typeLiteral, strategyFor(annotationType)); + } + + /** + * Returns a new key of the same type with the specified annotation. + * + *

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 withAnnotation(Annotation annotation) { + return new Key(typeLiteral, strategyFor(annotation)); } /** * Returns true if this key has annotation attributes. + * + * @since 3.0 */ public boolean hasAttributes() { return annotationStrategy.hasAttributes(); } /** - * Returns this key without annotation attributes, i.e. with only the - * annotation type. + * Returns this key without annotation attributes, i.e. with only the annotation type. + * + * @since 3.0 */ public Key withoutAttributes() { return new Key(typeLiteral, annotationStrategy.withoutAttributes()); } - enum NullAnnotationStrategy implements AnnotationStrategy { + interface AnnotationStrategy { + Annotation getAnnotation(); + + Class getAnnotationType(); + + boolean hasAttributes(); + + AnnotationStrategy withoutAttributes(); + } + + /** Gets the strategy for an annotation. */ + static AnnotationStrategy strategyFor(Annotation annotation) { + checkNotNull(annotation, "annotation"); + Class 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 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 annotationType) { + checkArgument( + Annotations.isRetainedAtRuntime(annotationType), + "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", + annotationType.getName()); + } + + private static void ensureIsBindingAnnotation(Class annotationType) { + checkArgument( + Annotations.isBindingAnnotation(annotationType), + "%s is not a binding annotation. Please annotate it with @BindingAnnotation.", + annotationType.getName()); + } + + static enum NullAnnotationStrategy implements AnnotationStrategy { INSTANCE; @Override @@ -392,17 +396,6 @@ public class Key { } } - interface AnnotationStrategy { - - Annotation getAnnotation(); - - Class getAnnotationType(); - - boolean hasAttributes(); - - AnnotationStrategy withoutAttributes(); - } - // this class not test-covered static class AnnotationInstanceStrategy implements AnnotationStrategy { @@ -460,8 +453,7 @@ public class Key { // Keep the instance around if we have it so the client can request it. final Annotation annotation; - AnnotationTypeStrategy(Class annotationType, - Annotation annotation) { + AnnotationTypeStrategy(Class annotationType, Annotation annotation) { this.annotationType = checkNotNull(annotationType, "annotation type"); this.annotation = annotation; } diff --git a/src/main/java/com/google/inject/RestrictedBindingSource.java b/src/main/java/com/google/inject/RestrictedBindingSource.java new file mode 100644 index 0000000..216e165 --- /dev/null +++ b/src/main/java/com/google/inject/RestrictedBindingSource.java @@ -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. + * + *

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. + * + *

There are two kinds of binding source: + * + *

    + *
  1. 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. + *
  2. 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. + *
+ * + *

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. + * + *

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. + * + *

Example usage: + * + *

{@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() { ... }
+ * }
+ * }
+ * + * @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. + * + *

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[] permits(); + + /** + * Exempt modules whose fully qualified class names match this regex. + * + *

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). + * + *

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; +} diff --git a/src/main/java/com/google/inject/assistedinject/FactoryModuleBuilder.java b/src/main/java/com/google/inject/assistedinject/FactoryModuleBuilder.java index 9da0198..c0ef9f0 100644 --- a/src/main/java/com/google/inject/assistedinject/FactoryModuleBuilder.java +++ b/src/main/java/com/google/inject/assistedinject/FactoryModuleBuilder.java @@ -5,14 +5,15 @@ import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; - import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandles; /** * Provides a factory that combines the caller's arguments with injector-supplied values to * construct objects. * *

Defining a factory

+ * * 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. * @@ -24,6 +25,7 @@ import java.lang.annotation.Annotation; * or newPayment. * *

Creating a type that accepts factory parameters

+ * * {@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 * should have parameters that match each of the factory method's parameters. Each factory-supplied @@ -42,10 +44,10 @@ import java.lang.annotation.Annotation; * } * *

Multiple factory methods for the same type

+ * * 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 - * {@literal @}{@link AssistedInject}, in order to match the different parameters types of the - * factory methods. + * constructors in your concrete class, each constructor marked with with {@literal @}{@link + * AssistedInject}, in order to match the different parameters types of the factory methods. * *
public interface PaymentFactory {
  *    Payment create(Date startDate, Money amount);
@@ -72,8 +74,8 @@ import java.lang.annotation.Annotation;
  * }
* *

Configuring simple factories

- * 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: * *
install(new FactoryModuleBuilder()
  *     .implement(Payment.class, RealPayment.class)
@@ -83,8 +85,9 @@ import java.lang.annotation.Annotation;
  * factory cannot be used until the injector has been initialized.
  *
  * 

Configuring complex factories

- * Factories can create an arbitrary number of objects, one per each method. Each factory - * method can be configured using .implement. + * + * Factories can create an arbitrary number of objects, one per each method. Each factory method can + * be configured using .implement. * *
public interface OrderFactory {
  *    Payment create(Date startDate, Money amount);
@@ -99,220 +102,223 @@ import java.lang.annotation.Annotation;
  *     // excluding .implement for Shipment means the implementation class
  *     // will be 'Shipment' itself, which is legal if it's not an interface.
  *     .implement(Receipt.class, RealReceipt.class)
- *     .build(OrderFactory.class));
+ *     .build(OrderFactory.class));
+ * *
* *

Using the factory

- * 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. * *
public class PaymentAction {
- * {@literal @}Inject private PaymentFactory paymentFactory;
+ *   {@literal @}Inject private PaymentFactory paymentFactory;
  *
- * public void doPayment(Money amount) {
- * Payment payment = paymentFactory.create(new Date(), amount);
- * payment.apply();
- * }
+ *   public void doPayment(Money amount) {
+ *     Payment payment = paymentFactory.create(new Date(), amount);
+ *     payment.apply();
+ *   }
  * }
* *

Making parameter types distinct

- * 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 - * parameters. The names must be applied to the factory method's parameters: + * + * 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 parameters. + * The names must be applied to the factory method's parameters: * *
public interface PaymentFactory {
- * Payment create(
- * {@literal @}Assisted("startDate") Date startDate,
- * {@literal @}Assisted("dueDate") Date dueDate,
- * Money amount);
+ *   Payment create(
+ *       {@literal @}Assisted("startDate") Date startDate,
+ *       {@literal @}Assisted("dueDate") Date dueDate,
+ *       Money amount);
  * } 
* * ...and to the concrete type's constructor parameters: * *
public class RealPayment implements Payment {
- * {@literal @}Inject
- * public RealPayment(
- * CreditService creditService,
- * AuthService authService,
- * {@literal @}Assisted("startDate") Date startDate,
- * {@literal @}Assisted("dueDate") Date dueDate,
- * {@literal @}Assisted Money amount) {
- * ...
- * }
+ *   {@literal @}Inject
+ *   public RealPayment(
+ *      CreditService creditService,
+ *      AuthService authService,
+ *      {@literal @}Assisted("startDate") Date startDate,
+ *      {@literal @}Assisted("dueDate") Date dueDate,
+ *      {@literal @}Assisted Money amount) {
+ *     ...
+ *   }
  * }
* *

Values are created by Guice

+ * * 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 * returned. * *

More configuration options

+ * * 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: - * If you just want to return the types specified in the factory, do not configure any + * + *

If you just want to return the types specified in the factory, do not configure any * implementations: * *

public interface FruitFactory {
- * Apple getApple(Color color);
+ *   Apple getApple(Color color);
  * }
  * ...
  * protected void configure() {
- * install(new FactoryModuleBuilder().build(FruitFactory.class));
+ *   install(new FactoryModuleBuilder().build(FruitFactory.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 + * + *

To return two different implementations for the same interface from your factory, use binding * annotations on your return types: * *

interface CarFactory {
- * {@literal @}Named("fast") Car getFastCar(Color color);
- * {@literal @}Named("clean") Car getCleanCar(Color color);
+ *   {@literal @}Named("fast") Car getFastCar(Color color);
+ *   {@literal @}Named("clean") Car getCleanCar(Color color);
  * }
  * ...
  * protected void configure() {
- * install(new FactoryModuleBuilder()
- * .implement(Car.class, Names.named("fast"), Porsche.class)
- * .implement(Car.class, Names.named("clean"), Prius.class)
- * .build(CarFactory.class));
+ *   install(new FactoryModuleBuilder()
+ *       .implement(Car.class, Names.named("fast"), Porsche.class)
+ *       .implement(Car.class, Names.named("clean"), Prius.class)
+ *       .build(CarFactory.class));
  * }
* *

Implementation limitations

- * 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 { 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 FactoryModuleBuilder implement(Class source, Class target) { return implement(source, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ public FactoryModuleBuilder implement(Class source, TypeLiteral target) { return implement(TypeLiteral.get(source), target); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ public FactoryModuleBuilder implement(TypeLiteral source, Class target) { return implement(source, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(TypeLiteral source, - TypeLiteral target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + TypeLiteral source, TypeLiteral target) { return implement(Key.get(source), target); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(Class source, Annotation annotation, - Class target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + Class source, Annotation annotation, Class target) { return implement(source, annotation, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(Class source, Annotation annotation, - TypeLiteral target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + Class source, Annotation annotation, TypeLiteral target) { return implement(TypeLiteral.get(source), annotation, target); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(TypeLiteral source, Annotation annotation, - Class target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + TypeLiteral source, Annotation annotation, Class target) { return implement(source, annotation, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(TypeLiteral source, Annotation annotation, - TypeLiteral target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + TypeLiteral source, Annotation annotation, TypeLiteral target) { return implement(Key.get(source, annotation), target); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(Class source, - Class annotationType, Class target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + Class source, Class annotationType, Class target) { return implement(source, annotationType, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(Class source, - Class annotationType, TypeLiteral target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + Class source, + Class annotationType, + TypeLiteral target) { return implement(TypeLiteral.get(source), annotationType, target); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(TypeLiteral source, - Class annotationType, Class target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + TypeLiteral source, + Class annotationType, + Class target) { return implement(source, annotationType, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ - public FactoryModuleBuilder implement(TypeLiteral source, - Class annotationType, TypeLiteral target) { + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ + public FactoryModuleBuilder implement( + TypeLiteral source, + Class annotationType, + TypeLiteral 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 FactoryModuleBuilder implement(Key source, Class target) { return implement(source, TypeLiteral.get(target)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ public FactoryModuleBuilder implement(Key source, TypeLiteral target) { bindings.addBinding(source, target); 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). + * + *

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 FactoryModuleBuilder withLookups(MethodHandles.Lookup lookups) { + this.lookups = lookups; + return this; + } + + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ public Module build(Class factoryInterface) { return build(TypeLiteral.get(factoryInterface)); } - /** - * See the factory configuration examples at {@link FactoryModuleBuilder}. - */ + /** See the factory configuration examples at {@link FactoryModuleBuilder}. */ public Module build(TypeLiteral factoryInterface) { return build(Key.get(factoryInterface)); } - public Module build(final Key factoryInterface) { return new AbstractModule() { @Override protected void configure() { - Provider provider = new FactoryProvider2(factoryInterface, bindings); - bind(factoryInterface).toProvider(provider); + Provider provider = new FactoryProvider2<>(factoryInterface, bindings, lookups); + binder().skipSources(this.getClass()).bind(factoryInterface).toProvider(provider); } }; } diff --git a/src/main/java/com/google/inject/assistedinject/FactoryProvider.java b/src/main/java/com/google/inject/assistedinject/FactoryProvider.java index 5d82cb2..426bf72 100644 --- a/src/main/java/com/google/inject/assistedinject/FactoryProvider.java +++ b/src/main/java/com/google/inject/assistedinject/FactoryProvider.java @@ -1,5 +1,7 @@ package com.google.inject.assistedinject; +import static com.google.inject.internal.Annotations.getKey; + import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -13,11 +15,9 @@ import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.internal.Errors; import com.google.inject.internal.ErrorsException; -import com.google.inject.internal.Messages; import com.google.inject.spi.Dependency; import com.google.inject.spi.HasDependencies; import com.google.inject.spi.Message; - import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; @@ -28,8 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static com.google.inject.internal.Annotations.getKey; - /** * Obsolete. Prefer {@link FactoryModuleBuilder} for its more concise API and * additional capability. @@ -38,20 +36,25 @@ import static com.google.inject.internal.Annotations.getKey; * construct objects. * *

Defining a factory

+ * * 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. + * *
public interface PaymentFactory {
  *   Payment create(Date startDate, Money amount);
  * }
+ * * You can name your factory methods whatever you like, such as create, createPayment * or newPayment. * *

Creating a type that accepts factory parameters

+ * * {@code constructedType} is a concrete class with an {@literal @}{@link 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 parameter - * requires an {@literal @}{@link Assisted} annotation. This serves to document that the parameter - * is not bound by your application's modules. + * constructor. In addition to injector-supplied parameters, the constructor should have parameters + * that match each of the factory method's parameters. Each factory-supplied parameter requires an + * {@literal @}{@link Assisted} annotation. This serves to document that the parameter is not bound + * by your application's modules. + * *
public class RealPayment implements Payment {
  *   {@literal @}Inject
  *   public RealPayment(
@@ -62,19 +65,25 @@ import static com.google.inject.internal.Annotations.getKey;
  *     ...
  *   }
  * }
+ * * Any parameter that permits a null value should also be annotated {@code @Nullable}. * *

Configuring factories

+ * * In your {@link com.google.inject.Module module}, bind the factory interface to the returned * factory: + * *
bind(PaymentFactory.class).toProvider(
  *     FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
+ * * 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. * *

Using the factory

- * 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. + * *
public class PaymentAction {
  *   {@literal @}Inject private PaymentFactory paymentFactory;
  *
@@ -85,9 +94,10 @@ import static com.google.inject.internal.Annotations.getKey;
  * }
* *

Making parameter types distinct

- * 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 - * parameters. The names must be applied to the factory method's parameters: + * + * 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 parameters. + * The names must be applied to the factory method's parameters: * *
public interface PaymentFactory {
  *   Payment create(
@@ -95,7 +105,9 @@ import static com.google.inject.internal.Annotations.getKey;
  *       {@literal @}Assisted("dueDate") Date dueDate,
  *       Money amount);
  * } 
+ * * ...and to the concrete type's constructor parameters: + * *
public class RealPayment implements Payment {
  *   {@literal @}Inject
  *   public RealPayment(
@@ -109,46 +121,42 @@ import static com.google.inject.internal.Annotations.getKey;
  * }
* *

Values are created by Guice

+ * * 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 * returned. * *

Backwards compatibility using {@literal @}AssistedInject

+ * * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. * *

Instead of matching factory method arguments to constructor parameters using their names, the - * parameters are matched by their order. The first factory method argument is - * used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no - * effect. + * parameters are matched by their order. The first factory method argument is used + * for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no effect. * *

Returned values are not created by Guice. These types are not eligible for * method interception. They do receive post-construction member injection. * * @param 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 public class FactoryProvider implements Provider, HasDependencies { - /* - * This class implements the old @AssistedInject implementation that manually matches constructors - * to factory methods. The new child injector implementation lives in FactoryProvider2. - */ + /* + * This class implements the old @AssistedInject implementation that manually matches constructors + * to factory methods. The new child injector implementation lives in FactoryProvider2. + */ + + private Injector injector; private final TypeLiteral factoryType; private final TypeLiteral implementationType; private final Map> factoryMethodToConstructor; - private Injector injector; - - private FactoryProvider(TypeLiteral factoryType, - TypeLiteral implementationType, - Map> factoryMethodToConstructor) { - this.factoryType = factoryType; - this.implementationType = implementationType; - this.factoryMethodToConstructor = factoryMethodToConstructor; - checkDeclaredExceptionsMatch(); - } public static Provider newFactory(Class factoryType, Class implementationType) { return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); @@ -156,8 +164,8 @@ public class FactoryProvider implements Provider, HasDependencies { public static Provider newFactory( TypeLiteral factoryType, TypeLiteral implementationType) { - Map> factoryMethodToConstructor - = createMethodMapping(factoryType, implementationType); + Map> factoryMethodToConstructor = + createMethodMapping(factoryType, implementationType); if (!factoryMethodToConstructor.isEmpty()) { return new FactoryProvider(factoryType, implementationType, factoryMethodToConstructor); @@ -171,8 +179,8 @@ public class FactoryProvider implements Provider, HasDependencies { try { for (Method method : factoryType.getRawType().getMethods()) { - Key returnType = getKey(factoryType.getReturnType(method), method, - method.getAnnotations(), errors); + Key returnType = + getKey(factoryType.getReturnType(method), method, method.getAnnotations(), errors); if (!implementationKey.equals(returnType)) { collector.addBinding(returnType, implementationType); } @@ -181,83 +189,18 @@ public class FactoryProvider implements Provider, HasDependencies { throw new ConfigurationException(e.getErrors().getMessages()); } - return new FactoryProvider2(Key.get(factoryType), collector); + return new FactoryProvider2(Key.get(factoryType), collector, /* userLookups= */ null); } } - private static Map> createMethodMapping( - TypeLiteral factoryType, TypeLiteral implementationType) { - List> 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> paramsToConstructor = Maps.newHashMap(); - - for (AssistedConstructor c : constructors) { - if (paramsToConstructor.containsKey(c.getAssistedParameters())) { - throw new RuntimeException("Duplicate constructor, " + c); - } - paramsToConstructor.put(c.getAssistedParameters(), c); - } - - Map> 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 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)))); + private FactoryProvider( + TypeLiteral factoryType, + TypeLiteral implementationType, + Map> factoryMethodToConstructor) { + this.factoryType = factoryType; + this.implementationType = implementationType; + this.factoryMethodToConstructor = factoryMethodToConstructor; + checkDeclaredExceptionsMatch(); } @Inject @@ -269,8 +212,10 @@ public class FactoryProvider implements Provider, HasDependencies { // this is lame - we're not using the proper mechanism to add an // error to the injector. Throughout this class we throw exceptions // to add errors, which isn't really the best way in Guice - throw newConfigurationException("Parameter of type '%s' is not injectable or annotated " - + "with @Assisted for Constructor '%s'", p, c); + throw newConfigurationException( + "Parameter of type '%s' is not injectable or annotated " + + "with @Assisted for Constructor '%s'", + p, c); } } } @@ -281,8 +226,10 @@ public class FactoryProvider implements Provider, HasDependencies { for (Class constructorException : entry.getValue().getDeclaredExceptions()) { if (!isConstructorExceptionCompatibleWithFactoryExeception( constructorException, entry.getKey().getExceptionTypes())) { - throw newConfigurationException("Constructor %s declares an exception, but no compatible " - + "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); + throw newConfigurationException( + "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 implements Provider, HasDependencies { return parameter.isBound(injector); } + private static Map> createMethodMapping( + TypeLiteral factoryType, TypeLiteral implementationType) { + List> 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> paramsToConstructor = Maps.newHashMap(); + + for (AssistedConstructor c : constructors) { + if (paramsToConstructor.containsKey(c.getAssistedParameters())) { + throw new RuntimeException("Duplicate constructor, " + c); + } + paramsToConstructor.put(c.getAssistedParameters(), c); + } + + Map> 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 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> getDependencies() { List> dependencies = Lists.newArrayList(); for (AssistedConstructor constructor : factoryMethodToConstructor.values()) { @@ -314,51 +337,55 @@ public class FactoryProvider implements Provider, HasDependencies { return ImmutableSet.copyOf(dependencies); } + @Override public F get() { - InvocationHandler invocationHandler = new InvocationHandler() { - public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable { - // pass methods from Object.class to the proxy - if (method.getDeclaringClass().equals(Object.class)) { - if ("equals".equals(method.getName())) { - return proxy == creationArgs[0]; - } else if ("hashCode".equals(method.getName())) { - return System.identityHashCode(proxy); - } else { - return method.invoke(this, creationArgs); + InvocationHandler invocationHandler = + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] creationArgs) + throws Throwable { + // pass methods from Object.class to the proxy + if (method.getDeclaringClass().equals(Object.class)) { + if ("equals".equals(method.getName())) { + return proxy == creationArgs[0]; + } else if ("hashCode".equals(method.getName())) { + return System.identityHashCode(proxy); + } else { + return method.invoke(this, creationArgs); + } + } + + AssistedConstructor constructor = factoryMethodToConstructor.get(method); + Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); + Object objectToReturn = constructor.newInstance(constructorArgs); + injector.injectMembers(objectToReturn); + return objectToReturn; } - } - AssistedConstructor constructor = factoryMethodToConstructor.get(method); - Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); - Object objectToReturn = constructor.newInstance(constructorArgs); - injector.injectMembers(objectToReturn); - return objectToReturn; - } + public Object[] gatherArgsForConstructor( + AssistedConstructor constructor, Object[] factoryArgs) { + int numParams = constructor.getAllParameters().size(); + int argPosition = 0; + Object[] result = new Object[numParams]; - public Object[] gatherArgsForConstructor( - AssistedConstructor constructor, - Object[] factoryArgs) { - int numParams = constructor.getAllParameters().size(); - int argPosition = 0; - Object[] result = new Object[numParams]; - - for (int i = 0; i < numParams; i++) { - Parameter parameter = constructor.getAllParameters().get(i); - if (parameter.isProvidedByFactory()) { - result[i] = factoryArgs[argPosition]; - argPosition++; - } else { - result[i] = parameter.getValue(injector); + for (int i = 0; i < numParams; i++) { + Parameter parameter = constructor.getAllParameters().get(i); + if (parameter.isProvidedByFactory()) { + result[i] = factoryArgs[argPosition]; + argPosition++; + } else { + result[i] = parameter.getValue(injector); + } + } + return result; } - } - return result; - } - }; + }; @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class - Class factoryRawType = (Class) factoryType.getRawType(); - return factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(), - new Class[]{factoryRawType}, invocationHandler)); + Class factoryRawType = (Class) (Class) factoryType.getRawType(); + return factoryRawType.cast( + Proxy.newProxyInstance( + factoryRawType.getClassLoader(), new Class[] {factoryRawType}, invocationHandler)); } @Override @@ -375,4 +402,8 @@ public class FactoryProvider implements Provider, HasDependencies { return factoryType.equals(other.factoryType) && implementationType.equals(other.implementationType); } -} \ No newline at end of file + + private static ConfigurationException newConfigurationException(String format, Object... args) { + return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); + } +} diff --git a/src/main/java/com/google/inject/assistedinject/FactoryProvider2.java b/src/main/java/com/google/inject/assistedinject/FactoryProvider2.java index ca5002e..f1fb238 100644 --- a/src/main/java/com/google/inject/assistedinject/FactoryProvider2.java +++ b/src/main/java/com/google/inject/assistedinject/FactoryProvider2.java @@ -1,5 +1,8 @@ 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.Objects; 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.Toolable; import com.google.inject.util.Providers; - 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.InvocationHandler; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; @@ -51,85 +55,173 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; 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 - * create values. + * The newer implementation of factory provider. This implementation uses a child injector to create + * 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 implements InvocationHandler, - ProviderWithExtensionVisitor, HasDependencies, AssistedInjectBinding { +final class FactoryProvider2 + implements InvocationHandler, + ProviderWithExtensionVisitor, + HasDependencies, + AssistedInjectBinding { - /** - * 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(); // use the logger under a well-known name, not FactoryProvider2 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() { - public String value() { - return ""; - } + private static boolean allowLookupReflection = true; - public Class annotationType() { - return Assisted.class; - } + /** if a factory method parameter isn't annotated, it gets this annotation. */ + static final Assisted DEFAULT_ANNOTATION = + new Assisted() { + @Override + public String value() { + return ""; + } - @Override - public boolean equals(Object o) { - return o instanceof Assisted && ((Assisted) o).value().isEmpty(); - } + @Override + public Class annotationType() { + return Assisted.class; + } - @Override - public int hashCode() { - return 127 * "value".hashCode() ^ "".hashCode(); + @Override + public boolean equals(Object o) { + return o instanceof Assisted && ((Assisted) o).value().isEmpty(); + } + + @Override + public int hashCode() { + return 127 * "value".hashCode() ^ "".hashCode(); + } + + @Override + public String toString() { + return "@" + + Assisted.class.getName() + + "(" + + Annotations.memberValueString("value", "") + + ")"; + } + }; + + /** 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> paramTypes; + /** the type of the implementation constructed */ + final TypeLiteral implementationType; + + /** All non-assisted dependencies required by this method. */ + final Set> 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 providers; + /** used to perform optimized factory creations. */ + volatile Binding cachedBinding; // TODO: volatile necessary? + + AssistData( + Constructor constructor, + Key returnType, + ImmutableList> paramTypes, + TypeLiteral implementationType, + Method factoryMethod, + Set> dependencies, + boolean optimized, + List 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 "@" + Assisted.class.getName() + "(value=\"\")"; + 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(); } - }; - /** - * Mapping from method to the data about how the method will be assisted. - */ + + @Override + public Set> 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 assistDataByMethod; - /** - * Mapping from method to method handle, for generated default methods. - */ - private final ImmutableMap methodHandleByMethod; - /** - * the factory interface, implemented and provided - */ - private final F factory; - /** - * The key that this is bound to. - */ - private final Key factoryKey; - /** - * The binding collector, for equality/hashing purposes. - */ - private final BindingCollector collector; - /** - * the hosting injector, or null if we haven't been initialized yet - */ + + /** Mapping from method to method handle, for generated default methods. */ + private final ImmutableMap methodHandleByMethod; + + /** the hosting injector, or null if we haven't been initialized yet */ private Injector injector; + /** the factory interface, implemented and provided */ + private final F factory; + + /** The key that this is bound to. */ + private final Key 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 collector binding configuration that maps method return types to - * implementation types. + * @param collector binding configuration that maps method return types to implementation types. + * @param userLookups user provided lookups, optional. */ - FactoryProvider2(Key factoryKey, BindingCollector collector) { + FactoryProvider2( + Key factoryKey, BindingCollector collector, MethodHandles.Lookup userLookups) { this.factoryKey = factoryKey; this.collector = collector; @@ -137,7 +229,7 @@ final class FactoryProvider2 implements InvocationHandler, Errors errors = new Errors(); @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class - Class factoryRawType = (Class) (Class) factoryType.getRawType(); + Class factoryRawType = (Class) (Class) factoryType.getRawType(); try { if (!factoryRawType.isInterface()) { @@ -149,6 +241,11 @@ final class FactoryProvider2 implements InvocationHandler, ImmutableMap.Builder assistDataBuilder = ImmutableMap.builder(); // TODO: also grab methods from superinterfaces for (Method method : factoryRawType.getMethods()) { + // Skip static methods + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + // Skip default methods that java8 may have created. if (isDefault(method) && (method.isBridge() || method.isSynthetic())) { // Even synthetic default methods need the return type validation... @@ -162,7 +259,8 @@ final class FactoryProvider2 implements InvocationHandler, TypeLiteral returnTypeLiteral = factoryType.getReturnType(method); Key returnType; try { - returnType = Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors); + returnType = + Annotations.getKey(returnTypeLiteral, method, method.getAnnotations(), errors); } catch (ConfigurationException ce) { // 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. @@ -182,7 +280,8 @@ final class FactoryProvider2 implements InvocationHandler, Class underlylingType = paramKey.getTypeLiteral().getRawType(); if (underlylingType.equals(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]", p, paramKey, method); } @@ -198,7 +297,8 @@ final class FactoryProvider2 implements InvocationHandler, Class scope = Annotations.findScopeAnnotation(errors, implementation.getRawType()); 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" + " remove the scope annotation.", scope, implementation.getRawType(), factoryType); @@ -207,7 +307,8 @@ final class FactoryProvider2 implements InvocationHandler, InjectionPoint ctorInjectionPoint; try { ctorInjectionPoint = - findMatchingConstructorInjectionPoint(method, returnType, implementation, immutableParamList); + findMatchingConstructorInjectionPoint( + method, returnType, implementation, immutableParamList); } catch (ErrorsException ee) { errors.merge(ee.getErrors()); continue; @@ -231,36 +332,74 @@ final class FactoryProvider2 implements InvocationHandler, optimized = true; } - AssistData data = new AssistData(constructor, - returnType, - immutableParamList, - implementation, - method, - removeAssistedDeps(deps), - optimized, - providers); + AssistData data = + new AssistData( + constructor, + returnType, + immutableParamList, + implementation, + method, + removeAssistedDeps(deps), + optimized, + providers); assistDataBuilder.put(method, data); } - factory = factoryRawType.cast(Proxy.newProxyInstance( - factoryRawType.getClassLoader(), new Class[]{factoryRawType}, this)); + factory = + factoryRawType.cast( + Proxy.newProxyInstance( + factoryRawType.getClassLoader(), new Class[] {factoryRawType}, this)); // 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 // signatures. Map dataSoFar = assistDataBuilder.build(); - ImmutableMap.Builder methodHandleBuilder = ImmutableMap.builder(); + ImmutableMap.Builder methodHandleBuilder = ImmutableMap.builder(); + boolean warnedAboutUserLookups = false; for (Map.Entry 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(); - 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) { methodHandleBuilder.put(defaultMethod, handle); } else { + // TODO: remove this workaround when Java8 support is dropped boolean foundMatch = false; for (Method otherMethod : otherMethods.get(defaultMethod.getName())) { if (dataSoFar.containsKey(otherMethod) && isCompatible(defaultMethod, otherMethod)) { 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." + " Unable to create factory. As a workaround, remove the override" + " so javac stops generating a default method.", @@ -277,7 +416,8 @@ final class FactoryProvider2 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()) { throw errors.toException(); } @@ -297,23 +437,6 @@ final class FactoryProvider2 implements InvocationHandler, == 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) { if (!src.getReturnType().isAssignableFrom(dst.getReturnType())) { return false; @@ -331,32 +454,37 @@ final class FactoryProvider2 implements InvocationHandler, return true; } + @Override public F get() { return factory; } + @Override public Set> getDependencies() { - Set> combinedDeps = new HashSet>(); + Set> combinedDeps = new HashSet<>(); for (AssistData data : assistDataByMethod.values()) { combinedDeps.addAll(data.dependencies); } return ImmutableSet.copyOf(combinedDeps); } + @Override public Key getKey() { return factoryKey; } // Safe cast because values are typed to AssistedData, which is an AssistedMethod, and // the collection is immutable. + @Override @SuppressWarnings("unchecked") public Collection getAssistedMethods() { return (Collection) (Collection) assistDataByMethod.values(); } + @Override @SuppressWarnings("unchecked") - public V acceptExtensionVisitor(BindingTargetVisitor visitor, - ProviderInstanceBinding binding) { + public V acceptExtensionVisitor( + BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof AssistedInjectTargetVisitor) { return ((AssistedInjectTargetVisitor) visitor).visit((AssistedInjectBinding) this); } @@ -366,7 +494,8 @@ final class FactoryProvider2 implements InvocationHandler, private void validateFactoryReturnType(Errors errors, Class returnType, Class factoryType) { if (Modifier.isPublic(factoryType.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. " + "Please either make the factory non-public or the return type public.", factoryType, returnType); @@ -380,8 +509,8 @@ final class FactoryProvider2 implements InvocationHandler, private boolean isTypeNotSpecified(TypeLiteral typeLiteral, ConfigurationException ce) { Collection messages = ce.getErrorMessages(); if (messages.size() == 1) { - Message msg = Iterables.getOnlyElement( - new Errors().keyNotFullySpecified(typeLiteral).getMessages()); + Message msg = + Iterables.getOnlyElement(new Errors().keyNotFullySpecified(typeLiteral).getMessages()); return msg.getMessage().equals(Iterables.getOnlyElement(messages).getMessage()); } else { return false; @@ -389,11 +518,11 @@ final class FactoryProvider2 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 - * match the parameters (in any order) listed in the method. Otherwise, if no - * {@link AssistedInject} constructors exist, this will default to looking for an - * {@literal @}{@link Inject} constructor. + * match the parameters (in any order) listed in the method. Otherwise, if no {@link + * AssistedInject} constructors exist, this will default to looking for an {@literal @}{@link + * Inject} constructor. */ private InjectionPoint findMatchingConstructorInjectionPoint( Method method, Key returnType, TypeLiteral implementation, List> paramList) @@ -429,12 +558,11 @@ final class FactoryProvider2 implements InvocationHandler, anyAssistedInjectConstructors = true; if (constructorHasMatchingParams(implementation, constructor, paramList, errors)) { if (matchingConstructor != null) { - errors - .addMessage( - "%s has more than one constructor annotated with @AssistedInject" - + " that matches the parameters in method %s. Unable to create " - + "AssistedInject factory.", - implementation, method); + errors.addMessage( + "%s has more than one constructor annotated with @AssistedInject" + + " that matches the parameters in method %s. Unable to create " + + "AssistedInject factory.", + implementation, method); throw errors.toException(); } else { matchingConstructor = constructor; @@ -444,7 +572,7 @@ final class FactoryProvider2 implements InvocationHandler, } if (!anyAssistedInjectConstructors) { - // If none existed, use @Inject. + // If none existed, use @Inject or a no-arg constructor. try { return InjectionPoint.forConstructorOf(implementation); } catch (ConfigurationException e) { @@ -456,8 +584,9 @@ final class FactoryProvider2 implements InvocationHandler, if (matchingConstructor != null) { // safe because we got the constructor from this implementation. @SuppressWarnings("unchecked") - InjectionPoint ip = InjectionPoint.forConstructor( - (Constructor) matchingConstructor, implementation); + InjectionPoint ip = + InjectionPoint.forConstructor( + (Constructor) matchingConstructor, implementation); return ip; } else { errors.addMessage( @@ -470,21 +599,19 @@ final class FactoryProvider2 implements InvocationHandler, } /** - * Matching logic for constructors annotated with AssistedInject. - * This returns true if and only if all @Assisted parameters in the - * constructor exactly match (in any order) all @Assisted parameters - * the method's parameter. + * Matching logic for constructors annotated with AssistedInject. This returns true if and only if + * all @Assisted parameters in the constructor exactly match (in any order) all @Assisted + * parameters the method's parameter. */ - private boolean constructorHasMatchingParams(TypeLiteral type, - Constructor constructor, List> paramList, Errors errors) + private boolean constructorHasMatchingParams( + TypeLiteral type, Constructor constructor, List> paramList, Errors errors) throws ErrorsException { List> params = type.getParameterTypes(constructor); Annotation[][] paramAnnotations = constructor.getParameterAnnotations(); int p = 0; List> constructorKeys = Lists.newArrayList(); for (TypeLiteral param : params) { - Key paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++], - errors); + Key paramKey = Annotations.getKey(param, constructor, paramAnnotations[p++], errors); constructorKeys.add(paramKey); } // Require that every key exist in the constructor to match up exactly. @@ -504,10 +631,9 @@ final class FactoryProvider2 implements InvocationHandler, return true; } - /** - * Calculates all dependencies required by the implementation and constructor. - */ - private Set> getDependencies(InjectionPoint ctorPoint, TypeLiteral implementation) { + /** Calculates all dependencies required by the implementation and constructor. */ + private Set> getDependencies( + InjectionPoint ctorPoint, TypeLiteral implementation) { ImmutableSet.Builder> builder = ImmutableSet.builder(); builder.addAll(ctorPoint.getDependencies()); if (!implementation.getRawType().isInterface()) { @@ -518,9 +644,7 @@ final class FactoryProvider2 implements InvocationHandler, return builder.build(); } - /** - * Return all non-assisted dependencies. - */ + /** Return all non-assisted dependencies. */ private Set> removeAssistedDeps(Set> deps) { ImmutableSet.Builder> builder = ImmutableSet.builder(); for (Dependency dep : deps) { @@ -538,8 +662,8 @@ final class FactoryProvider2 implements InvocationHandler, * 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. */ - private boolean isValidForOptimizedAssistedInject(Set> dependencies, - Class implementation, TypeLiteral factoryType) { + private boolean isValidForOptimizedAssistedInject( + Set> dependencies, Class implementation, TypeLiteral factoryType) { Set> badDeps = null; // optimization: create lazily for (Dependency dep : dependencies) { if (isInjectorOrAssistedProvider(dep)) { @@ -550,61 +674,76 @@ final class FactoryProvider2 implements InvocationHandler, } } 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. " + "Stop injecting @Assisted Provider (instead use @Assisted T) " + "or Injector to speed things up. (It will be a ~6500% speed bump!) " + "The exact offending deps are: {2}", - new Object[] { factoryType, implementation, badDeps } ); + new Object[] {factoryType, implementation, badDeps}); return false; } return true; } /** - * Returns true if the dependency is for {@link Injector} or if the dependency - * is a {@link Provider} for a parameter that is {@literal @}{@link Assisted}. + * Returns true if the dependency is for {@link Injector} or if the dependency is a {@link + * Provider} for a parameter that is {@literal @}{@link Assisted}. */ private boolean isInjectorOrAssistedProvider(Dependency dependency) { Class annotationType = dependency.getKey().getAnnotationType(); 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; } - } 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 false; } /** - * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. - * This fails if another binding annotation is clobbered in the process. If the key already has - * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value. + * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. This + * fails if another binding annotation is clobbered in the process. If the key already has the + * {@literal @}Assisted annotation, it is returned as-is to preserve any String value. */ private Key assistKey(Method method, Key key, Errors errors) throws ErrorsException { if (key.getAnnotationType() == null) { - return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION); + return key.withAnnotation(DEFAULT_ANNOTATION); } else if (key.getAnnotationType() == Assisted.class) { return key; } else { - errors.withSource(method).addMessage( - "Only @Assisted is allowed for factory parameters, but found @%s", - key.getAnnotationType()); + errors + .withSource(method) + .addMessage( + "Only @Assisted is allowed for factory parameters, but found @%s", + key.getAnnotationType()); throw errors.toException(); } } /** - * At injector-creation time, we initialize the invocation handler. At this time we make sure - * all factory methods will be able to build the target types. + * At injector-creation time, we initialize the invocation handler. At this time we make sure all + * factory methods will be able to build the target types. */ @Inject @Toolable void initialize(Injector injector) { if (this.injector != null) { - throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class, - "Factories.create() factories may only be used in one Injector!"))); + throw new ConfigurationException( + ImmutableList.of( + new Message( + FactoryProvider2.class, + "Factories.create() factories may only be used in one Injector!"))); } this.injector = injector; @@ -619,7 +758,8 @@ final class FactoryProvider2 implements InvocationHandler, } else { 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 implements InvocationHandler, */ public Binding getBindingFromNewInjector( 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."); final Key returnType = data.returnType; @@ -636,37 +777,42 @@ final class FactoryProvider2 implements InvocationHandler, // We ignore any pre-existing binding annotation. final Key returnKey = Key.get(returnType.getTypeLiteral(), RETURN_ANNOTATION); - Module assistedModule = new AbstractModule() { - @Override - @SuppressWarnings({ - "unchecked", "rawtypes"}) // raw keys are necessary for the args array and return value - protected void configure() { - Binder binder = binder().withSource(method); + Module assistedModule = + new AbstractModule() { + @Override + @SuppressWarnings({ + "unchecked", + "rawtypes" + }) // raw keys are necessary for the args array and return value + protected void configure() { + Binder binder = binder().withSource(method); - int p = 0; - if (!data.optimized) { - for (Key paramKey : data.paramTypes) { - // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter - binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); - } - } else { - for (Key paramKey : data.paramTypes) { - // Bind to our ThreadLocalProviders. - binder.bind((Key) paramKey).toProvider(data.providers.get(p++)); - } - } + int p = 0; + if (!data.optimized) { + for (Key paramKey : data.paramTypes) { + // Wrap in a Provider to cover null, and to prevent Guice from injecting the + // parameter + binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); + } + } else { + for (Key paramKey : data.paramTypes) { + // Bind to our ThreadLocalProviders. + binder.bind((Key) paramKey).toProvider(data.providers.get(p++)); + } + } - Constructor constructor = data.constructor; - // Constructor *should* always be non-null here, - // but if it isn't, we'll end up throwing a fairly good error - // message for the user. - if (constructor != null) { - binder.bind(returnKey) - .toConstructor(constructor, (TypeLiteral) data.implementationType) - .in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type - } - } - }; + Constructor constructor = data.constructor; + // Constructor *should* always be non-null here, + // but if it isn't, we'll end up throwing a fairly good error + // message for the user. + if (constructor != null) { + binder + .bind(returnKey) + .toConstructor(constructor, (TypeLiteral) data.implementationType) + .in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type + } + } + }; Injector forCreate = injector.createChildInjector(assistedModule); Binding binding = forCreate.getBinding(returnKey); @@ -681,6 +827,7 @@ final class FactoryProvider2 implements InvocationHandler, * 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. */ + @Override 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. // This is necessary for default methods that java8 creates, so we @@ -749,93 +896,19 @@ final class FactoryProvider2 implements InvocationHandler, return factoryKey.equals(other.factoryKey) && Objects.equal(collector, other.collector); } - /** - * 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> paramTypes; - /** - * the type of the implementation constructed - */ - final TypeLiteral implementationType; - - /** - * All non-assisted dependencies required by this method. - */ - final Set> 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 providers; - /** - * used to perform optimized factory creations. - */ - volatile Binding cachedBinding; // TODO: volatile necessary? - - AssistData(Constructor constructor, Key returnType, ImmutableList> paramTypes, - TypeLiteral implementationType, Method factoryMethod, - Set> dependencies, - boolean optimized, List 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; + /** 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; } - @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(); + for (Class declared : invoked.getExceptionTypes()) { + if (declared.isInstance(thrown)) { + return true; + } } - public Set> getDependencies() { - return dependencies; - } - - public Method getFactoryMethod() { - return factoryMethod; - } - - public Constructor getImplementationConstructor() { - return constructor; - } - - public TypeLiteral getImplementationType() { - return implementationType; - } + return false; } // not because we'll never know and this is easier than suppressing warnings. @@ -848,86 +921,106 @@ final class FactoryProvider2 implements InvocationHandler, } } - /** - * Wrapper around MethodHandles/MethodHandle, so we can compile+run on java6. - */ - private static class MethodHandleWrapper { - static final int ALL_MODES = Modifier.PRIVATE - | Modifier.STATIC /* package */ - | Modifier.PUBLIC - | Modifier.PROTECTED; + private static MethodHandle superMethodHandle( + Method method, Object proxy, MethodHandles.Lookup userLookups) + throws ReflectiveOperationException { + MethodHandles.Lookup lookup = userLookups == null ? MethodHandles.lookup() : userLookups; + MethodHandle handle = SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup); + return handle != null ? handle.bindTo(proxy) : null; + } - static final Method unreflectSpecial; - static final Method bindTo; - static final Method invokeWithArguments; - static final Constructor lookupCxtor; - static final boolean valid; + // begin by trying unreflectSpecial to find super method handles; this should work on Java14+ + private static final AtomicReference SUPER_METHOD_LOOKUP = + new AtomicReference<>(SuperMethodLookup.UNREFLECT_SPECIAL); - static { - Method unreflectSpecialTmp = null; - Method bindToTmp = null; - Method invokeWithArgumentsTmp = null; - boolean validTmp = false; - Constructor lookupCxtorTmp = null; + private static enum SuperMethodLookup { + UNREFLECT_SPECIAL { + @Override + MethodHandle superMethodHandle(Method method, MethodHandles.Lookup lookup) + throws ReflectiveOperationException { + try { + return lookup.unreflectSpecial(method, method.getDeclaringClass()); + } catch (ReflectiveOperationException e) { + // fall back to findSpecial which should work on Java9+; use that for future lookups + SUPER_METHOD_LOOKUP.compareAndSet(this, FIND_SPECIAL); + return SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup); + } + } + }, + FIND_SPECIAL { + @Override + MethodHandle superMethodHandle(Method method, MethodHandles.Lookup lookup) + throws ReflectiveOperationException { + try { + Class declaringClass = method.getDeclaringClass(); + // use findSpecial to workaround https://bugs.openjdk.java.net/browse/JDK-8209005 + return lookup.findSpecial( + declaringClass, + method.getName(), + MethodType.methodType(method.getReturnType(), method.getParameterTypes()), + declaringClass); + } catch (ReflectiveOperationException e) { + if (!allowLookupReflection) { + throw e; + } + // fall back to private Lookup which should work on Java8; use that for future lookups + SUPER_METHOD_LOOKUP.compareAndSet(this, PRIVATE_LOOKUP); + return SUPER_METHOD_LOOKUP.get().superMethodHandle(method, lookup); + } + } + }, + PRIVATE_LOOKUP { + @Override + MethodHandle superMethodHandle(Method method, MethodHandles.Lookup unused) + 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 privateLookupCxtor = + findPrivateLookupCxtor(); + + private static Constructor findPrivateLookupCxtor() { try { - Class lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup"); - unreflectSpecialTmp = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class); - Class methodHandleClass = Class.forName("java.lang.invoke.MethodHandle"); - bindToTmp = methodHandleClass.getMethod("bindTo", Object.class); - invokeWithArgumentsTmp = methodHandleClass.getMethod("invokeWithArguments", Object[].class); - 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; - } - try { - Class declaringClass = method.getDeclaringClass(); - // Note: this isn't a public API, but we need to use it in order to call default methods. - Object lookup = lookupCxtor.newInstance(declaringClass, ALL_MODES); - method.setAccessible(true); - // These are part of the public API, but we use reflection since we run on java6 - // and they were introduced in java7. - lookup = unreflectSpecial.invoke(lookup, method, declaringClass); - Object handle = bindTo.invoke(lookup, proxy); - return new MethodHandleWrapper(handle); - } catch (InvocationTargetException ite) { - return null; - } catch (IllegalAccessException iae) { - return null; - } catch (InstantiationException ie) { + Constructor 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; } } - Object invokeWithArguments(Object[] args) throws Exception { - // 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 - public String toString() { - return handle.toString(); + 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); } } } diff --git a/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java b/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java index 7049807..14e2f33 100644 --- a/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java +++ b/src/main/java/com/google/inject/internal/AbstractBindingProcessor.java @@ -14,34 +14,33 @@ import com.google.inject.Stage; import com.google.inject.TypeLiteral; import com.google.inject.spi.DefaultBindingTargetVisitor; -import java.util.Set; - /** * Guarantees that processing of Binding elements happens in a sane way. */ 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 // all our unit tests. - private static final Set> FORBIDDEN_TYPES = ImmutableSet.of( - AbstractModule.class, - Binder.class, - Binding.class, - Injector.class, - Key.class, - MembersInjector.class, - Module.class, - Provider.class, - Scope.class, - Stage.class, - TypeLiteral.class); + private static final ImmutableSet> FORBIDDEN_TYPES = + ImmutableSet.>of( + AbstractModule.class, + Binder.class, + Binding.class, + Injector.class, + Key.class, + MembersInjector.class, + Module.class, + Provider.class, + Scope.class, + Stage.class, + TypeLiteral.class); - protected final ProcessedBindingData bindingData; + protected final ProcessedBindingData processedBindingData; - AbstractBindingProcessor(Errors errors, ProcessedBindingData bindingData) { + AbstractBindingProcessor(Errors errors, ProcessedBindingData processedBindingData) { super(errors); - this.bindingData = bindingData; + this.processedBindingData = processedBindingData; } protected UntargettedBindingImpl invalidBinding( @@ -61,10 +60,10 @@ abstract class AbstractBindingProcessor extends AbstractProcessor { BindingImpl original = injector.getExistingBinding(key); if (original != null) { // If it failed because of an explicit duplicate binding... - if (injector.state.getExplicitBinding(key) != null) { + if (injector.getBindingData().getExplicitBinding(key) != null) { try { - if (!isOkayDuplicate(original, binding, injector.state)) { - errors.bindingAlreadySet(key, original.getSource()); + if (!isOkayDuplicate(original, binding, injector.getBindingData())) { + errors.bindingAlreadySet(binding, original); return; } } catch (Throwable t) { @@ -80,27 +79,34 @@ abstract class AbstractBindingProcessor extends AbstractProcessor { } // prevent the parent from creating a JIT binding for this key - injector.state.parent().blacklist(key, injector.state, binding.getSource()); - injector.state.putBinding(key, binding); + injector + .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 - * are considered duplicates. + * We tolerate duplicate bindings if one exposes the other or if the two bindings are considered + * duplicates (see {@link Bindings#areDuplicates(BindingImpl, BindingImpl)}. * * @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) { - ExposedBindingImpl exposed = (ExposedBindingImpl) original; + ExposedBindingImpl exposed = (ExposedBindingImpl) original; InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector(); return (exposedFrom == binding.getInjector()); } 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 // 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 - * actually process the binding should call prepareBinding first. + * Processor for visiting bindings. Each overriden method that wants to actually process the + * binding should call prepareBinding first. */ abstract class Processor extends DefaultBindingTargetVisitor { final Object source; @@ -136,7 +142,7 @@ abstract class AbstractBindingProcessor extends AbstractProcessor { * initialially processed. */ protected void scheduleInitialization(BindingImpl binding) { - bindingData.addUninitializedBinding(() -> initializeBinding(binding)); + processedBindingData.addUninitializedBinding(() -> initializeBinding(binding)); } /** @@ -144,7 +150,7 @@ abstract class AbstractBindingProcessor extends AbstractProcessor { * bindings. */ protected void scheduleDelayedInitialization(BindingImpl binding) { - bindingData.addDelayedUninitializedBinding(() -> initializeBinding(binding)); + processedBindingData.addDelayedUninitializedBinding(() -> initializeBinding(binding)); } private void initializeBinding(BindingImpl binding) { diff --git a/src/main/java/com/google/inject/internal/Annotations.java b/src/main/java/com/google/inject/internal/Annotations.java index 58a1c8c..408f464 100644 --- a/src/main/java/com/google/inject/internal/Annotations.java +++ b/src/main/java/com/google/inject/internal/Annotations.java @@ -2,7 +2,6 @@ package com.google.inject.internal; 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.MapJoiner; 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.name.Named; import com.google.inject.name.Names; - -import javax.inject.Qualifier; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,40 +27,16 @@ import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import javax.inject.Qualifier; /** * Annotation utilities. * + * @author crazybob@google.com (Bob Lee) */ public class Annotations { - private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("="); - private static final Function DEEP_TO_STRING_FN = new Function() { - @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, Annotation> cache = - CacheBuilder.newBuilder() - .weakKeys() - .build(new CacheLoader<>() { - @Override - public Annotation load(Class 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. - */ + /** Returns {@code true} if the given annotation type has no attributes. */ public static boolean isMarker(Class annotationType) { return annotationType.getDeclaredMethods().length == 0; } @@ -79,11 +52,22 @@ public class Annotations { return hasMethods; } + private static final LoadingCache, Annotation> cache = + CacheBuilder.newBuilder() + .weakKeys() + .build( + new CacheLoader, Annotation>() { + @Override + public Annotation load(Class input) { + return generateAnnotationImpl(input); + } + }); + /** * Generates an Annotation for the annotation class. Requires that the annotation is all * optionals. */ - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") // Safe because generateAnnotationImpl returns T for Class public static T generateAnnotation(Class annotationType) { Preconditions.checkState( isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType); @@ -92,24 +76,27 @@ public class Annotations { private static T generateAnnotationImpl(final Class annotationType) { final Map members = resolveMembers(annotationType); - return annotationType.cast(Proxy.newProxyInstance( - annotationType.getClassLoader(), - new Class[]{annotationType}, - (proxy, method, args) -> { - String name = method.getName(); - switch (name) { - case "annotationType": - return annotationType; - case "toString": - return annotationToString(annotationType, members); - case "hashCode": - return annotationHashCode(annotationType, members); - case "equals": - return annotationEquals(annotationType, members, args[0]); - default: - return members.get(name); - } - })); + return annotationType.cast( + Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[] {annotationType}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Exception { + String name = method.getName(); + if (name.equals("annotationType")) { + return annotationType; + } else if (name.equals("toString")) { + return annotationToString(annotationType, members); + } else if (name.equals("hashCode")) { + return annotationHashCode(annotationType, members); + } else if (name.equals("equals")) { + return annotationEquals(annotationType, members, args[0]); + } else { + return members.get(name); + } + } + })); } private static ImmutableMap resolveMembers( @@ -121,68 +108,67 @@ public class Annotations { return result.build(); } - /** - * Implements {@link Annotation#equals}. - */ - private static boolean annotationEquals(Class type, - Map members, Object other) throws Exception { + /** Implements {@link Annotation#equals}. */ + private static boolean annotationEquals( + Class type, Map members, Object other) + throws Exception { if (!type.isInstance(other)) { return false; } for (Method method : type.getDeclaredMethods()) { String name = method.getName(); if (!Arrays.deepEquals( - new Object[]{method.invoke(other)}, new Object[]{members.get(name)})) { + new Object[] {method.invoke(other)}, new Object[] {members.get(name)})) { return false; } } return true; } - /** - * Implements {@link Annotation#hashCode}. - */ - private static int annotationHashCode(Class type, - Map members) throws Exception { + /** Implements {@link Annotation#hashCode}. */ + private static int annotationHashCode( + Class type, Map members) throws Exception { int result = 0; for (Method method : type.getDeclaredMethods()) { String name = method.getName(); Object value = members.get(name); - result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[]{value}) - 31); + result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[] {value}) - 31); } return result; } - /** - * Implements {@link Annotation#toString}. - */ - private static String annotationToString(Class type, - Map members) throws Exception { + private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("="); + + /** Implements {@link Annotation#toString}. */ + private static String annotationToString( + Class type, Map members) throws Exception { 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(); } - /** - * 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 annotationType) { Retention retention = annotationType.getAnnotation(Retention.class); 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 findScopeAnnotation( Errors errors, Class implementation) { return findScopeAnnotation(errors, implementation.getAnnotations()); } - /** - * Returns the scoping annotation, or null if there isn't one. - */ - public static Class findScopeAnnotation(Errors errors, Annotation[] annotations) { + /** Returns the scoping annotation, or null if there isn't one. */ + public static Class findScopeAnnotation( + Errors errors, Annotation[] annotations) { Class found = null; for (Annotation annotation : annotations) { @@ -210,10 +196,50 @@ public class Annotations { 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) { - return QUOTE_MEMBER_VALUES ? "\"" + value + "\"" : value; + return ANNOTATION_TO_STRING_CONFIG.quote ? "\"" + value + "\"" : value; + } + + /** + * Returns string representation of the annotation memeber. + * + *

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. + * + *

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) @@ -221,26 +247,64 @@ public class Annotations { String value(); } - @TestAnnotation("determineWhetherToQuote") - private static boolean determineWhetherToQuote() { + @TestAnnotation("determineAnnotationToStringConfig") + private static AnnotationToStringConfig determineAnnotationToStringConfig() { try { - String annotation = Annotations.class - .getDeclaredMethod("determineWhetherToQuote") + String annotation = + Annotations.class + .getDeclaredMethod("determineAnnotationToStringConfig") .getAnnotation(TestAnnotation.class) .toString(); - return annotation.contains("\"determineWhetherToQuote\""); + boolean quote = annotation.contains("\"determineAnnotationToStringConfig\""); + boolean includeMemberName = annotation.contains("value="); + return new AnnotationToStringConfig(quote, includeMemberName); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } + /** Checks for the presence of annotations. Caches results because Android doesn't. */ + static class AnnotationChecker { + private final Collection> annotationTypes; + + /** Returns true if the given class has one of the desired annotations. */ + private CacheLoader, Boolean> hasAnnotations = + new CacheLoader, Boolean>() { + @Override + public Boolean load(Class annotationType) { + for (Annotation annotation : annotationType.getAnnotations()) { + if (annotationTypes.contains(annotation.annotationType())) { + return true; + } + } + return false; + } + }; + + final LoadingCache, Boolean> cache = + CacheBuilder.newBuilder().weakKeys().build(hasAnnotations); + + /** Constructs a new checker that looks for annotations of the given types. */ + AnnotationChecker(Collection> annotationTypes) { + this.annotationTypes = annotationTypes; + } + + /** Returns true if the given type has one of the desired annotations. */ + boolean hasAnnotations(Class 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 annotationType) { return scopeChecker.hasAnnotations(annotationType); } /** - * Adds an error if there is a misplaced annotations on {@code type}. Scoping - * annotations are not allowed on abstract classes or interfaces. + * Adds an error if there is a misplaced annotations on {@code type}. Scoping annotations are not + * allowed on abstract classes or interfaces. */ public static void checkForMisplacedScopeAnnotations( Class type, Object source, Errors errors) { @@ -256,21 +320,22 @@ public class Annotations { } } - /** - * Gets a key for the given type, member and annotations. - */ - public static Key getKey(TypeLiteral type, Member member, Annotation[] annotations, - Errors errors) throws ErrorsException { + // NOTE: getKey/findBindingAnnotation are used by Gin which is abandoned. So changing this API + // will prevent Gin users from upgrading Guice version. + + /** Gets a key for the given type, member and annotations. */ + public static Key getKey( + TypeLiteral type, Member member, Annotation[] annotations, Errors errors) + throws ErrorsException { int numErrorsBefore = errors.size(); Annotation found = findBindingAnnotation(errors, member, annotations); errors.throwIfNewErrors(numErrorsBefore); return found == null ? Key.get(type) : Key.get(type, found); } - /** - * Returns the binding annotation on {@code member}, or null if there isn't one. - */ - public static Annotation findBindingAnnotation(Errors errors, Member member, Annotation[] annotations) { + /** Returns the binding annotation on {@code member}, or null if there isn't one. */ + public static Annotation findBindingAnnotation( + Errors errors, Member member, Annotation[] annotations) { Annotation found = null; for (Annotation annotation : annotations) { @@ -287,16 +352,17 @@ public class Annotations { return found; } - /** - * Returns true if annotations of the specified type are binding annotations. - */ + private static final AnnotationChecker bindingAnnotationChecker = + new AnnotationChecker(Arrays.asList(BindingAnnotation.class, Qualifier.class)); + + /** Returns true if annotations of the specified type are binding annotations. */ public static boolean isBindingAnnotation(Class annotationType) { return bindingAnnotationChecker.hasAnnotations(annotationType); } /** * If the annotation is an instance of {@code javax.inject.Named}, canonicalizes to - * com.google.guice.name.Named. Returns the given annotation otherwise. + * com.google.guice.name.Named. Returns the given annotation otherwise. */ public static Annotation canonicalizeIfNamed(Annotation annotation) { if (annotation instanceof javax.inject.Named) { @@ -336,44 +402,4 @@ public class Annotations { return ""; } } - - /** - * Checks for the presence of annotations. Caches results because Android doesn't. - */ - static class AnnotationChecker { - private final Collection> annotationTypes; - - /** - * Returns true if the given class has one of the desired annotations. - */ - private CacheLoader, Boolean> hasAnnotations = - new CacheLoader<>() { - @Override - public Boolean load(Class annotationType) { - for (Annotation annotation : annotationType.getAnnotations()) { - if (annotationTypes.contains(annotation.annotationType())) { - return true; - } - } - return false; - } - }; - - final LoadingCache, Boolean> cache = - CacheBuilder.newBuilder().weakKeys().build(hasAnnotations); - - /** - * Constructs a new checker that looks for annotations of the given types. - */ - AnnotationChecker(Collection> annotationTypes) { - this.annotationTypes = annotationTypes; - } - - /** - * Returns true if the given type has one of the desired annotations. - */ - boolean hasAnnotations(Class annotated) { - return cache.getUnchecked(annotated); - } - } } diff --git a/src/main/java/com/google/inject/internal/BindingAlreadySetError.java b/src/main/java/com/google/inject/internal/BindingAlreadySetError.java new file mode 100644 index 0000000..86fa06f --- /dev/null +++ b/src/main/java/com/google/inject/internal/BindingAlreadySetError.java @@ -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 { + private final Binding binding; + private final Binding original; + + BindingAlreadySetError(Binding binding, Binding original, List 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> mergeableErrors, Formatter formatter) { + List> 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 newSources) { + return new BindingAlreadySetError(binding, original, newSources); + } +} diff --git a/src/main/java/com/google/inject/internal/BindingProcessor.java b/src/main/java/com/google/inject/internal/BindingProcessor.java index 79b3bf9..e6a7ab6 100644 --- a/src/main/java/com/google/inject/internal/BindingProcessor.java +++ b/src/main/java/com/google/inject/internal/BindingProcessor.java @@ -15,7 +15,6 @@ import com.google.inject.spi.ProviderBinding; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.UntargettedBinding; - import java.util.Set; /** @@ -25,8 +24,9 @@ final class BindingProcessor extends AbstractBindingProcessor { private final Initializer initializer; - BindingProcessor(Errors errors, Initializer initializer, ProcessedBindingData bindingData) { - super(errors, bindingData); + BindingProcessor( + Errors errors, Initializer initializer, ProcessedBindingData processedBindingData) { + super(errors, processedBindingData); this.initializer = initializer; } @@ -49,145 +49,174 @@ final class BindingProcessor extends AbstractBindingProcessor { return true; } - return command.acceptTargetVisitor(new Processor<>((BindingImpl) command) { - @Override - public Boolean visit(ConstructorBinding binding) { - prepareBinding(); - try { - ConstructorBindingImpl onInjector = ConstructorBindingImpl.create(injector, key, - binding.getConstructor(), source, scoping, errors, false, false); - scheduleInitialization(onInjector); - putBinding(onInjector); - } catch (ErrorsException e) { - errors.merge(e.getErrors()); - putBinding(invalidBinding(injector, key, source)); - } - return true; - } - - @Override - public Boolean visit(InstanceBinding binding) { - prepareBinding(); - Set injectionPoints = binding.getInjectionPoints(); - T instance = binding.getInstance(); - @SuppressWarnings("unchecked") // safe to cast to binding because - // the processor was constructed w/ it - Initializable ref = initializer.requestInjection( - injector, instance, (Binding) binding, source, injectionPoints); - ConstantFactory factory = new ConstantFactory<>(ref); - InternalFactory scopedFactory = - Scoping.scope(key, injector, factory, source, scoping); - putBinding(new InstanceBindingImpl<>(injector, key, source, - scopedFactory, injectionPoints, instance)); - return true; - } - - @Override - public Boolean visit(ProviderInstanceBinding binding) { - prepareBinding(); - javax.inject.Provider provider = binding.getUserSuppliedProvider(); - if (provider instanceof InternalProviderInstanceBindingImpl.Factory) { - @SuppressWarnings("unchecked") - InternalProviderInstanceBindingImpl.Factory asProviderMethod = - (InternalProviderInstanceBindingImpl.Factory) provider; - return visitInternalProviderInstanceBindingFactory(asProviderMethod); + return command.acceptTargetVisitor( + new Processor((BindingImpl) command) { + @Override + public Boolean visit(ConstructorBinding binding) { + prepareBinding(); + try { + ConstructorBindingImpl onInjector = + ConstructorBindingImpl.create( + injector, + key, + binding.getConstructor(), + source, + scoping, + errors, + false, + false); + scheduleInitialization(onInjector); + putBinding(onInjector); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + putBinding(invalidBinding(injector, key, source)); + } + return true; } - Set injectionPoints = binding.getInjectionPoints(); - Initializable> initializable = - initializer.requestInjection(injector, provider, null, source, injectionPoints); - // always visited with Binding - @SuppressWarnings("unchecked") - InternalFactory factory = new InternalFactoryToInitializableAdapter<>( - initializable, source, - injector.provisionListenerStore.get((ProviderInstanceBinding) binding)); - InternalFactory scopedFactory = Scoping.scope(key, injector, factory, source, scoping); - putBinding(new ProviderInstanceBindingImpl<>(injector, key, source, scopedFactory, scoping, - provider, injectionPoints)); - return true; - } - @Override - public Boolean visit(ProviderKeyBinding binding) { - prepareBinding(); - Key> providerKey = binding.getProviderKey(); - // always visited with Binding - @SuppressWarnings("unchecked") - BoundProviderFactory boundProviderFactory = new BoundProviderFactory<>( - injector, providerKey, source, - injector.provisionListenerStore.get((ProviderKeyBinding) binding)); - bindingData.addCreationListener(boundProviderFactory); - InternalFactory scopedFactory = Scoping.scope( - key, injector, boundProviderFactory, source, scoping); - putBinding(new LinkedProviderBindingImpl<>( - injector, key, source, scopedFactory, scoping, providerKey)); - return true; - } + @Override + public Boolean visit(InstanceBinding binding) { + prepareBinding(); + Set injectionPoints = binding.getInjectionPoints(); + T instance = binding.getInstance(); + @SuppressWarnings("unchecked") // safe to cast to binding because + // the processor was constructed w/ it + Initializable ref = + initializer.requestInjection( + injector, instance, (Binding) binding, source, injectionPoints); + ConstantFactory factory = new ConstantFactory<>(ref); + InternalFactory scopedFactory = + Scoping.scope(key, injector, factory, source, scoping); + putBinding( + new InstanceBindingImpl( + injector, key, source, scopedFactory, injectionPoints, instance)); + return true; + } - @Override - public Boolean visit(LinkedKeyBinding binding) { - prepareBinding(); - Key linkedKey = binding.getLinkedKey(); - if (key.equals(linkedKey)) { - errors.recursiveBinding(); - } - FactoryProxy factory = new FactoryProxy<>(injector, key, linkedKey, source); - bindingData.addCreationListener(factory); - InternalFactory scopedFactory = - Scoping.scope(key, injector, factory, source, scoping); - putBinding(new LinkedBindingImpl<>(injector, key, source, scopedFactory, scoping, linkedKey)); - return true; - } + @Override + public Boolean visit(ProviderInstanceBinding binding) { + prepareBinding(); + javax.inject.Provider provider = binding.getUserSuppliedProvider(); + if (provider instanceof InternalProviderInstanceBindingImpl.Factory) { + @SuppressWarnings("unchecked") + InternalProviderInstanceBindingImpl.Factory asProviderMethod = + (InternalProviderInstanceBindingImpl.Factory) provider; + return visitInternalProviderInstanceBindingFactory(asProviderMethod); + } + Set injectionPoints = binding.getInjectionPoints(); + Initializable> initializable = + initializer.>requestInjection( + injector, provider, null, source, injectionPoints); + // always visited with Binding + @SuppressWarnings("unchecked") + InternalFactory factory = + new InternalFactoryToInitializableAdapter( + initializable, + source, + injector.provisionListenerStore.get((ProviderInstanceBinding) binding)); + InternalFactory scopedFactory = + Scoping.scope(key, injector, factory, source, scoping); + putBinding( + new ProviderInstanceBindingImpl( + injector, key, source, scopedFactory, scoping, provider, injectionPoints)); + return true; + } - /** Handle ProviderMethods specially. */ - private Boolean visitInternalProviderInstanceBindingFactory( - InternalProviderInstanceBindingImpl.Factory provider) { - InternalProviderInstanceBindingImpl binding = - new InternalProviderInstanceBindingImpl<>( - injector, - key, - source, - provider, - Scoping.scope(key, injector, provider, source, scoping), - scoping); - switch (binding.getInitializationTiming()) { - case DELAYED: - scheduleDelayedInitialization(binding); - break; - case EAGER: - scheduleInitialization(binding); - break; - default: - throw new AssertionError(); - } - putBinding(binding); - return true; - } + @Override + public Boolean visit(ProviderKeyBinding binding) { + prepareBinding(); + Key> providerKey = + binding.getProviderKey(); + // always visited with Binding + @SuppressWarnings("unchecked") + BoundProviderFactory boundProviderFactory = + new BoundProviderFactory( + injector, + providerKey, + source, + injector.provisionListenerStore.get((ProviderKeyBinding) binding)); + processedBindingData.addCreationListener(boundProviderFactory); + InternalFactory scopedFactory = + Scoping.scope( + key, + injector, + (InternalFactory) boundProviderFactory, + source, + scoping); + putBinding( + new LinkedProviderBindingImpl( + injector, key, source, scopedFactory, scoping, providerKey)); + return true; + } - @Override - public Boolean visit(UntargettedBinding untargetted) { - return false; - } + @Override + public Boolean visit(LinkedKeyBinding binding) { + prepareBinding(); + Key linkedKey = binding.getLinkedKey(); + if (key.equals(linkedKey)) { + // TODO: b/168656899 check for transitive recursive binding + errors.recursiveBinding(key, linkedKey); + } - @Override - public Boolean visit(ExposedBinding binding) { - throw new IllegalArgumentException("Cannot apply a non-module element"); - } + FactoryProxy factory = new FactoryProxy<>(injector, key, linkedKey, source); + processedBindingData.addCreationListener(factory); + InternalFactory scopedFactory = + Scoping.scope(key, injector, factory, source, scoping); + putBinding( + new LinkedBindingImpl(injector, key, source, scopedFactory, scoping, linkedKey)); + return true; + } - @Override - public Boolean visit(ConvertedConstantBinding binding) { - throw new IllegalArgumentException("Cannot apply a non-module element"); - } + /** Handle ProviderMethods specially. */ + private Boolean visitInternalProviderInstanceBindingFactory( + InternalProviderInstanceBindingImpl.Factory provider) { + InternalProviderInstanceBindingImpl binding = + new InternalProviderInstanceBindingImpl( + injector, + key, + source, + provider, + Scoping.scope(key, injector, provider, source, scoping), + scoping); + switch (binding.getInitializationTiming()) { + case DELAYED: + scheduleDelayedInitialization(binding); + break; + case EAGER: + scheduleInitialization(binding); + break; + default: + throw new AssertionError(); + } + putBinding(binding); + return true; + } - @Override - public Boolean visit(ProviderBinding binding) { - throw new IllegalArgumentException("Cannot apply a non-module element"); - } + @Override + public Boolean visit(UntargettedBinding untargetted) { + return false; + } - @Override - protected Boolean visitOther(Binding binding) { - throw new IllegalStateException("BindingProcessor should override all visitations"); - } - }); + @Override + public Boolean visit(ExposedBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + public Boolean visit(ConvertedConstantBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + public Boolean visit(ProviderBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + @Override + protected Boolean visitOther(Binding binding) { + throw new IllegalStateException("BindingProcessor should override all visitations"); + } + }); } @Override @@ -200,8 +229,13 @@ final class BindingProcessor extends AbstractBindingProcessor { private void bindExposed(PrivateElements privateElements, Key key) { ExposedKeyFactory exposedKeyFactory = new ExposedKeyFactory<>(key, privateElements); - bindingData.addCreationListener(exposedKeyFactory); - putBinding(new ExposedBindingImpl<>( - injector, privateElements.getExposedSource(key), key, exposedKeyFactory, privateElements)); + processedBindingData.addCreationListener(exposedKeyFactory); + putBinding( + new ExposedBindingImpl( + injector, + privateElements.getExposedSource(key), + key, + exposedKeyFactory, + privateElements)); } } diff --git a/src/main/java/com/google/inject/internal/ChildBindingAlreadySetError.java b/src/main/java/com/google/inject/internal/ChildBindingAlreadySetError.java new file mode 100644 index 0000000..9c5ce1e --- /dev/null +++ b/src/main/java/com/google/inject/internal/ChildBindingAlreadySetError.java @@ -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 { + private final Key key; + private final ImmutableList existingSources; + + ChildBindingAlreadySetError(Key key, Iterable existingSoruces, List 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> 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> sourcesList = new ArrayList<>(); + sourcesList.add(getSources()); + mergeableErrors.forEach(error -> sourcesList.add(error.getSources())); + + List> 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 newSources) { + return new ChildBindingAlreadySetError(key, existingSources, newSources); + } + + /** Omit the key itself in the source list since the information is redundant. */ + private List trimSource(List sources) { + return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java b/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java index 543a29d..12019d6 100644 --- a/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java +++ b/src/main/java/com/google/inject/internal/ConstructorBindingImpl.java @@ -1,11 +1,15 @@ 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.Objects; import com.google.common.collect.ImmutableSet; import com.google.inject.Binder; import com.google.inject.ConfigurationException; -import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.internal.util.Classes; @@ -13,63 +17,66 @@ import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.ConstructorBinding; import com.google.inject.spi.Dependency; import com.google.inject.spi.InjectionPoint; - import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.Set; -import static com.google.common.base.Preconditions.checkState; -import static com.google.inject.internal.Annotations.findScopeAnnotation; - final class ConstructorBindingImpl extends BindingImpl implements ConstructorBinding, DelayedInitialize { private final Factory factory; - private final InjectionPoint constructorInjectionPoint; - private ConstructorBindingImpl(InjectorImpl injector, - Key key, - Object source, - InternalFactory scopedFactory, - Scoping scoping, - Factory factory, - InjectionPoint constructorInjectionPoint) { + private ConstructorBindingImpl( + InjectorImpl injector, + Key key, + Object source, + InternalFactory scopedFactory, + Scoping scoping, + Factory factory, + InjectionPoint constructorInjectionPoint) { super(injector, key, source, scopedFactory, scoping); this.factory = factory; this.constructorInjectionPoint = constructorInjectionPoint; } - public ConstructorBindingImpl(Key key, Object source, Scoping scoping, - InjectionPoint constructorInjectionPoint, Set injectionPoints) { + public ConstructorBindingImpl( + Key key, + Object source, + Scoping scoping, + InjectionPoint constructorInjectionPoint, + Set injectionPoints) { super(source, key, scoping); - this.factory = new Factory(false, key); - ConstructionProxy constructionProxy - = new DefaultConstructionProxyFactory(constructorInjectionPoint).create(); + this.factory = new Factory<>(false, key); + ConstructionProxy constructionProxy = + new DefaultConstructionProxyFactory(constructorInjectionPoint).create(); this.constructorInjectionPoint = constructorInjectionPoint; - factory.constructorInjector = new ConstructorInjector( - injectionPoints, constructionProxy, null, null); + factory.constructorInjector = + new ConstructorInjector(injectionPoints, constructionProxy, null, null); } /** * @param constructorInjector the constructor to use, or {@code null} to use the default. - * @param failIfNotLinked true if this ConstructorBindingImpl's InternalFactory should - * only succeed if retrieved from a linked binding + * @param failIfNotLinked true if this ConstructorBindingImpl's InternalFactory should only + * succeed if retrieved from a linked binding */ - static ConstructorBindingImpl create(InjectorImpl injector, Key key, - InjectionPoint constructorInjector, - Object source, - Scoping scoping, - Errors errors, - boolean failIfNotLinked, boolean failIfNotExplicit) + static ConstructorBindingImpl create( + InjectorImpl injector, + Key key, + InjectionPoint constructorInjector, + Object source, + Scoping scoping, + Errors errors, + boolean failIfNotLinked, + boolean atInjectRequired) throws ErrorsException { int numErrors = errors.size(); - @SuppressWarnings("unchecked") // constructorBinding guarantees type is consistent - Class rawType = constructorInjector == null - ? key.getTypeLiteral().getRawType() - : (Class) constructorInjector.getDeclaringType().getRawType(); + Class rawType = + constructorInjector == null + ? key.getTypeLiteral().getRawType() + : constructorInjector.getDeclaringType().getRawType(); // We can't inject abstract classes. if (Modifier.isAbstract(rawType.getModifiers())) { @@ -86,10 +93,8 @@ final class ConstructorBindingImpl extends BindingImpl // Find a constructor annotated @Inject if (constructorInjector == null) { try { - constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral()); - if (failIfNotExplicit && !hasAtInject((Constructor) constructorInjector.getMember())) { - errors.atInjectRequired(rawType); - } + constructorInjector = + InjectionPoint.forConstructorOf(key.getTypeLiteral(), atInjectRequired); } catch (ConfigurationException e) { throw errors.merge(e.getErrorMessages()).toException(); } @@ -100,47 +105,36 @@ final class ConstructorBindingImpl extends BindingImpl Class annotatedType = constructorInjector.getMember().getDeclaringClass(); Class scopeAnnotation = findScopeAnnotation(errors, annotatedType); if (scopeAnnotation != null) { - scoping = Scoping.makeInjectable(Scoping.forAnnotation(scopeAnnotation), - injector, errors.withSource(rawType)); + scoping = + Scoping.makeInjectable( + Scoping.forAnnotation(scopeAnnotation), injector, errors.withSource(rawType)); } } errors.throwIfNewErrors(numErrors); - Factory factoryFactory = new Factory(failIfNotLinked, key); - InternalFactory scopedFactory - = Scoping.scope(key, injector, factoryFactory, source, scoping); + Factory factoryFactory = new Factory<>(failIfNotLinked, key); + InternalFactory scopedFactory = + Scoping.scope(key, injector, factoryFactory, source, scoping); return new ConstructorBindingImpl( injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector); } - /** - * 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); - } - + @Override @SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { factory.constructorInjector = (ConstructorInjector) injector.constructors.get(constructorInjectionPoint, errors); - factory.provisionCallback = - injector.provisionListenerStore.get(this); + factory.provisionCallback = 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() { 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() { if (factory.constructorInjector != null) { return factory.constructorInjector.getConstructionProxy().getInjectionPoint(); @@ -149,22 +143,21 @@ final class ConstructorBindingImpl extends BindingImpl } } - /** - * 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> getInternalDependencies() { ImmutableSet.Builder builder = ImmutableSet.builder(); if (factory.constructorInjector == null) { builder.add(constructorInjectionPoint); - // If the below throws, it's OK -- we just ignore those dependencies, because no one - // could have used them anyway. try { - builder.addAll(InjectionPoint.forInstanceMethodsAndFields(constructorInjectionPoint.getDeclaringType())); + builder.addAll( + InjectionPoint.forInstanceMethodsAndFields( + constructorInjectionPoint.getDeclaringType())); } catch (ConfigurationException ignored) { + // This is OK -- we just ignore those dependencies, because no one could have used them + // anyway. } } else { - builder.add(getConstructor()) - .addAll(getInjectableMembers()); + builder.add(getConstructor()).addAll(getInjectableMembers()); } return Dependency.forInjectionPoints(builder.build()); @@ -190,10 +183,11 @@ final class ConstructorBindingImpl extends BindingImpl @Override public Set> getDependencies() { - return Dependency.forInjectionPoints(new ImmutableSet.Builder() - .add(getConstructor()) - .addAll(getInjectableMembers()) - .build()); + return Dependency.forInjectionPoints( + new ImmutableSet.Builder() + .add(getConstructor()) + .addAll(getInjectableMembers()) + .build()); } @Override @@ -208,11 +202,17 @@ final class ConstructorBindingImpl extends BindingImpl null, key, getSource(), factory, getScoping(), factory, constructorInjectionPoint); } + @Override @SuppressWarnings("unchecked") // the raw constructor member and declaring type always agree public void applyTo(Binder binder) { InjectionPoint constructor = getConstructor(); - getScoping().applyTo(binder.withSource(getSource()).bind(getKey()).toConstructor( - (Constructor) getConstructor().getMember(), (TypeLiteral) constructor.getDeclaringType())); + getScoping() + .applyTo( + withTrustedSource(GUICE_INTERNAL, binder, getSource()) + .bind(getKey()) + .toConstructor( + (Constructor) getConstructor().getMember(), + (TypeLiteral) constructor.getDeclaringType())); } @Override @@ -252,6 +252,7 @@ final class ConstructorBindingImpl extends BindingImpl this.key = key; } + @Override @SuppressWarnings("unchecked") public T get(InternalContext context, Dependency dependency, boolean linked) throws InternalProvisionException { @@ -259,6 +260,7 @@ final class ConstructorBindingImpl extends BindingImpl if (localInjector == null) { throw new IllegalStateException("Constructor not ready"); } + if (!linked && failIfNotLinked) { throw InternalProvisionException.jitDisabled(key); } diff --git a/src/main/java/com/google/inject/internal/DuplicateElementError.java b/src/main/java/com/google/inject/internal/DuplicateElementError.java new file mode 100644 index 0000000..9cd1a81 --- /dev/null +++ b/src/main/java/com/google/inject/internal/DuplicateElementError.java @@ -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 extends InternalErrorDetail> { + private final Key> setKey; + private final ImmutableMultimap> elements; + + DuplicateElementError( + Key> setKey, List> bindings, T[] values, List sources) { + this(setKey, indexElements(bindings, values), sources); + } + + private DuplicateElementError( + Key> setKey, ImmutableMultimap> elements, List 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> others, Formatter formatter) { + formatter.format("%n%s%n", Messages.bold("Duplicates:")); + int duplicateIndex = 1; + for (Map.Entry>> entry : elements.asMap().entrySet()) { + formatter.format("%-2s: ", duplicateIndex++); + if (entry.getValue().size() > 1) { + Set 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 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 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 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 element, Formatter formatter) { + Object source = element.binding.getSource(); + new SourceFormatter( + source, + formatter, + /** omitPreposition= */ + true) + .format(); + } + + @Override + public DuplicateElementError withSources(List newSources) { + return new DuplicateElementError<>(setKey, elements, newSources); + } + + static ImmutableMultimap> indexElements(List> bindings, T[] values) { + ImmutableMultimap.Builder> map = ImmutableMultimap.builder(); + for (int i = 0; i < values.length; i++) { + map.put(values[i], new Element(values[i], bindings.get(i))); + } + return map.build(); + } + + static class Element { + T value; + Binding binding; + + Element(T value, Binding binding) { + this.value = value; + this.binding = binding; + } + } +} diff --git a/src/main/java/com/google/inject/internal/DuplicateMapKeyError.java b/src/main/java/com/google/inject/internal/DuplicateMapKeyError.java new file mode 100644 index 0000000..aec8838 --- /dev/null +++ b/src/main/java/com/google/inject/internal/DuplicateMapKeyError.java @@ -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 extends InternalErrorDetail> { + private final Key> mapKey; + private final Multimap> duplicates; + + DuplicateMapKeyError( + Key> mapKey, Multimap> duplicates, List sources) { + super(ErrorId.DUPLICATE_MAP_KEY, getDuplicateKeysMessage(mapKey, duplicates), sources, null); + this.mapKey = mapKey; + this.duplicates = duplicates; + } + + @Override + protected final void formatDetail(List> others, Formatter formatter) { + formatter.format("%n%s%n", Messages.bold("Duplicates:")); + + for (Map.Entry>> entry : duplicates.asMap().entrySet()) { + formatter.format(" Key: \"%s\"%n", Messages.redBold(entry.getKey().toString())); + formatter.format(" Bound at:%n"); + int index = 1; + for (Binding 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 withSources(List newSources) { + return new DuplicateMapKeyError<>(mapKey, duplicates, newSources); + } + + private static String getDuplicateKeysMessage( + Key> mapKey, Multimap> duplicates) { + Set 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); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ErrorFormatter.java b/src/main/java/com/google/inject/internal/ErrorFormatter.java new file mode 100644 index 0000000..e8fae01 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ErrorFormatter.java @@ -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 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 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(); + } + } +} diff --git a/src/main/java/com/google/inject/internal/ErrorId.java b/src/main/java/com/google/inject/internal/ErrorId.java new file mode 100644 index 0000000..354f35f --- /dev/null +++ b/src/main/java/com/google/inject/internal/ErrorId.java @@ -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; +} diff --git a/src/main/java/com/google/inject/internal/Errors.java b/src/main/java/com/google/inject/internal/Errors.java index 30d3bfe..48b6ff9 100644 --- a/src/main/java/com/google/inject/internal/Errors.java +++ b/src/main/java/com/google/inject/internal/Errors.java @@ -1,10 +1,11 @@ package com.google.inject.internal; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; -import com.google.common.primitives.Primitives; import com.google.inject.Binding; import com.google.inject.ConfigurationException; import com.google.inject.CreationException; @@ -14,18 +15,18 @@ import com.google.inject.ProvisionException; import com.google.inject.Scope; import com.google.inject.TypeLiteral; import com.google.inject.internal.util.SourceProvider; +import com.google.inject.spi.ElementSource; import com.google.inject.spi.Message; import com.google.inject.spi.ScopeBinding; import com.google.inject.spi.TypeConverterBinding; import com.google.inject.spi.TypeListenerBinding; - +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.Collection; import java.util.Formatter; 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 * 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 checkNotNull(T reference, String name) { if (reference != null) { return reference; } + NullPointerException npe = new NullPointerException(name); throw new ConfigurationException(ImmutableSet.of(new Message(npe.toString(), npe))); } + /** + * Throws a ConfigurationException with a formatted {@link Message} if this condition is {@code + * false}. + */ static void checkConfiguration(boolean condition, String format, Object... args) { if (condition) { return; } - throw new ConfigurationException(ImmutableSet.of(new Message(Messages.format(format, args)))); - } - private static final ImmutableSet> COMMON_AMBIGUOUS_TYPES = - ImmutableSet.>builder() - .add(Object.class) - .add(String.class) - .addAll(Primitives.allWrapperTypes()) - .build(); + throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); + } /** The root errors object. Used to access the list of error messages. */ private final Errors root; @@ -82,13 +82,10 @@ public final class Errors { /** The parent errors object. Used to obtain the chain of source objects. */ private final Errors parent; - /** - * The leaf source for errors added here. - */ + /** The leaf source for errors added here. */ 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 errors; // lazy, use getErrorsForAdd() public Errors() { @@ -116,305 +113,380 @@ public final class Errors { : 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: + * + *
Guice.createInjector(new AbstractModule() {
+     *   public void configure() {
+     *     bind(Runnable.class);
+     *   }
+     * }
+ * + * ...and at provide-time errors: + * + *
Guice.createInjector().getInstance(Runnable.class);
+ * + * Otherwise we need to know who's calling when resolving a just-in-time binding, which makes + * things unnecessarily complex. + */ public Errors missingImplementation(Key key) { - return addMessage("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 */ Errors missingImplementationWithHint(Key key, Injector injector) { - StringBuilder sb = new StringBuilder(); - - sb.append(Messages.format("No implementation for %s was bound.", key)); - - // Keys which have similar strings as the desired key - List possibleMatches = new ArrayList<>(); - - // Check for other keys that may have the same type, - // but not the same annotation - TypeLiteral type = key.getTypeLiteral(); - List> sameTypes = injector.findBindingsByType(type); - if (!sameTypes.isEmpty()) { - sb.append(Messages.format("%n Did you mean?")); - int howMany = Math.min(sameTypes.size(), MAX_MATCHING_TYPES_REPORTED); - for (int i = 0; i < howMany; ++i) { - // TODO: Look into a better way to prioritize suggestions. For example, possbily - // use levenshtein distance of the given annotation vs actual annotation. - sb.append(Messages.format("%n * %s", sameTypes.get(i).getKey())); - } - int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED; - if (remaining > 0) { - String plural = (remaining == 1) ? "" : "s"; - sb.append(Messages.format("%n %d more binding%s with other annotations.", remaining, plural)); - } - } else { - // For now, do a simple substring search for possibilities. This can help spot - // issues when there are generics being used (such as a wrapper class) and the - // user has forgotten they need to bind based on the wrapper, not the underlying - // class. In the future, consider doing a strict in-depth type search. - // TODO: Look into a better way to prioritize suggestions. For example, possbily - // use levenshtein distance of the type literal strings. - String want = type.toString(); - Map, Binding> bindingMap = injector.getAllBindings(); - for (Key bindingKey : bindingMap.keySet()) { - String have = bindingKey.getTypeLiteral().toString(); - if (have.contains(want) || want.contains(have)) { - Formatter fmt = new Formatter(); - Messages.formatSource(fmt, bindingMap.get(bindingKey).getSource()); - String match = String.format("%s bound%s", Messages.convert(bindingKey), fmt.toString()); - possibleMatches.add(match); - // TODO: Consider a check that if there are more than some number of results, - // don't suggest any. - if (possibleMatches.size() > MAX_RELATED_TYPES_REPORTED) { - // Early exit if we have found more than we need. - break; - } - } - } - - if ((possibleMatches.size() > 0) && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) { - sb.append(Messages.format("%n Did you mean?")); - for (String possibleMatch : possibleMatches) { - sb.append(Messages.format("%n %s", possibleMatch)); - } - } - } - - // If where are no possibilities to suggest, then handle the case of missing - // annotations on simple types. This is usually a bad idea. - if (sameTypes.isEmpty() - && possibleMatches.isEmpty() - && key.getAnnotationType() == null - && COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) { - // We don't recommend using such simple types without annotations. - sb.append(Messages.format("%nThe key seems very generic, did you forget an annotation?")); - } - - return addMessage(sb.toString()); + MissingImplementationError error = + new MissingImplementationError(key, injector, getSources()); + return addMessage( + new Message(GuiceInternal.GUICE_INTERNAL, ErrorId.MISSING_IMPLEMENTATION, error)); } public Errors jitDisabled(Key key) { - return addMessage("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) { - 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.", key); } - public Errors atInjectRequired(Class clazz) { - return addMessage("Explicit @Inject annotations are required on constructors," - + " but %s has no constructors annotated with @Inject.", - clazz); + public Errors atInjectRequired(TypeLiteral type) { + return addMessage( + new Message( + GuiceInternal.GUICE_INTERNAL, + ErrorId.MISSING_CONSTRUCTOR, + new MissingConstructorError(type, /* atInjectRequired= */ true, getSources()))); } - public Errors converterReturnedNull(String stringValue, Object source, - TypeLiteral type, TypeConverterBinding typeConverterBinding) { - return addMessage("Received null converting '%s' (bound at %s) to %s%n" - + " using %s.", - stringValue, Messages.convert(source), type, typeConverterBinding); + public Errors converterReturnedNull( + String stringValue, + Object source, + TypeLiteral type, + TypeConverterBinding typeConverterBinding) { + return addMessage( + ErrorId.CONVERTER_RETURNED_NULL, + "Received null converting '%s' (bound at %s) to %s%n using %s.", + stringValue, + convert(source), + type, + typeConverterBinding); } - public Errors conversionTypeError(String stringValue, Object source, TypeLiteral type, - TypeConverterBinding typeConverterBinding, Object converted) { - return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n" + public Errors conversionTypeError( + String stringValue, + Object source, + TypeLiteral type, + TypeConverterBinding typeConverterBinding, + Object converted) { + return addMessage( + ErrorId.CONVERSION_TYPE_ERROR, + "Type mismatch converting '%s' (bound at %s) to %s%n" + " using %s.%n" + " Converter returned %s.", - stringValue, Messages.convert(source), type, typeConverterBinding, converted); + stringValue, + convert(source), + type, + typeConverterBinding, + converted); } - public Errors conversionError(String stringValue, Object source, - TypeLiteral type, TypeConverterBinding typeConverterBinding, RuntimeException cause) { - return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n" - + " using %s.%n" - + " Reason: %s", - stringValue, Messages.convert(source), type, typeConverterBinding, cause); + public Errors conversionError( + String stringValue, + Object source, + TypeLiteral type, + TypeConverterBinding typeConverterBinding, + RuntimeException cause) { + return errorInUserCode( + cause, + "Error converting '%s' (bound at %s) to %s%n using %s.%n Reason: %s", + stringValue, + convert(source), + type, + typeConverterBinding, + cause); } - public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral type, - TypeConverterBinding a, TypeConverterBinding b) { - return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n" + public Errors ambiguousTypeConversion( + String stringValue, + Object source, + TypeLiteral type, + TypeConverterBinding a, + TypeConverterBinding b) { + return addMessage( + ErrorId.AMBIGUOUS_TYPE_CONVERSION, + "Multiple converters can convert '%s' (bound at %s) to %s:%n" + " %s and%n" + " %s.%n" + " Please adjust your type converter configuration to avoid overlapping matches.", - stringValue, Messages.convert(source), type, a, b); + stringValue, + convert(source), + type, + a, + b); } 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) { - 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() { - 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() { - 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 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 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) { - return addMessage("%s is annotated @Inject(optional=true), " - + "but constructors cannot be optional.", constructor); + return addMessage( + ErrorId.OPTIONAL_CONSTRUCTOR, + "%s is annotated @Inject(optional=true), but constructors cannot be optional.", + constructor); } public Errors cannotBindToGuiceType(String simpleName) { - return addMessage("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 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( Class scopeAnnotation, Class type, Object source) { - return addMessage("%s is annotated with %s, but scope annotations are not supported " - + "for abstract types.%n Bound at %s.", type, scopeAnnotation, Messages.convert(source)); + return addMessage( + ErrorId.SCOPE_ANNOTATION_ON_ABSTRACT_TYPE, + "%s is annotated with %s, but scope annotations are not supported " + + "for abstract types.%n Bound at %s.", + type, + scopeAnnotation, + convert(source)); } public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) { - return addMessage("%s is annotated with %s, but binding annotations should be applied " - + "to its parameters instead.", member, bindingAnnotation); + return addMessage( + ErrorId.MISPLACED_BINDING_ANNOTATION, + "%s is annotated with %s, but binding annotations should be applied " + + "to its parameters instead.", + member, + bindingAnnotation); } + // TODO(diamondm) don't mention zero-arg constructors if requireAtInjectOnConstructors is true private static final String CONSTRUCTOR_RULES = "Injectable classes must have either one (and only one) constructor annotated with @Inject" + " or a zero-argument constructor that is not private."; public Errors missingConstructor(TypeLiteral type) { - // Don't bother including the type in the message twice, unless the type is generic (i.e. the - // type has generics that the raw class loses) - String typeString = type.toString(); - String rawTypeString = MoreTypes.getRawType(type.getType()).getName(); return addMessage( - "No implementation for %s (with no qualifier annotation) was bound, and could not find an" - + " injectable constructor%s. %s", - typeString, - typeString.equals(rawTypeString) ? "" : " in " + rawTypeString, - CONSTRUCTOR_RULES); + new Message( + GuiceInternal.GUICE_INTERNAL, + ErrorId.MISSING_CONSTRUCTOR, + new MissingConstructorError(type, /* atInjectRequired= */ false, getSources()))); } public Errors tooManyConstructors(Class implementation) { return addMessage( + ErrorId.TOO_MANY_CONSTRUCTORS, "%s has more than one constructor annotated with @Inject. %s", - implementation, CONSTRUCTOR_RULES); + implementation, + CONSTRUCTOR_RULES); } 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, - Class annotationType, Scope scope) { - return addMessage("Scope %s is already bound to %s at %s.%n Cannot bind %s.", - existing.getScope(), annotationType, existing.getSource(), scope); + public Errors duplicateMapKey(Key> mapKey, Multimap> duplicates) { + return addMessage( + new Message( + GuiceInternal.GUICE_INTERNAL, + ErrorId.DUPLICATE_MAP_KEY, + new DuplicateMapKeyError(mapKey, duplicates, getSources()))); + } + + public Errors duplicateScopes( + ScopeBinding existing, Class annotationType, Scope scope) { + return addMessage( + ErrorId.DUPLICATE_SCOPES, + "Scope %s is already bound to %s at %s.%n Cannot bind %s.", + existing.getScope(), + annotationType, + existing.getSource(), + scope); } public Errors voidProviderMethod() { - return addMessage("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() { - 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) { - return addMessage("Injecting into inner classes is not supported. " - + "Please use a 'static' class (top-level or nested) instead of %s.", type); + return addMessage( + ErrorId.INJECT_INNER_CLASS, + "Injecting into inner classes is not supported. " + + "Please use a 'static' class (top-level or nested) instead of %s.", + type); } - public Errors duplicateBindingAnnotations(Member member, - Class a, Class b) { - return addMessage("%s has more than one annotation annotated with @BindingAnnotation: " - + "%s and %s", member, a, b); + public Errors duplicateBindingAnnotations( + Member member, Class a, Class b) { + return addMessage( + ErrorId.DUPLICATE_BINDING_ANNOTATIONS, + "%s has more than one annotation annotated with @BindingAnnotation: %s and %s", + member, + a, + b); } public Errors staticInjectionOnInterface(Class clazz) { - return addMessage("%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) { - 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) { - return addMessage("Injected method %s cannot be abstract.", method); - } - - public Errors cannotInjectNonVoidMethod(Method method) { - return addMessage("Injected method %s must return void.", method); + return addMessage( + ErrorId.INJECT_ABSTRACT_METHOD, "Injected method %s cannot be abstract.", 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( Class a, Class 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() { - return addMessage("Binding points to itself."); + public Errors recursiveBinding(Key key, Key linkedKey) { + return addMessage( + ErrorId.RECURSIVE_BINDING, "Binding points to itself. Key: %s", Messages.convert(key)); + } + + Errors bindingAlreadySet(Binding binding, Binding original) { + BindingAlreadySetError error = new BindingAlreadySetError(binding, original, getSources()); + return addMessage( + new Message(GuiceInternal.GUICE_INTERNAL, ErrorId.BINDING_ALREADY_SET, error)); } public Errors bindingAlreadySet(Key key, Object source) { - return addMessage("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) { - 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 sources) { - Formatter allSources = new Formatter(); - for (Object source : sources) { - if (source == null) { - allSources.format("%n (bound by a just-in-time binding)"); - } else { - allSources.format("%n bound at %s", source); - } - } - 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()); + Message message = + new Message( + GuiceInternal.GUICE_INTERNAL, + ErrorId.CHILD_BINDING_ALREADY_SET, + new ChildBindingAlreadySetError(key, sources, getSources())); + return addMessage(message); } public Errors errorCheckingDuplicateBinding(Key key, Object source, Throwable t) { return addMessage( + ErrorId.OTHER, "A binding to %s was already configured at %s and an error was thrown " + "while checking duplicate bindings. Error: %s", - key, Messages.convert(source), t); + key, + convert(source), + t); } - public Errors errorNotifyingTypeListener(TypeListenerBinding listener, - TypeLiteral type, Throwable cause) { - return errorInUserCode(cause, - "Error notifying TypeListener %s (bound at %s) of %s.%n" - + " Reason: %s", - listener.getListener(), Messages.convert(listener.getSource()), type, cause); + public Errors errorNotifyingTypeListener( + TypeListenerBinding listener, TypeLiteral type, Throwable cause) { + return errorInUserCode( + cause, + "Error notifying TypeListener %s (bound at %s) of %s.%n Reason: %s", + listener.getListener(), + convert(listener.getSource()), + type, + cause); } public Errors exposedButNotBound(Key key) { - return addMessage("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) { - 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) { @@ -439,39 +511,55 @@ public final class Errors { if (!messages.isEmpty()) { return merge(messages); } else { - return addMessage(cause, messageFormat, arguments); + return addMessage(ErrorId.ERROR_IN_USER_CODE, cause, messageFormat, arguments); } } - 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() { - 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) { - return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType); + return addMessage(ErrorId.OTHER, "Cannot inject a TypeLiteral of %s", unsupportedType); } 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() { if (!hasErrors()) { return; } - throw new CreationException(getMessages()); + + CreationException exception = new CreationException(getMessages()); + throw exception; } public void throwConfigurationExceptionIfErrorsExist() { if (!hasErrors()) { 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 messages) { @@ -486,6 +574,7 @@ public final class Errors { if (moreErrors.root == root || moreErrors.root.errors == null) { return this; } + merge(moreErrors.root.errors); return this; } @@ -495,7 +584,7 @@ public final class Errors { return this; } - public List getSources() { + private List getSources() { List sources = Lists.newArrayList(); for (Errors e = this; e != null; e = e.parent) { if (e.source != SourceProvider.UNKNOWN_SOURCE) { @@ -522,11 +611,16 @@ public final class Errors { } 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) { - addMessage(Messages.create(cause, getSources(), messageFormat, arguments)); + public Errors addMessage(ErrorId errorId, String messageFormat, Object... arguments) { + return addMessage(errorId, null, messageFormat, arguments); + } + + private Errors addMessage( + ErrorId errorId, Throwable cause, String messageFormat, Object... arguments) { + addMessage(Messages.create(errorId, cause, getSources(), messageFormat, arguments)); return this; } @@ -538,10 +632,16 @@ public final class Errors { return this; } + // TODO(lukes): inline into callers + public static String format(String messageFormat, Object... arguments) { + return Messages.format(messageFormat, arguments); + } + public List getMessages() { if (root.errors == null) { return ImmutableList.of(); } + return new Ordering() { @Override public int compare(Message a, Message b) { @@ -553,4 +653,20 @@ public final class Errors { public int size() { return root.errors == null ? 0 : root.errors.size(); } + + // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult + public static Object convert(Object o) { + return Messages.convert(o); + } + + // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult + public static Object convert(Object o, ElementSource source) { + return Messages.convert(o, source); + } + + // TODO(lukes): inline in callers. There are some callers outside of guice, so this is difficult + public static void formatSource(Formatter formatter, Object source) { + formatter.format(" "); + new SourceFormatter(source, formatter, false).format(); + } } diff --git a/src/main/java/com/google/inject/internal/ExposedKeyFactory.java b/src/main/java/com/google/inject/internal/ExposedKeyFactory.java index 153a601..ea7261a 100644 --- a/src/main/java/com/google/inject/internal/ExposedKeyFactory.java +++ b/src/main/java/com/google/inject/internal/ExposedKeyFactory.java @@ -21,7 +21,7 @@ final class ExposedKeyFactory implements InternalFactory, CreationListener @Override public void notify(Errors errors) { InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector(); - BindingImpl explicitBinding = privateInjector.state.getExplicitBinding(key); + BindingImpl explicitBinding = privateInjector.getBindingData().getExplicitBinding(key); // 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 @@ -34,8 +34,10 @@ final class ExposedKeyFactory implements InternalFactory, CreationListener this.delegate = explicitBinding; } + @Override public T get(InternalContext context, Dependency dependency, boolean linked) throws InternalProvisionException { + // TODO(lukes): add a source to the thrown exception? return delegate.getInternalFactory().get(context, dependency, linked); } } diff --git a/src/main/java/com/google/inject/internal/GenericErrorDetail.java b/src/main/java/com/google/inject/internal/GenericErrorDetail.java new file mode 100644 index 0000000..4d2bf06 --- /dev/null +++ b/src/main/java/com/google/inject/internal/GenericErrorDetail.java @@ -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 + implements Serializable { + public GenericErrorDetail( + ErrorId errorId, String message, List sources, Throwable cause) { + super(errorId, checkNotNull(message, "message"), sources, cause); + } + + @Override + public void formatDetail(List> mergeableErrors, Formatter formatter) { + Preconditions.checkArgument(mergeableErrors.isEmpty(), "Unexpected mergeable errors"); + List dependencies = getSources(); + for (Object source : Lists.reverse(dependencies)) { + formatter.format(" "); + new SourceFormatter(source, formatter, /* omitPreposition= */ false).format(); + } + } + + @Override + public GenericErrorDetail withSources(List newSources) { + return new GenericErrorDetail(errorId, getMessage(), newSources, getCause()); + } +} diff --git a/src/main/java/com/google/inject/internal/GuiceInternal.java b/src/main/java/com/google/inject/internal/GuiceInternal.java new file mode 100644 index 0000000..1fa7c79 --- /dev/null +++ b/src/main/java/com/google/inject/internal/GuiceInternal.java @@ -0,0 +1,14 @@ +package com.google.inject.internal; + +/** + * Class used for restricting APIs in other packages to only be used by this package. + * + *

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() {} +} diff --git a/src/main/java/com/google/inject/internal/InheritingState.java b/src/main/java/com/google/inject/internal/InjectorBindingData.java similarity index 61% rename from src/main/java/com/google/inject/internal/InheritingState.java rename to src/main/java/com/google/inject/internal/InjectorBindingData.java index 3e3f65a..291cdbc 100644 --- a/src/main/java/com/google/inject/internal/InheritingState.java +++ b/src/main/java/com/google/inject/internal/InjectorBindingData.java @@ -1,6 +1,9 @@ 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.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; 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.TypeConverterBinding; import com.google.inject.spi.TypeListenerBinding; - import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; 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 { - - private final State parent; + // The parent injector's InjectorBindingData, if the parent injector exists. + private final Optional parent; // Must be a linked hashmap in order to preserve order of bindings in Modules. private final Map, Binding> explicitBindingsMutable = Maps.newLinkedHashMap(); @@ -44,109 +50,97 @@ final class InheritingState implements State { private final List typeListenerBindings = Lists.newArrayList(); private final List provisionListenerBindings = Lists.newArrayList(); private final List scannerBindings = Lists.newArrayList(); - private final WeakKeySet blacklistedKeys; - private final Object lock; + // The injector's explicit bindings, indexed by the binding's type. + private final ListMultimap, Binding> indexedExplicitBindings = + ArrayListMultimap.create(); - InheritingState(State parent) { - this.parent = checkNotNull(parent, "parent"); - this.lock = (parent == State.NONE) ? this : parent.lock(); - this.blacklistedKeys = new WeakKeySet(lock); + InjectorBindingData(Optional parent) { + this.parent = parent; } - @Override - public State parent() { + public Optional parent() { return parent; } @SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types - @Override public BindingImpl getExplicitBinding(Key key) { Binding binding = explicitBindings.get(key); - return binding != null ? (BindingImpl) binding : parent.getExplicitBinding(key); + if (binding == null && parent.isPresent()) { + return parent.get().getExplicitBinding(key); + } + return (BindingImpl) binding; } - @Override public Map, Binding> getExplicitBindingsThisLevel() { return explicitBindings; } - @Override public void putBinding(Key key, BindingImpl binding) { explicitBindingsMutable.put(key, binding); } - @Override public void putProviderLookup(ProviderLookup lookup) { providerLookups.add(lookup); } - @Override public Set> getProviderLookupsThisLevel() { return providerLookups; } - @Override public void putStaticInjectionRequest(StaticInjectionRequest staticInjectionRequest) { staticInjectionRequests.add(staticInjectionRequest); } - @Override public Set getStaticInjectionRequestsThisLevel() { return staticInjectionRequests; } - @Override public void putInjectionRequest(InjectionRequest injectionRequest) { injectionRequests.add(injectionRequest); } - @Override public Set> getInjectionRequestsThisLevel() { return injectionRequests; } - @Override public void putMembersInjectorLookup(MembersInjectorLookup membersInjectorLookup) { membersInjectorLookups.add(membersInjectorLookup); } - @Override public Set> getMembersInjectorLookupsThisLevel() { return membersInjectorLookups; } - @Override public ScopeBinding getScopeBinding(Class 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 annotationType, ScopeBinding scope) { scopes.put(annotationType, scope); } - @Override public Collection getScopeBindingsThisLevel() { return scopes.values(); } - @Override public Iterable getConvertersThisLevel() { return converters; } - @Override public void addConverter(TypeConverterBinding typeConverterBinding) { converters.add(typeConverterBinding); } - @Override public TypeConverterBinding getConverter( String stringValue, TypeLiteral type, Errors errors, Object source) { TypeConverterBinding matchingConverter = null; - for (State s = this; s != State.NONE; s = s.parent()) { - for (TypeConverterBinding converter : s.getConvertersThisLevel()) { + InjectorBindingData b = this; + while (b != null) { + for (TypeConverterBinding converter : b.getConvertersThisLevel()) { if (converter.getTypeMatcher().matches(type)) { if (matchingConverter != null) { errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter); @@ -154,92 +148,65 @@ final class InheritingState implements State { matchingConverter = converter; } } + b = b.parent().orElse(null); } return matchingConverter; } - @Override public void addTypeListener(TypeListenerBinding listenerBinding) { typeListenerBindings.add(listenerBinding); } - @Override - public List getTypeListenerBindings() { - List parentBindings = parent.getTypeListenerBindings(); - List result = - Lists.newArrayListWithCapacity(parentBindings.size() + typeListenerBindings.size()); - result.addAll(parentBindings); - result.addAll(typeListenerBindings); - return result; + public ImmutableList getTypeListenerBindings() { + if (parent.isPresent()) { + return new ImmutableList.Builder() + .addAll(parent.get().getTypeListenerBindings()) + .addAll(typeListenerBindings) + .build(); + } + return ImmutableList.copyOf(typeListenerBindings); } - @Override - public List getTypeListenerBindingsThisLevel() { - return typeListenerBindings; + public ImmutableList getTypeListenerBindingsThisLevel() { + return ImmutableList.copyOf(typeListenerBindings); } - @Override public void addProvisionListener(ProvisionListenerBinding listenerBinding) { provisionListenerBindings.add(listenerBinding); } - @Override - public List getProvisionListenerBindings() { - List parentBindings = parent.getProvisionListenerBindings(); - List result = - Lists.newArrayListWithCapacity(parentBindings.size() + provisionListenerBindings.size()); - result.addAll(parentBindings); - result.addAll(provisionListenerBindings); - return result; + public ImmutableList getProvisionListenerBindings() { + if (parent.isPresent()) { + return new ImmutableList.Builder() + .addAll(parent.get().getProvisionListenerBindings()) + .addAll(provisionListenerBindings) + .build(); + } + return ImmutableList.copyOf(provisionListenerBindings); } - @Override - public List getProvisionListenerBindingsThisLevel() { - return provisionListenerBindings; + public ImmutableList getProvisionListenerBindingsThisLevel() { + return ImmutableList.copyOf(provisionListenerBindings); } - @Override public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) { scannerBindings.add(scanner); } - @Override - public List getScannerBindings() { - List parentBindings = parent.getScannerBindings(); - List result = - Lists.newArrayListWithCapacity(parentBindings.size() + scannerBindings.size()); - result.addAll(parentBindings); - result.addAll(scannerBindings); - return result; + public ImmutableList getScannerBindings() { + if (parent.isPresent()) { + return new ImmutableList.Builder() + .addAll(parent.get().getScannerBindings()) + .addAll(scannerBindings) + .build(); + } + return ImmutableList.copyOf(scannerBindings); } - @Override - public List getScannerBindingsThisLevel() { - return scannerBindings; + public ImmutableList getScannerBindingsThisLevel() { + return ImmutableList.copyOf(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 getSourcesForBlacklistedKey(Key key) { - return blacklistedKeys.getSources(key); - } - - @Override - public Object lock() { - return lock; - } - - @Override public Map, Scope> getScopes() { ImmutableMap.Builder, Scope> builder = ImmutableMap.builder(); for (Map.Entry, ScopeBinding> entry : scopes.entrySet()) { @@ -247,4 +214,18 @@ final class InheritingState implements State { } 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, Binding> getIndexedExplicitBindings() { + return indexedExplicitBindings; + } } diff --git a/src/main/java/com/google/inject/internal/InjectorImpl.java b/src/main/java/com/google/inject/internal/InjectorImpl.java index 45715e3..332fdf3 100644 --- a/src/main/java/com/google/inject/internal/InjectorImpl.java +++ b/src/main/java/com/google/inject/internal/InjectorImpl.java @@ -1,15 +1,13 @@ package com.google.inject.internal; import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.base.MoreObjects; import com.google.common.base.Objects; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import com.google.inject.Binder; @@ -36,7 +34,6 @@ import com.google.inject.spi.InstanceBinding; import com.google.inject.spi.ProviderBinding; import com.google.inject.spi.TypeConverterBinding; import com.google.inject.util.Providers; - import java.lang.annotation.Annotation; import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; @@ -51,54 +48,72 @@ import java.util.Set; /** * Default {@link Injector} implementation. * + * @author crazybob@google.com (Bob Lee) */ final class InjectorImpl implements Injector, Lookups { - public static final TypeLiteral STRING_TYPE = TypeLiteral.get(String.class); - final State state; + /** Options that control how the injector behaves. */ + static class InjectorOptions { + final Stage stage; + final boolean jitDisabled; + final boolean disableCircularProxies; + final boolean atInjectRequired; + final boolean exactBindingAnnotationsRequired; + InjectorOptions( + Stage stage, + boolean jitDisabled, + boolean disableCircularProxies, + boolean atInjectRequired, + boolean exactBindingAnnotationsRequired) { + this.stage = stage; + this.jitDisabled = jitDisabled; + this.disableCircularProxies = disableCircularProxies; + this.atInjectRequired = atInjectRequired; + this.exactBindingAnnotationsRequired = exactBindingAnnotationsRequired; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("stage", stage) + .add("jitDisabled", jitDisabled) + .add("disableCircularProxies", disableCircularProxies) + .add("atInjectRequired", atInjectRequired) + .add("exactBindingAnnotationsRequired", exactBindingAnnotationsRequired) + .toString(); + } + } + + /** some limitations on what just in time bindings are allowed. */ + enum JitLimitation { + /** does not allow just in time bindings */ + NO_JIT, + /** allows existing just in time bindings, but does not allow new ones */ + EXISTING_JIT, + /** allows existing just in time bindings & allows new ones to be created */ + NEW_OR_EXISTING_JIT, + } + + private final InjectorBindingData bindingData; + private final InjectorJitBindingData jitBindingData; final InjectorImpl parent; - - final ListMultimap, Binding> bindingsMultimap = ArrayListMultimap.create(); - final InjectorOptions options; - /** - * Just-in-time binding cache. Guarded by state.lock() - */ - final Map, BindingImpl> jitBindings = Maps.newHashMap(); - - /** - * Cache of Keys that we were unable to create JIT bindings for, so we don't - * keep trying. Also guarded by state.lock(). - */ - final Set> failedJitBindings = Sets.newHashSet(); - Lookups lookups = new DeferredLookups(this); /** The set of types passed to {@link #getMembersInjector} and {@link #injectMembers}. */ final Set> userRequestedMembersInjectorTypes = Sets.newConcurrentHashSet(); - /** - * Cached constructor injectors for each type - */ - final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this); - - private final ThreadLocal localContext; - - /** - * Cached field and method injectors for each type. - */ - MembersInjectorStore membersInjectorStore; - /** - * Cached provision listener callbacks for each key. - */ - ProvisionListenerCallbackStore provisionListenerStore; - - InjectorImpl(InjectorImpl parent, State state, InjectorOptions injectorOptions) { + InjectorImpl( + InjectorImpl parent, + InjectorBindingData bindingData, + InjectorJitBindingData jitBindingData, + InjectorOptions injectorOptions) { this.parent = parent; - this.state = state; + this.bindingData = bindingData; + this.jitBindingData = jitBindingData; this.options = injectorOptions; if (parent != null) { @@ -111,46 +126,16 @@ final class InjectorImpl implements Injector, Lookups { } } - - /** Only to be called by the {@link SingletonScope} provider. */ - InternalContext getLocalContext() { - return (InternalContext) localContext.get()[0]; - } - - InternalContext enterContext() { - Object[] reference = localContext.get(); - if (reference == null) { - reference = new Object[1]; - localContext.set(reference); - } - InternalContext ctx = (InternalContext) reference[0]; - if (ctx == null) { - reference[0] = ctx = new InternalContext(options, reference); - } else { - ctx.enter(); - } - return ctx; - } - - /** - * Indexes bindings by type. - */ - void index() { - for (Binding binding : state.getExplicitBindingsThisLevel().values()) { - bindingsMultimap.put(binding.getKey().getTypeLiteral(), binding); - } - } - @Override public List> findBindingsByType(TypeLiteral type) { @SuppressWarnings("unchecked") // safe because we only put matching entries into the map - List> list = (List>) (List) bindingsMultimap.get(checkNotNull(type, "type")); + List> list = + (List>) + (List) bindingData.getIndexedExplicitBindings().get(checkNotNull(type, "type")); return Collections.unmodifiableList(list); } - /** - * Returns the binding for {@code key} - */ + /** Returns the binding for {@code key} */ @Override public BindingImpl getBinding(Key key) { Errors errors = new Errors(checkNotNull(key, "key")); @@ -159,22 +144,25 @@ final class InjectorImpl implements Injector, Lookups { errors.throwConfigurationExceptionIfErrorsExist(); return result; } catch (ErrorsException e) { - throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + ConfigurationException exception = + new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + + throw exception; } } @Override public BindingImpl getExistingBinding(Key key) { // Check explicit bindings, i.e. bindings created by modules. - BindingImpl explicitBinding = state.getExplicitBinding(key); + BindingImpl explicitBinding = bindingData.getExplicitBinding(checkNotNull(key, "key")); if (explicitBinding != null) { return explicitBinding; } - synchronized (state.lock()) { + synchronized (jitBindingData.lock()) { // See if any jit bindings have been created for this key. for (InjectorImpl injector = this; injector != null; injector = injector.parent) { @SuppressWarnings("unchecked") - BindingImpl jitBinding = (BindingImpl) injector.jitBindings.get(key); + BindingImpl jitBinding = (BindingImpl) injector.jitBindingData.getJitBinding(key); if (jitBinding != null) { return jitBinding; } @@ -192,7 +180,8 @@ final class InjectorImpl implements Injector, Lookups { return getBinding(key); } } catch (ErrorsException e) { - throw new ConfigurationException(e.getErrors().getMessages()); + ConfigurationException exception = new ConfigurationException(e.getErrors().getMessages()); + throw exception; } } @@ -201,15 +190,15 @@ final class InjectorImpl implements Injector, Lookups { } /** - * Gets a binding implementation. First, it check to see if the parent has a binding. If the - * parent has a binding and the binding is scoped, it will use that binding. Otherwise, this + * Gets a binding implementation. First, it check to see if the parent has a binding. If the + * parent has a binding and the binding is scoped, it will use that binding. Otherwise, this * checks for an explicit binding. If no explicit binding is found, it looks for a just-in-time * binding. */ BindingImpl getBindingOrThrow(Key key, Errors errors, JitLimitation jitType) throws ErrorsException { // Check explicit bindings, i.e. bindings created by modules. - BindingImpl binding = state.getExplicitBinding(key); + BindingImpl binding = bindingData.getExplicitBinding(key); if (binding != null) { return binding; } @@ -238,6 +227,14 @@ final class InjectorImpl implements Injector, Lookups { return createChildInjector(ImmutableList.copyOf(modules)); } + InjectorBindingData getBindingData() { + return bindingData; + } + + InjectorJitBindingData getJitBindingData() { + return jitBindingData; + } + /** * Returns a just-in-time binding for {@code key}, creating it if necessary. * @@ -247,11 +244,11 @@ final class InjectorImpl implements Injector, Lookups { throws ErrorsException { boolean jitOverride = isProvider(key) || isTypeLiteral(key) || isMembersInjector(key); - synchronized (state.lock()) { + synchronized (jitBindingData.lock()) { // first try to find a JIT binding that we've already created for (InjectorImpl injector = this; injector != null; injector = injector.parent) { @SuppressWarnings("unchecked") // we only store bindings that match their key - BindingImpl binding = (BindingImpl) injector.jitBindings.get(key); + BindingImpl binding = (BindingImpl) injector.jitBindingData.getJitBindings().get(key); if (binding != null) { // If we found a JIT binding and we don't allow them, @@ -273,24 +270,22 @@ final class InjectorImpl implements Injector, Lookups { // entry in jitBindings (during cleanup), and we may be trying // to create it again (in the case of a recursive JIT binding). // We need both of these guards for different reasons - // failedJitBindings.contains: We want to continue processing if we've never + // isFailedJitBinding: We want to continue processing if we've never // failed before, so that our initial error message contains // as much useful information as possible about what errors exist. // errors.hasErrors: If we haven't already failed, then it's OK to // continue processing, to make sure the ultimate error message // is the correct one. // See: ImplicitBindingsTest#testRecursiveJitBindingsCleanupCorrectly - // for where this guard compes into play. - if (failedJitBindings.contains(key) && errors.hasErrors()) { + // for where this guard comes into play. + if (jitBindingData.isFailedJitBinding(key) && errors.hasErrors()) { throw errors.toException(); } return createJustInTimeBindingRecursive(key, errors, options.jitDisabled, jitType); - } // end synchronized(state.lock()) + } // end synchronized(jitBindingData.lock()) } - /** - * Returns true if the key type is Provider (but not a subclass of Provider). - */ + /** Returns true if the key type is Provider (but not a subclass of Provider). */ private static boolean isProvider(Key key) { return key.getTypeLiteral().getRawType().equals(Provider.class); } @@ -299,7 +294,6 @@ final class InjectorImpl implements Injector, Lookups { return key.getTypeLiteral().getRawType().equals(TypeLiteral.class); } - private static Key getProvidedKey(Key> key, Errors errors) throws ErrorsException { Type providerType = key.getTypeLiteral().getType(); @@ -312,7 +306,7 @@ final class InjectorImpl implements Injector, Lookups { Type entryType = ((ParameterizedType) providerType).getActualTypeArguments()[0]; @SuppressWarnings("unchecked") // safe because T came from Key> - Key providedKey = (Key) key.ofType(entryType); + Key providedKey = (Key) key.ofType(entryType); return providedKey; } @@ -330,677 +324,58 @@ final class InjectorImpl implements Injector, Lookups { } @SuppressWarnings("unchecked") // safe because T came from Key> - TypeLiteral instanceType = (TypeLiteral) TypeLiteral.get( - ((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]); + TypeLiteral instanceType = + (TypeLiteral) + TypeLiteral.get(((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]); MembersInjector membersInjector = membersInjectorStore.get(instanceType, errors); InternalFactory> factory = - new ConstantFactory<>(Initializables.of(membersInjector)); + new ConstantFactory>(Initializables.of(membersInjector)); - return new InstanceBindingImpl<>(this, key, SourceProvider.UNKNOWN_SOURCE, - factory, ImmutableSet.of(), membersInjector); + return new InstanceBindingImpl>( + this, + key, + SourceProvider.UNKNOWN_SOURCE, + factory, + ImmutableSet.of(), + membersInjector); } /** - * Creates a synthetic binding to {@code Provider}, i.e. a binding to the provider from - * {@code Binding}. + * Creates a synthetic binding to {@code Provider}, i.e. a framework-created JIT binding to the + * provider from {@code Binding}. */ - private BindingImpl> createProviderBinding(Key> key, Errors errors) - throws ErrorsException { + private BindingImpl> createSyntheticProviderBinding( + Key> key, Errors errors) throws ErrorsException { Key providedKey = getProvidedKey(key, errors); BindingImpl delegate = getBindingOrThrow(providedKey, errors, JitLimitation.NO_JIT); - return new ProviderBindingImpl(this, key, delegate); + return new SyntheticProviderBindingImpl(this, key, delegate); } - /** - * Converts a constant string binding to the required type. - * - * @return the binding if it could be resolved, or null if the binding doesn't exist - * @throws com.google.inject.internal.ErrorsException if there was an error resolving the binding - */ - private BindingImpl convertConstantStringBinding(Key key, Errors errors) - throws ErrorsException { - // Find a constant string binding. - Key stringKey = key.ofType(STRING_TYPE); - BindingImpl stringBinding = state.getExplicitBinding(stringKey); - if (stringBinding == null || !stringBinding.isConstant()) { - return null; - } - - // We can't call getProvider().get() because this InstanceBinding may not have been inintialized - // yet (because we may have been called during InternalInjectorCreator.initializeStatically and - // instance binding validation hasn't happened yet.) - @SuppressWarnings("unchecked") - String stringValue = ((InstanceBinding) stringBinding).getInstance(); - Object source = stringBinding.getSource(); - - // Find a matching type converter. - TypeLiteral type = key.getTypeLiteral(); - TypeConverterBinding typeConverterBinding = state.getConverter(stringValue, type, errors, source); - - if (typeConverterBinding == null) { - // No converter can handle the given type. - return null; - } - - // Try to convert the string. A failed conversion results in an error. - try { - @SuppressWarnings("unchecked") // This cast is safe because we double check below. - T converted = (T) typeConverterBinding.getTypeConverter().convert(stringValue, type); - - if (converted == null) { - throw errors.converterReturnedNull(stringValue, source, type, typeConverterBinding) - .toException(); - } - - if (!type.getRawType().isInstance(converted)) { - throw errors.conversionTypeError(stringValue, source, type, typeConverterBinding, converted) - .toException(); - } - - return new ConvertedConstantBindingImpl(this, key, converted, stringBinding, - typeConverterBinding); - } catch (RuntimeException e) { - throw errors.conversionError(stringValue, source, type, typeConverterBinding, e) - .toException(); - } - } - - void initializeBinding(BindingImpl binding, Errors errors) throws ErrorsException { - if (binding instanceof DelayedInitialize) { - ((DelayedInitialize) binding).initialize(this, errors); - } - } - - void initializeJitBinding(BindingImpl binding, Errors errors) throws ErrorsException { - // Put the partially constructed binding in the map a little early. This enables us to handle - // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl. - // Note: We don't need to synchronize on state.lock() during injector creation. - if (binding instanceof DelayedInitialize) { - Key key = binding.getKey(); - jitBindings.put(key, binding); - boolean successful = false; - DelayedInitialize delayed = (DelayedInitialize) binding; - try { - delayed.initialize(this, errors); - successful = true; - } finally { - if (!successful) { - // We do not pass cb.getInternalConstructor as the second parameter - // so that cached exceptions while constructing it get stored. - // See TypeListenerTest#testTypeListenerThrows - removeFailedJitBinding(binding, null); - cleanup(binding, new HashSet<>()); - } - } - } - } - - /** - * Iterates through the binding's dependencies to clean up any stray bindings that were leftover - * from a failed JIT binding. This is required because the bindings are eagerly & - * optimistically added to allow circular dependency support, so dependencies may pass where they - * should have failed. - */ - private boolean cleanup(BindingImpl binding, Set> encountered) { - boolean bindingFailed = false; - Set> deps = getInternalDependencies(binding); - for (Dependency dep : deps) { - Key depKey = dep.getKey(); - InjectionPoint ip = dep.getInjectionPoint(); - if (encountered.add(depKey)) { // only check if we haven't looked at this key yet - BindingImpl depBinding = jitBindings.get(depKey); - if (depBinding != null) { // if the binding still exists, validate - boolean failed = cleanup(depBinding, encountered); // if children fail, we fail - if (depBinding instanceof ConstructorBindingImpl) { - ConstructorBindingImpl ctorBinding = (ConstructorBindingImpl) depBinding; - ip = ctorBinding.getInternalConstructor(); - if (!ctorBinding.isInitialized()) { - failed = true; - } - } - if (failed) { - removeFailedJitBinding(depBinding, ip); - bindingFailed = true; - } - } else if (state.getExplicitBinding(depKey) == null) { - // ignore keys if they were explicitly bound, but if neither JIT - // nor explicit, it's also invalid & should let parent know. - bindingFailed = true; - } - } - } - return bindingFailed; - } - - /** - * Cleans up any state that may have been cached when constructing the JIT binding. - */ - private void removeFailedJitBinding(Binding binding, InjectionPoint ip) { - failedJitBindings.add(binding.getKey()); - jitBindings.remove(binding.getKey()); - membersInjectorStore.remove(binding.getKey().getTypeLiteral()); - provisionListenerStore.remove(binding); - if (ip != null) { - constructors.remove(ip); - } - } - - /** - * Safely gets the dependencies of possibly not initialized bindings. - */ - @SuppressWarnings("unchecked") - private Set> getInternalDependencies(BindingImpl binding) { - if (binding instanceof ConstructorBindingImpl) { - return ((ConstructorBindingImpl) binding).getInternalDependencies(); - } else if (binding instanceof HasDependencies) { - return ((HasDependencies) binding).getDependencies(); - } else { - return ImmutableSet.of(); - } - } - - /** - * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if - * none is specified. - */ - BindingImpl createUninitializedBinding(Key key, Scoping scoping, Object source, - Errors errors, boolean jitBinding) throws ErrorsException { - Class rawType = key.getTypeLiteral().getRawType(); - - ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class); - - // Don't try to inject arrays or enums annotated with @ImplementedBy. - if (rawType.isArray() || (rawType.isEnum() && implementedBy != null)) { - throw errors.missingImplementationWithHint(key, this).toException(); - } - - // Handle TypeLiteral by binding the inner type - if (rawType == TypeLiteral.class) { - @SuppressWarnings("unchecked") // we have to fudge the inner type as Object - BindingImpl binding = (BindingImpl) createTypeLiteralBinding( - (Key>) key, errors); - return binding; - } - - // Handle @ImplementedBy - if (implementedBy != null) { - Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); - return createImplementedByBinding(key, scoping, implementedBy, errors); - } - - // Handle @ProvidedBy. - ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class); - if (providedBy != null) { - Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); - return createProvidedByBinding(key, scoping, providedBy, errors); - } - - - return ConstructorBindingImpl.create(this, - key, - null, /* use default constructor */ - source, - scoping, - errors, - jitBinding && options.jitDisabled, - options.atInjectRequired); - } - - /** - * Converts a binding for a {@code Key>} to the value {@code TypeLiteral}. It's - * a bit awkward because we have to pull out the inner type in the type literal. - */ - private BindingImpl> createTypeLiteralBinding( - Key> key, Errors errors) throws ErrorsException { - Type typeLiteralType = key.getTypeLiteral().getType(); - if (!(typeLiteralType instanceof ParameterizedType)) { - throw errors.cannotInjectRawTypeLiteral().toException(); - } - - ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType; - Type innerType = parameterizedType.getActualTypeArguments()[0]; - - // this is unforunate. We don't support building TypeLiterals for type variable like 'T'. If - // this proves problematic, we can probably fix TypeLiteral to support type variables - if (!(innerType instanceof Class) - && !(innerType instanceof GenericArrayType) - && !(innerType instanceof ParameterizedType)) { - throw errors.cannotInjectTypeLiteralOf(innerType).toException(); - } - - @SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe - TypeLiteral value = (TypeLiteral) TypeLiteral.get(innerType); - InternalFactory> factory = new ConstantFactory<>(Initializables.of(value)); - return new InstanceBindingImpl<>(this, key, SourceProvider.UNKNOWN_SOURCE, - factory, ImmutableSet.of(), value); - } - - /** - * Creates a binding for a type annotated with @ProvidedBy. - */ - BindingImpl createProvidedByBinding(Key key, Scoping scoping, - ProvidedBy providedBy, Errors errors) throws ErrorsException { - Class rawType = key.getTypeLiteral().getRawType(); - Class> providerType = providedBy.value(); - - // Make sure it's not the same type. TODO: Can we check for deeper loops? - if (providerType == rawType) { - throw errors.recursiveProviderType().toException(); - } - - // Assume the provider provides an appropriate type. We double check at runtime. - @SuppressWarnings("unchecked") - Key> providerKey = (Key>) Key.get(providerType); - ProvidedByInternalFactory internalFactory = - new ProvidedByInternalFactory(rawType, providerType, providerKey); - BindingImpl binding = LinkedProviderBindingImpl.createWithInitializer( - this, - key, - rawType, - Scoping.scope(key, this, internalFactory, rawType, scoping), - scoping, - providerKey, - internalFactory); - internalFactory.setProvisionListenerCallback(provisionListenerStore.get(binding)); - return binding; - } - - /** - * Creates a binding for a type annotated with @ImplementedBy. - */ - private BindingImpl createImplementedByBinding(Key key, Scoping scoping, - ImplementedBy implementedBy, Errors errors) - throws ErrorsException { - Class rawType = key.getTypeLiteral().getRawType(); - Class implementationType = implementedBy.value(); - - // Make sure it's not the same type. TODO: Can we check for deeper cycles? - if (implementationType == rawType) { - throw errors.recursiveImplementationType().toException(); - } - - // Make sure implementationType extends type. - if (!rawType.isAssignableFrom(implementationType)) { - throw errors.notASubtype(implementationType, rawType).toException(); - } - - @SuppressWarnings("unchecked") // After the preceding check, this cast is safe. - Class subclass = (Class) implementationType; - - // Look up the target binding. - final Key targetKey = Key.get(subclass); - - FactoryProxy factory = new FactoryProxy<>(this, key, targetKey, rawType); - factory.notify(errors); // causes the factory to initialize itself internally - return new LinkedBindingImpl( - this, - key, - rawType, - Scoping.scope(key, this, factory, rawType, scoping), - scoping, - targetKey); - } - - /** - * Attempts to create a just-in-time binding for {@code key} in the root injector, falling back to - * other ancestor injectors until this injector is tried. - */ - private BindingImpl createJustInTimeBindingRecursive(Key key, Errors errors, - boolean jitDisabled, JitLimitation jitType) throws ErrorsException { - // ask the parent to create the JIT binding - if (parent != null) { - if (jitType == JitLimitation.NEW_OR_EXISTING_JIT - && jitDisabled && !parent.options.jitDisabled) { - // If the binding would be forbidden here but allowed in a parent, report an error instead - throw errors.jitDisabledInParent(key).toException(); - } - - try { - return parent.createJustInTimeBindingRecursive(key, new Errors(), jitDisabled, - parent.options.jitDisabled ? JitLimitation.NO_JIT : jitType); - } catch (ErrorsException ignored) { - } - } - - // Retrieve the sources before checking for blacklisting to guard against sources becoming null - // due to a full GC happening after calling state.isBlacklisted and - // state.getSourcesForBlacklistedKey. - // TODO(user): Consolidate these two APIs. - Set sources = state.getSourcesForBlacklistedKey(key); - if (state.isBlacklisted(key)) { - throw errors.childBindingAlreadySet(key, sources).toException(); - } - - key = MoreTypes.canonicalizeKey(key); // before storing the key long-term, canonicalize it. - BindingImpl binding = createJustInTimeBinding(key, errors, jitDisabled, jitType); - state.parent().blacklist(key, state, binding.getSource()); - jitBindings.put(key, binding); - return binding; - } - - /** - * Returns a new just-in-time binding created by resolving {@code key}. The strategies used to - * create just-in-time bindings are: - *
    - *
  1. Internalizing Providers. If the requested binding is for {@code Provider}, we delegate - * to the binding for {@code T}. - *
  2. Converting constants. - *
  3. ImplementedBy and ProvidedBy annotations. Only for unannotated keys. - *
  4. The constructor of the raw type. Only for unannotated keys. - *
- * - * @throws com.google.inject.internal.ErrorsException if the binding cannot be created. - */ - private BindingImpl createJustInTimeBinding(Key key, Errors errors, - boolean jitDisabled, JitLimitation jitType) throws ErrorsException { - int numErrorsBefore = errors.size(); - - // Retrieve the sources before checking for blacklisting to guard against sources becoming null - // due to a full GC happening after calling state.isBlacklisted and - // state.getSourcesForBlacklistedKey. - // TODO(user): Consolidate these two APIs. - Set sources = state.getSourcesForBlacklistedKey(key); - if (state.isBlacklisted(key)) { - throw errors.childBindingAlreadySet(key, sources).toException(); - } - - // Handle cases where T is a Provider. - if (isProvider(key)) { - // These casts are safe. We know T extends Provider and that given Key>, - // createProviderBinding() will return BindingImpl>. - @SuppressWarnings({"unchecked", "cast"}) - BindingImpl binding = (BindingImpl) createProviderBinding((Key) key, errors); - return binding; - } - - // Handle cases where T is a MembersInjector - if (isMembersInjector(key)) { - // These casts are safe. T extends MembersInjector and that given Key>, - // createMembersInjectorBinding() will return BindingImpl>. - @SuppressWarnings({"unchecked", "cast"}) - BindingImpl binding = (BindingImpl) createMembersInjectorBinding((Key) key, errors); - return binding; - } - - // Try to convert a constant string binding to the requested type. - BindingImpl convertedBinding = convertConstantStringBinding(key, errors); - if (convertedBinding != null) { - return convertedBinding; - } - - if (!isTypeLiteral(key) && jitDisabled && jitType != JitLimitation.NEW_OR_EXISTING_JIT) { - throw errors.jitDisabled(key).toException(); - } - - // If the key has an annotation... - if (key.getAnnotationType() != null) { - // Look for a binding without annotation attributes or return null. - if (key.hasAttributes() && !options.exactBindingAnnotationsRequired) { - try { - Errors ignored = new Errors(); - return getBindingOrThrow(key.withoutAttributes(), ignored, JitLimitation.NO_JIT); - } catch (ErrorsException ignored) { - // throw with a more appropriate message below - } - } - throw errors.missingImplementation(key).toException(); - } - - Object source = key.getTypeLiteral().getRawType(); - BindingImpl binding = createUninitializedBinding(key, Scoping.UNSCOPED, source, errors, true); - errors.throwIfNewErrors(numErrorsBefore); - initializeJitBinding(binding, errors); - return binding; - } - - InternalFactory getInternalFactory(Key key, Errors errors, JitLimitation jitType) - throws ErrorsException { - return getBindingOrThrow(key, errors, jitType).getInternalFactory(); - } - - @Override - public Map, Binding> getBindings() { - return state.getExplicitBindingsThisLevel(); - } - - @Override - public Map, Binding> getAllBindings() { - synchronized (state.lock()) { - return new ImmutableMap.Builder, Binding>() - .putAll(state.getExplicitBindingsThisLevel()) - .putAll(jitBindings) - .build(); - } - } - - @Override - public Map, Scope> getScopeBindings() { - return ImmutableMap.copyOf(state.getScopes()); - } - - @Override - public Set getTypeConverterBindings() { - return ImmutableSet.copyOf(state.getConvertersThisLevel()); - } - - @Override - public List getElements() { - ImmutableList.Builder elements = ImmutableList.builder(); - elements.addAll(getAllBindings().values()); - elements.addAll(state.getProviderLookupsThisLevel()); - elements.addAll(state.getConvertersThisLevel()); - elements.addAll(state.getScopeBindingsThisLevel()); - elements.addAll(state.getTypeListenerBindingsThisLevel()); - elements.addAll(state.getProvisionListenerBindingsThisLevel()); - elements.addAll(state.getScannerBindingsThisLevel()); - elements.addAll(state.getStaticInjectionRequestsThisLevel()); - elements.addAll(state.getMembersInjectorLookupsThisLevel()); - elements.addAll(state.getInjectionRequestsThisLevel()); - - return elements.build(); - } - - @SuppressWarnings("unchecked") - @Override - public Map, List> getAllMembersInjectorInjectionPoints() { - // Note, this is a safe cast per the ListMultimap javadocs. - // We could use Multimaps.asMap to avoid the cast, but unfortunately it's a @Beta method. - return (Map, List>) - (Map, ?>) - ImmutableListMultimap.copyOf( - Multimaps.filterKeys( - membersInjectorStore.getAllInjectionPoints(), - userRequestedMembersInjectorTypes::contains)) - .asMap(); - } - - /** - * Returns parameter injectors, or {@code null} if there are no parameters. - */ - SingleParameterInjector[] getParametersInjectors( - List> parameters, Errors errors) throws ErrorsException { - if (parameters.isEmpty()) { - return null; - } - - int numErrorsBefore = errors.size(); - SingleParameterInjector[] result = new SingleParameterInjector[parameters.size()]; - int i = 0; - for (Dependency parameter : parameters) { - try { - result[i++] = createParameterInjector(parameter, errors.withSource(parameter)); - } catch (ErrorsException rethrownBelow) { - // rethrown below - } - } - - errors.throwIfNewErrors(numErrorsBefore); - return result; - } - - SingleParameterInjector createParameterInjector(final Dependency dependency, - final Errors errors) throws ErrorsException { - BindingImpl binding = getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT); - return new SingleParameterInjector(dependency, binding); - } - - @SuppressWarnings({"unchecked","rawtypes"}) // the members injector type is consistent with instance's type - @Override - public void injectMembers(Object instance) { - MembersInjector membersInjector = getMembersInjector(instance.getClass()); - membersInjector.injectMembers(instance); - } - - @Override - public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { - checkNotNull(typeLiteral, "typeLiteral"); - userRequestedMembersInjectorTypes.add(typeLiteral); - - Errors errors = new Errors(typeLiteral); - try { - return membersInjectorStore.get(typeLiteral, errors); - } catch (ErrorsException e) { - throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); - } - } - - @Override - public MembersInjector getMembersInjector(Class type) { - return getMembersInjector(TypeLiteral.get(type)); - } - - @Override - public Provider getProvider(Class type) { - return getProvider(Key.get(checkNotNull(type, "type"))); - } - - Provider getProviderOrThrow(final Dependency dependency, Errors errors) throws ErrorsException { - final Key key = dependency.getKey(); - final BindingImpl binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT); - final InternalFactory internalFactory = binding.getInternalFactory(); - final Object source = binding.getSource(); - return new Provider<>() { - public T get() { - InternalContext currentContext = enterContext(); - Dependency previous = currentContext.pushDependency(dependency, source); - try { - return internalFactory.get(currentContext, dependency, false); - } catch (InternalProvisionException e) { - throw e.addSource(dependency).toProvisionException(); - } finally { - currentContext.popStateAndSetDependency(previous); - currentContext.close(); - } - } - - @Override - public String toString() { - return internalFactory.toString(); - } - }; - } - - @Override - public Provider getProvider(final Key key) { - checkNotNull(key, "key"); - Errors errors = new Errors(key); - try { - Provider result = getProviderOrThrow(Dependency.get(key), errors); - errors.throwIfNewErrors(0); - return result; - } catch (ErrorsException e) { - throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); - } - } - - @Override - public T getInstance(Key key) { - return getProvider(key).get(); - } - - @Override - public T getInstance(Class type) { - return getProvider(type).get(); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(Injector.class) - .add("bindings", state.getExplicitBindingsThisLevel().values()) - .toString(); - } - - /** - * some limitations on what just in time bindings are allowed. - */ - enum JitLimitation { - /** - * does not allow just in time bindings - */ - NO_JIT, - /** - * allows existing just in time bindings, but does not allow new ones - */ - EXISTING_JIT, - /** - * allows existing just in time bindings & allows new ones to be created - */ - NEW_OR_EXISTING_JIT, - } - - /** - * Invokes a method. - */ - interface MethodInvoker { - Object invoke(Object target, Object... parameters) - throws IllegalAccessException, InvocationTargetException; - } - - /** - * Options that control how the injector behaves. - */ - static class InjectorOptions { - final Stage stage; - final boolean jitDisabled; - final boolean disableCircularProxies; - final boolean atInjectRequired; - final boolean exactBindingAnnotationsRequired; - - InjectorOptions(Stage stage, boolean jitDisabled, boolean disableCircularProxies, - boolean atInjectRequired, boolean exactBindingAnnotationsRequired) { - this.stage = stage; - this.jitDisabled = jitDisabled; - this.disableCircularProxies = disableCircularProxies; - this.atInjectRequired = atInjectRequired; - this.exactBindingAnnotationsRequired = exactBindingAnnotationsRequired; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(getClass()) - .add("stage", stage) - .add("jitDisabled", jitDisabled) - .add("disableCircularProxies", disableCircularProxies) - .add("atInjectRequired", atInjectRequired) - .add("exactBindingAnnotationsRequired", exactBindingAnnotationsRequired) - .toString(); - } - } - - private static class ProviderBindingImpl extends BindingImpl> + /** A framework-created JIT Provider binding. */ + private static class SyntheticProviderBindingImpl extends BindingImpl> implements ProviderBinding>, HasDependencies { final BindingImpl providedBinding; - ProviderBindingImpl(InjectorImpl injector, Key> key, Binding providedBinding) { - super(injector, key, providedBinding.getSource(), createInternalFactory(providedBinding), + SyntheticProviderBindingImpl( + InjectorImpl injector, Key> key, Binding providedBinding) { + super( + injector, + key, + providedBinding.getSource(), + createInternalFactory(providedBinding), Scoping.UNSCOPED); this.providedBinding = (BindingImpl) providedBinding; } static InternalFactory> createInternalFactory(Binding providedBinding) { final Provider provider = providedBinding.getProvider(); - return (context, dependency, linked) -> provider; + return new InternalFactory>() { + @Override + public Provider get(InternalContext context, Dependency dependency, boolean linked) { + return provider; + } + }; } @Override @@ -1033,8 +408,8 @@ final class InjectorImpl implements Injector, Lookups { @Override public boolean equals(Object obj) { - if (obj instanceof ProviderBindingImpl) { - ProviderBindingImpl o = (ProviderBindingImpl) obj; + if (obj instanceof SyntheticProviderBindingImpl) { + SyntheticProviderBindingImpl o = (SyntheticProviderBindingImpl) obj; return getKey().equals(o.getKey()) && getScoping().equals(o.getScoping()) && Objects.equal(providedBinding, o.providedBinding); @@ -1049,20 +424,84 @@ final class InjectorImpl implements Injector, Lookups { } } - private static class ConvertedConstantBindingImpl - extends BindingImpl implements ConvertedConstantBinding { + /** + * Converts a constant string binding to the required type. + * + * @return the binding if it could be resolved, or null if the binding doesn't exist + * @throws com.google.inject.internal.ErrorsException if there was an error resolving the binding + */ + private BindingImpl convertConstantStringBinding(Key key, Errors errors) + throws ErrorsException { + // Find a constant string binding. + Key stringKey = key.ofType(STRING_TYPE); + BindingImpl stringBinding = bindingData.getExplicitBinding(stringKey); + if (stringBinding == null || !stringBinding.isConstant()) { + return null; + } + + // We can't call getProvider().get() because this InstanceBinding may not have been inintialized + // yet (because we may have been called during InternalInjectorCreator.initializeStatically and + // instance binding validation hasn't happened yet.) + String stringValue = ((InstanceBinding) stringBinding).getInstance(); + Object source = stringBinding.getSource(); + + // Find a matching type converter. + TypeLiteral type = key.getTypeLiteral(); + TypeConverterBinding typeConverterBinding = + bindingData.getConverter(stringValue, type, errors, source); + + if (typeConverterBinding == null) { + // No converter can handle the given type. + return null; + } + + // Try to convert the string. A failed conversion results in an error. + try { + @SuppressWarnings("unchecked") // This cast is safe because we double check below. + T converted = (T) typeConverterBinding.getTypeConverter().convert(stringValue, type); + + if (converted == null) { + throw errors + .converterReturnedNull(stringValue, source, type, typeConverterBinding) + .toException(); + } + + if (!type.getRawType().isInstance(converted)) { + throw errors + .conversionTypeError(stringValue, source, type, typeConverterBinding, converted) + .toException(); + } + + return new ConvertedConstantBindingImpl( + this, key, converted, stringBinding, typeConverterBinding); + } catch (ErrorsException e) { + throw e; + } catch (RuntimeException e) { + throw errors + .conversionError(stringValue, source, type, typeConverterBinding, e) + .toException(); + } + } + + private static class ConvertedConstantBindingImpl extends BindingImpl + implements ConvertedConstantBinding { final T value; final Provider provider; final Binding originalBinding; final TypeConverterBinding typeConverterBinding; - ConvertedConstantBindingImpl(InjectorImpl injector, - Key key, - T value, - Binding originalBinding, - TypeConverterBinding typeConverterBinding) { - super(injector, key, originalBinding.getSource(), - new ConstantFactory(Initializables.of(value)), Scoping.UNSCOPED); + ConvertedConstantBindingImpl( + InjectorImpl injector, + Key key, + T value, + Binding originalBinding, + TypeConverterBinding typeConverterBinding) { + super( + injector, + key, + originalBinding.getSource(), + new ConstantFactory(Initializables.of(value)), + Scoping.UNSCOPED); this.value = value; provider = Providers.of(value); this.originalBinding = originalBinding; @@ -1130,4 +569,612 @@ final class InjectorImpl implements Injector, Lookups { return Objects.hashCode(getKey(), getScoping(), value); } } + + void initializeBinding(BindingImpl binding, Errors errors) throws ErrorsException { + if (binding instanceof DelayedInitialize) { + ((DelayedInitialize) binding).initialize(this, errors); + } + } + + void initializeJitBinding(BindingImpl binding, Errors errors) throws ErrorsException { + // Put the partially constructed binding in the map a little early. This enables us to handle + // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl. + // Note: We don't need to synchronize on jitBindingData.lock() during injector creation. + if (binding instanceof DelayedInitialize) { + Key key = binding.getKey(); + jitBindingData.putJitBinding(key, binding); + boolean successful = false; + DelayedInitialize delayed = (DelayedInitialize) binding; + try { + delayed.initialize(this, errors); + successful = true; + } finally { + if (!successful) { + // We do not pass cb.getInternalConstructor as the second parameter + // so that cached exceptions while constructing it get stored. + // See TypeListenerTest#testTypeListenerThrows + removeFailedJitBinding(binding, null); + cleanup(binding, new HashSet>()); + } + } + } + } + + /** + * Iterates through the binding's dependencies to clean up any stray bindings that were leftover + * from a failed JIT binding. This is required because the bindings are eagerly & optimistically + * added to allow circular dependency support, so dependencies may pass where they should have + * failed. + */ + private boolean cleanup(BindingImpl binding, Set> encountered) { + boolean bindingFailed = false; + Set> deps = getInternalDependencies(binding); + for (Dependency dep : deps) { + Key depKey = dep.getKey(); + InjectionPoint ip = dep.getInjectionPoint(); + if (encountered.add(depKey)) { // only check if we haven't looked at this key yet + BindingImpl depBinding = jitBindingData.getJitBinding(depKey); + if (depBinding != null) { // if the binding still exists, validate + boolean failed = cleanup(depBinding, encountered); // if children fail, we fail + if (depBinding instanceof ConstructorBindingImpl) { + ConstructorBindingImpl ctorBinding = (ConstructorBindingImpl) depBinding; + ip = ctorBinding.getInternalConstructor(); + if (!ctorBinding.isInitialized()) { + failed = true; + } + } + if (failed) { + removeFailedJitBinding(depBinding, ip); + bindingFailed = true; + } + } else if (bindingData.getExplicitBinding(depKey) == null) { + // ignore keys if they were explicitly bound, but if neither JIT + // nor explicit, it's also invalid & should let parent know. + bindingFailed = true; + } + } + } + return bindingFailed; + } + + /** Cleans up any state that may have been cached when constructing the JIT binding. */ + private void removeFailedJitBinding(Binding binding, InjectionPoint ip) { + jitBindingData.addFailedJitBinding(binding.getKey()); + jitBindingData.removeJitBinding(binding.getKey()); + membersInjectorStore.remove(binding.getKey().getTypeLiteral()); + provisionListenerStore.remove(binding); + if (ip != null) { + constructors.remove(ip); + } + } + + /** Safely gets the dependencies of possibly not initialized bindings. */ + private Set> getInternalDependencies(BindingImpl binding) { + if (binding instanceof ConstructorBindingImpl) { + return ((ConstructorBindingImpl) binding).getInternalDependencies(); + } else if (binding instanceof HasDependencies) { + return ((HasDependencies) binding).getDependencies(); + } else { + return ImmutableSet.of(); + } + } + + /** + * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if + * none is specified. + */ + BindingImpl createUninitializedBinding( + Key key, Scoping scoping, Object source, Errors errors, boolean jitBinding) + throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + + ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class); + + // Don't try to inject arrays or enums annotated with @ImplementedBy. + if (rawType.isArray() || (rawType.isEnum() && implementedBy != null)) { + throw errors.missingImplementationWithHint(key, this).toException(); + } + + // Handle TypeLiteral by binding the inner type + if (rawType == TypeLiteral.class) { + @SuppressWarnings("unchecked") // we have to fudge the inner type as Object + BindingImpl binding = + (BindingImpl) createTypeLiteralBinding((Key>) key, errors); + return binding; + } + + // Handle @ImplementedBy + if (implementedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createImplementedByBinding(key, scoping, implementedBy, errors); + } + + // Handle @ProvidedBy. + ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class); + if (providedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createProvidedByBinding(key, scoping, providedBy, errors); + } + + return ConstructorBindingImpl.create( + this, + key, + null, /* use default constructor */ + source, + scoping, + errors, + jitBinding && options.jitDisabled, + options.atInjectRequired); + } + + /** + * Converts a binding for a {@code Key>} to the value {@code TypeLiteral}. It's + * a bit awkward because we have to pull out the inner type in the type literal. + */ + private BindingImpl> createTypeLiteralBinding( + Key> key, Errors errors) throws ErrorsException { + Type typeLiteralType = key.getTypeLiteral().getType(); + if (!(typeLiteralType instanceof ParameterizedType)) { + throw errors.cannotInjectRawTypeLiteral().toException(); + } + + ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType; + Type innerType = parameterizedType.getActualTypeArguments()[0]; + + // this is unforunate. We don't support building TypeLiterals for type variable like 'T'. If + // this proves problematic, we can probably fix TypeLiteral to support type variables + if (!(innerType instanceof Class) + && !(innerType instanceof GenericArrayType) + && !(innerType instanceof ParameterizedType)) { + throw errors.cannotInjectTypeLiteralOf(innerType).toException(); + } + + @SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe + TypeLiteral value = (TypeLiteral) TypeLiteral.get(innerType); + InternalFactory> factory = + new ConstantFactory>(Initializables.of(value)); + return new InstanceBindingImpl>( + this, + key, + SourceProvider.UNKNOWN_SOURCE, + factory, + ImmutableSet.of(), + value); + } + + /** Creates a binding for a type annotated with @ProvidedBy. */ + BindingImpl createProvidedByBinding( + Key key, Scoping scoping, ProvidedBy providedBy, Errors errors) throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + Class> providerType = providedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper loops? + if (providerType == rawType) { + throw errors.recursiveProviderType().toException(); + } + + // Assume the provider provides an appropriate type. We double check at runtime. + @SuppressWarnings("unchecked") + Key> providerKey = (Key>) Key.get(providerType); + ProvidedByInternalFactory internalFactory = + new ProvidedByInternalFactory(rawType, providerType, providerKey); + Object source = rawType; + BindingImpl binding = + LinkedProviderBindingImpl.createWithInitializer( + this, + key, + source, + Scoping.scope(key, this, internalFactory, source, scoping), + scoping, + providerKey, + internalFactory); + internalFactory.setProvisionListenerCallback(provisionListenerStore.get(binding)); + return binding; + } + + /** Creates a binding for a type annotated with @ImplementedBy. */ + private BindingImpl createImplementedByBinding( + Key key, Scoping scoping, ImplementedBy implementedBy, Errors errors) + throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + Class implementationType = implementedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper cycles? + if (implementationType == rawType) { + throw errors.recursiveImplementationType().toException(); + } + + // Make sure implementationType extends type. + if (!rawType.isAssignableFrom(implementationType)) { + throw errors.notASubtype(implementationType, rawType).toException(); + } + + @SuppressWarnings("unchecked") // After the preceding check, this cast is safe. + Class subclass = (Class) implementationType; + + // Look up the target binding. + final Key targetKey = Key.get(subclass); + Object source = rawType; + FactoryProxy factory = new FactoryProxy<>(this, key, targetKey, source); + factory.notify(errors); // causes the factory to initialize itself internally + return new LinkedBindingImpl( + this, + key, + source, + Scoping.scope(key, this, factory, source, scoping), + scoping, + targetKey); + } + + /** + * Attempts to create a just-in-time binding for {@code key} in the root injector, falling back to + * other ancestor injectors until this injector is tried. + */ + private BindingImpl createJustInTimeBindingRecursive( + Key key, Errors errors, boolean jitDisabled, JitLimitation jitType) + throws ErrorsException { + // ask the parent to create the JIT binding + if (parent != null) { + if (jitType == JitLimitation.NEW_OR_EXISTING_JIT + && jitDisabled + && !parent.options.jitDisabled) { + // If the binding would be forbidden here but allowed in a parent, report an error instead + throw errors.jitDisabledInParent(key).toException(); + } + + try { + return parent.createJustInTimeBindingRecursive( + key, + new Errors(), + jitDisabled, + parent.options.jitDisabled ? JitLimitation.NO_JIT : jitType); + } catch (ErrorsException ignored) { + // If JIT binding creation failed in parent injector(s), create the JIT binding in this + // injector instead. + } + } + + // Retrieve the sources before checking for banned key to guard against sources becoming null + // due to a full GC happening after calling jitBindingData.isBanned and + // state.getSourcesForBannedKey. + // TODO(user): Consolidate these two APIs. + Set sources = jitBindingData.getSourcesForBannedKey(key); + if (jitBindingData.isBannedKey(key)) { + throw errors.childBindingAlreadySet(key, sources).toException(); + } + + key = MoreTypes.canonicalizeKey(key); // before storing the key long-term, canonicalize it. + BindingImpl binding = createJustInTimeBinding(key, errors, jitDisabled, jitType); + jitBindingData.banKeyInParent(key, bindingData, binding.getSource()); + jitBindingData.putJitBinding(key, binding); + return binding; + } + + /** + * Returns a new just-in-time binding created by resolving {@code key}. The strategies used to + * create just-in-time bindings are: + * + *
    + *
  1. Internalizing Providers. If the requested binding is for {@code Provider}, we delegate + * to the binding for {@code T}. + *
  2. Converting constants. + *
  3. ImplementedBy and ProvidedBy annotations. Only for unannotated keys. + *
  4. The constructor of the raw type. Only for unannotated keys. + *
+ * + * @throws com.google.inject.internal.ErrorsException if the binding cannot be created. + */ + private BindingImpl createJustInTimeBinding( + Key key, Errors errors, boolean jitDisabled, JitLimitation jitType) + throws ErrorsException { + int numErrorsBefore = errors.size(); + + // Retrieve the sources before checking for a banned key to guard against sources becoming null + // due to a full GC happening after calling jitBindingData.isBanned and + // jitBindingData.getSourcesForBannedKey. + // TODO(user): Consolidate these two APIs. + Set sources = jitBindingData.getSourcesForBannedKey(key); + if (jitBindingData.isBannedKey(key)) { + throw errors.childBindingAlreadySet(key, sources).toException(); + } + + // Handle cases where T is a Provider. + if (isProvider(key)) { + // These casts are safe. We know T extends Provider and that given Key>, + // createSyntheticProviderBinding() will return BindingImpl>. + @SuppressWarnings({"unchecked", "cast"}) + BindingImpl binding = (BindingImpl) createSyntheticProviderBinding((Key) key, errors); + return binding; + } + + // Handle cases where T is a MembersInjector + if (isMembersInjector(key)) { + // These casts are safe. T extends MembersInjector and that given Key>, + // createMembersInjectorBinding() will return BindingImpl>. + @SuppressWarnings({"unchecked", "cast"}) + BindingImpl binding = (BindingImpl) createMembersInjectorBinding((Key) key, errors); + return binding; + } + + // Try to convert a constant string binding to the requested type. + BindingImpl convertedBinding = convertConstantStringBinding(key, errors); + if (convertedBinding != null) { + return convertedBinding; + } + + if (!isTypeLiteral(key) && jitDisabled && jitType != JitLimitation.NEW_OR_EXISTING_JIT) { + throw errors.jitDisabled(key).toException(); + } + + // If the key has an annotation... + if (key.getAnnotationType() != null) { + // Look for a binding without annotation attributes or return null. + if (key.hasAttributes() && !options.exactBindingAnnotationsRequired) { + try { + Errors ignored = new Errors(); + return getBindingOrThrow(key.withoutAttributes(), ignored, JitLimitation.NO_JIT); + } catch (ErrorsException ignored) { + // throw with a more appropriate message below + } + } + throw errors.missingImplementationWithHint(key, this).toException(); + } + + Object source = key.getTypeLiteral().getRawType(); + BindingImpl binding = + createUninitializedBinding(key, Scoping.UNSCOPED, source, errors, true); + errors.throwIfNewErrors(numErrorsBefore); + initializeJitBinding(binding, errors); + return binding; + } + + InternalFactory getInternalFactory( + Key key, Errors errors, JitLimitation jitType) throws ErrorsException { + return getBindingOrThrow(key, errors, jitType).getInternalFactory(); + } + + @Override + public Map, Binding> getBindings() { + return bindingData.getExplicitBindingsThisLevel(); + } + + @Override + public Map, Binding> getAllBindings() { + synchronized (jitBindingData.lock()) { + return new ImmutableMap.Builder, Binding>() + .putAll(bindingData.getExplicitBindingsThisLevel()) + .putAll(jitBindingData.getJitBindings()) + .build(); + } + } + + @Override + public Map, Scope> getScopeBindings() { + return ImmutableMap.copyOf(bindingData.getScopes()); + } + + @Override + public Set getTypeConverterBindings() { + return ImmutableSet.copyOf(bindingData.getConvertersThisLevel()); + } + + @Override + public List getElements() { + ImmutableList.Builder elements = ImmutableList.builder(); + elements.addAll(getAllBindings().values()); + elements.addAll(bindingData.getProviderLookupsThisLevel()); + elements.addAll(bindingData.getConvertersThisLevel()); + elements.addAll(bindingData.getScopeBindingsThisLevel()); + elements.addAll(bindingData.getTypeListenerBindingsThisLevel()); + elements.addAll(bindingData.getProvisionListenerBindingsThisLevel()); + elements.addAll(bindingData.getScannerBindingsThisLevel()); + elements.addAll(bindingData.getStaticInjectionRequestsThisLevel()); + elements.addAll(bindingData.getMembersInjectorLookupsThisLevel()); + elements.addAll(bindingData.getInjectionRequestsThisLevel()); + + return elements.build(); + } + + @Override + public Map, List> getAllMembersInjectorInjectionPoints() { + // Note, this is a safe cast per the ListMultimap javadocs. + // We could use Multimaps.asMap to avoid the cast, but unfortunately it's a @Beta method. + @SuppressWarnings("unchecked") + Map, List> res = + (Map, List>) + (Map, ?>) + ImmutableListMultimap.copyOf( + Multimaps.filterKeys( + membersInjectorStore.getAllInjectionPoints(), + userRequestedMembersInjectorTypes::contains)) + .asMap(); + return res; + } + + /** Returns parameter injectors, or {@code null} if there are no parameters. */ + SingleParameterInjector[] getParametersInjectors(List> parameters, Errors errors) + throws ErrorsException { + if (parameters.isEmpty()) { + return null; + } + + int numErrorsBefore = errors.size(); + SingleParameterInjector[] result = new SingleParameterInjector[parameters.size()]; + int i = 0; + for (Dependency parameter : parameters) { + try { + result[i++] = createParameterInjector(parameter, errors.withSource(parameter)); + } catch (ErrorsException rethrownBelow) { + // rethrown below + } + } + + errors.throwIfNewErrors(numErrorsBefore); + return result; + } + + SingleParameterInjector createParameterInjector( + final Dependency dependency, final Errors errors) throws ErrorsException { + BindingImpl binding = + getBindingOrThrow(dependency.getKey(), errors, JitLimitation.NO_JIT); + return new SingleParameterInjector(dependency, binding); + } + + /** Invokes a method. */ + interface MethodInvoker { + Object invoke(Object target, Object... parameters) + throws IllegalAccessException, InvocationTargetException; + } + + /** Cached constructor injectors for each type */ + final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this); + + /** Cached field and method injectors for each type. */ + MembersInjectorStore membersInjectorStore; + + /** Cached provision listener callbacks for each key. */ + ProvisionListenerCallbackStore provisionListenerStore; + + @Override + @SuppressWarnings({ + "unchecked", + "rawtypes" + }) // the members injector type is consistent with instance's type + public void injectMembers(Object instance) { + MembersInjector membersInjector = getMembersInjector(instance.getClass()); + membersInjector.injectMembers(instance); + } + + @Override + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + checkNotNull(typeLiteral, "typeLiteral"); + userRequestedMembersInjectorTypes.add(typeLiteral); + + Errors errors = new Errors(typeLiteral); + try { + return membersInjectorStore.get(typeLiteral, errors); + } catch (ErrorsException e) { + ConfigurationException exception = + new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + throw exception; + } + } + + @Override + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } + + @Override + public Provider getProvider(Class type) { + return getProvider(Key.get(checkNotNull(type, "type"))); + } + + Provider getProviderOrThrow(final Dependency dependency, Errors errors) + throws ErrorsException { + Key key = dependency.getKey(); + BindingImpl binding = getBindingOrThrow(key, errors, JitLimitation.NO_JIT); + final InternalFactory internalFactory = binding.getInternalFactory(); + + return new Provider() { + @Override + public T get() { + InternalContext currentContext = enterContext(); + try { + T t = internalFactory.get(currentContext, dependency, false); + return t; + } catch (InternalProvisionException e) { + throw e.addSource(dependency).toProvisionException(); + } finally { + currentContext.close(); + } + } + + @Override + public String toString() { + return internalFactory.toString(); + } + }; + } + + @Override + public Provider getProvider(final Key key) { + checkNotNull(key, "key"); + Errors errors = new Errors(key); + try { + Provider result = getProviderOrThrow(Dependency.get(key), errors); + errors.throwIfNewErrors(0); + return result; + } catch (ErrorsException e) { + ConfigurationException exception = + new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + throw exception; + } + } + + @Override + public T getInstance(Key key) { + return getProvider(key).get(); + } + + @Override + public T getInstance(Class type) { + return getProvider(type).get(); + } + + /** + * Holds Object[] as a mutable wrapper, rather than InternalContext, since array operations are + * faster than ThreadLocal.set() / .get() operations. + * + *

Holds Object[] rather than InternalContext[], since localContext never gets cleaned up at + * any point. This could lead to problems when, for example, an OSGI application is reloaded, the + * InjectorImpl is destroyed, but the thread that the injector runs on is kept alive. In such a + * case, ThreadLocal itself would hold on to a reference to localContext, which would hold on to + * the old InternalContext.class object, which would hold on to the old classloader that loaded + * that class, and so on. + */ + private final ThreadLocal localContext; + + /** Only to be called by the {@link SingletonScope} provider. */ + InternalContext getLocalContext() { + return (InternalContext) localContext.get()[0]; + } + + /** + * Looks up thread local context and {@link InternalContext#enter() enters} it or creates a new + * context if necessary. + * + *

All callers of this are responsible for calling {@link InternalContext#close()}. Typical + * usage should look like: + * + *

{@code
+     * InternalContext ctx = injector.enterContext();
+     * try {
+     *   ... use ctx ...
+     * } finally {
+     *   ctx.close();
+     * }
+     * }
+ */ + InternalContext enterContext() { + Object[] reference = localContext.get(); + if (reference == null) { + reference = new Object[1]; + localContext.set(reference); + } + InternalContext ctx = (InternalContext) reference[0]; + if (ctx == null) { + reference[0] = ctx = new InternalContext(options, reference); + } else { + ctx.enter(); + } + return ctx; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(Injector.class) + .add("bindings", bindingData.getExplicitBindingsThisLevel().values()) + .toString(); + } } diff --git a/src/main/java/com/google/inject/internal/InjectorJitBindingData.java b/src/main/java/com/google/inject/internal/InjectorJitBindingData.java new file mode 100644 index 0000000..e323bfe --- /dev/null +++ b/src/main/java/com/google/inject/internal/InjectorJitBindingData.java @@ -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, 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> 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 parent; + + /** + * This lock is needed for threadsafe InjectorJitBindingData accesses. It corresponds to this + * InjectorJitBindingData's highest ancestor. + */ + private final Object lock; + + InjectorJitBindingData(Optional parent) { + this.parent = parent; + this.lock = parent.isPresent() ? parent.get().lock() : this; + this.bannedKeys = new WeakKeySet(lock); + } + + Map, 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 getSourcesForBannedKey(Key key) { + return bannedKeys.getSources(key); + } + + Object lock() { + return lock; + } +} diff --git a/src/main/java/com/google/inject/internal/InjectorShell.java b/src/main/java/com/google/inject/internal/InjectorShell.java index bdcc21f..a1418db 100644 --- a/src/main/java/com/google/inject/internal/InjectorShell.java +++ b/src/main/java/com/google/inject/internal/InjectorShell.java @@ -1,5 +1,9 @@ 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.Lists; import com.google.inject.Binder; @@ -10,24 +14,34 @@ import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.Stage; 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.Stopwatch; +import com.google.inject.spi.BindingSourceRestriction; import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; +import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; import com.google.inject.spi.PrivateElements; import com.google.inject.spi.ProvisionListenerBinding; import com.google.inject.spi.TypeListenerBinding; - import java.util.List; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.inject.Scopes.SINGLETON; +import java.util.Optional; +import java.util.logging.Logger; /** - * A partially-initialized injector. See {@link InternalInjectorCreator}, which - * uses this to build a tree of injectors in batch. + * InjectorShell is used by {@link InternalInjectorCreator} to recursively create a tree of + * uninitialized {@link Injector}s. Each InjectorShell corresponds to either the top-level root + * injector, or a private child injector. + * + *

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. + * + *

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 { @@ -47,48 +61,19 @@ final class InjectorShell { 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 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 key = Key.get(Stage.class); - InstanceBindingImpl stageBinding = new InstanceBindingImpl<>( - injector, - key, - SourceProvider.UNKNOWN_SOURCE, - new ConstantFactory<>(Initializables.of(stage)), - ImmutableSet.of(), - stage); - injector.state.putBinding(key, stageBinding); - } - static class Builder { private final List elements = Lists.newArrayList(); private final List modules = Lists.newArrayList(); - /** - * lazily constructed - */ - private State state; + // lazily constructed fields + private InjectorBindingData bindingData; + private InjectorJitBindingData jitBindingData; private InjectorImpl parent; private InjectorOptions options; 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; Builder stage(Stage stage) { @@ -98,7 +83,8 @@ final class InjectorShell { Builder parent(InjectorImpl 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.stage = options.stage; return this; @@ -111,10 +97,8 @@ final class InjectorShell { } void addModules(Iterable modules) { - if (modules != null) { - for (Module module : modules) { - this.modules.add(module); - } + for (Module module : modules) { + this.modules.add(module); } } @@ -122,11 +106,15 @@ final class InjectorShell { return options.stage; } - /** - * Synchronize on this before calling {@link #build}. - */ + /** Synchronize on this before calling {@link #build}. */ 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 build( Initializer initializer, - ProcessedBindingData bindingData, - Stopwatch stopwatch, + ProcessedBindingData processedBindingData, + ContinuousStopwatch stopwatch, Errors errors) { checkState(stage != null, "Stage not initialized"); 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 if (parent == null) { modules.add(0, new RootModule()); } else { - modules.add(0, new InheritedScannersModule(parent.state)); + modules.add(0, new InheritedScannersModule(parent.getBindingData())); } 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 InjectorOptionsProcessor optionsProcessor = new InjectorOptionsProcessor(errors); optionsProcessor.process(null, elements); options = optionsProcessor.getOptions(stage, options); - InjectorImpl injector = new InjectorImpl(parent, state, options); + InjectorImpl injector = new InjectorImpl(parent, bindingData, jitBindingData, options); if (privateElements != null) { privateElements.initInjector(injector); } @@ -171,10 +170,11 @@ final class InjectorShell { new MessageProcessor(errors).process(injector, elements); new ListenerBindingProcessor(errors).process(injector, elements); - List typeListenerBindings = injector.state.getTypeListenerBindings(); + List typeListenerBindings = + injector.getBindingData().getTypeListenerBindings(); injector.membersInjectorStore = new MembersInjectorStore(injector, typeListenerBindings); List provisionListenerBindings = - injector.state.getProvisionListenerBindings(); + injector.getBindingData().getProvisionListenerBindings(); injector.provisionListenerStore = new ProvisionListenerCallbackStore(provisionListenerBindings); stopwatch.resetAndLog("TypeListeners & ProvisionListener creation"); @@ -187,13 +187,13 @@ final class InjectorShell { bindStage(injector, stage); bindInjector(injector); - //bindLogger(injector); + bindLogger(injector); // Process all normal bindings, then UntargettedBindings. // This is necessary because UntargettedBindings can create JIT bindings // and need all their other dependencies set up ahead of time. - new BindingProcessor(errors, initializer, bindingData).process(injector, elements); - new UntargettedBindingProcessor(errors, bindingData).process(injector, elements); + new BindingProcessor(errors, initializer, processedBindingData).process(injector, elements); + new UntargettedBindingProcessor(errors, processedBindingData).process(injector, elements); stopwatch.resetAndLog("Binding creation"); new ModuleAnnotatedMethodScannerProcessor(errors).process(injector, elements); @@ -206,19 +206,34 @@ final class InjectorShell { PrivateElementProcessor processor = new PrivateElementProcessor(errors); processor.process(injector, elements); 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"); 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 key = Key.get(Injector.class); + InjectorFactory injectorFactory = new InjectorFactory(injector); + injector + .getBindingData() + .putBinding( + key, + new ProviderInstanceBindingImpl( + injector, + key, + SourceProvider.UNKNOWN_SOURCE, + injectorFactory, + Scoping.UNSCOPED, + injectorFactory, + ImmutableSet.of())); } private static class InjectorFactory implements InternalFactory, Provider { @@ -238,11 +253,66 @@ final class InjectorShell { return injector; } + @Override public String toString() { return "Provider"; } } + /** + * 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 key = Key.get(Logger.class); + LoggerFactory loggerFactory = new LoggerFactory(); + injector + .getBindingData() + .putBinding( + key, + new ProviderInstanceBindingImpl( + injector, + key, + SourceProvider.UNKNOWN_SOURCE, + loggerFactory, + Scoping.UNSCOPED, + loggerFactory, + ImmutableSet.of())); + } + + private static class LoggerFactory implements InternalFactory, Provider { + @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"; + } + } + + private static void bindStage(InjectorImpl injector, Stage stage) { + Key key = Key.get(Stage.class); + InstanceBindingImpl stageBinding = + new InstanceBindingImpl( + injector, + key, + SourceProvider.UNKNOWN_SOURCE, + new ConstantFactory(Initializables.of(stage)), + ImmutableSet.of(), + stage); + injector.getBindingData().putBinding(key, stageBinding); + } + private static class RootModule implements Module { @Override public void configure(Binder binder) { @@ -253,15 +323,15 @@ final class InjectorShell { } private static class InheritedScannersModule implements Module { - private final State state; + private final InjectorBindingData bindingData; - InheritedScannersModule(State state) { - this.state = state; + InheritedScannersModule(InjectorBindingData bindingData) { + this.bindingData = bindingData; } @Override public void configure(Binder binder) { - for (ModuleAnnotatedMethodScannerBinding binding : state.getScannerBindings()) { + for (ModuleAnnotatedMethodScannerBinding binding : bindingData.getScannerBindings()) { binding.applyTo(binder); } } diff --git a/src/main/java/com/google/inject/internal/InternalErrorDetail.java b/src/main/java/com/google/inject/internal/InternalErrorDetail.java new file mode 100644 index 0000000..2f6c971 --- /dev/null +++ b/src/main/java/com/google/inject/internal/InternalErrorDetail.java @@ -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> extends ErrorDetail { + // A list of errors that have help documentation. + private static final ImmutableSet DOCUMENTED_ERRORS = + ImmutableSet.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 sources, Throwable cause) { + super(message, sources, cause); + this.errorId = errorId; + } + + @Override + protected final Optional getLearnMoreLink() { + if (DOCUMENTED_ERRORS.contains(errorId)) { + return Optional.of(DOC_BASE_URL + errorId.name()); + } + return Optional.empty(); + } + + @Override + protected final Optional getErrorIdentifier() { + if (errorId == ErrorId.OTHER) { + return Optional.empty(); + } + String id = "Guice/" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, errorId.name()); + return Optional.of(id); + } +} diff --git a/src/main/java/com/google/inject/internal/InternalFlags.java b/src/main/java/com/google/inject/internal/InternalFlags.java index 684cd57..3c71580 100644 --- a/src/main/java/com/google/inject/internal/InternalFlags.java +++ b/src/main/java/com/google/inject/internal/InternalFlags.java @@ -2,20 +2,131 @@ package com.google.inject.internal; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.logging.Logger; -/** - * Contains flags for Guice. - */ -public class InternalFlags { +/** 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 - = parseIncludeStackTraceOption(); + private static final IncludeStackTraceOption INCLUDE_STACK_TRACES = + getSystemOption( + "guice_include_stack_traces", + IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE); - private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING - = parseCustomClassLoadingOption(); + private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING = + getSystemOption( + "guice_custom_class_loading", + CustomClassLoadingOption.BRIDGE, + CustomClassLoadingOption.OFF); - private static final NullableProvidesOption NULLABLE_PROVIDES - = parseNullableProvidesOption(NullableProvidesOption.ERROR); + 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, + + /** + * 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. + * + *

Note: with this option you cannot look up fast/enhanced types by name or mock/spy them. + */ + ANONYMOUS, + + /** + * 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, + + /** + * Define fast/enhanced types in a child class loader whose parent is the original class loader. + * + *

Note: with this option you cannot intercept package-private methods. + */ + CHILD + } + + /** Options for handling nullable parameters used in provides methods. */ + 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: + * + *

    + *
  • Runtime bytecode generation (instead of reflection) will be used when Guice need to + * invoke application code. + *
  • Method interception. + *
+ * + *

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() { return INCLUDE_STACK_TRACES; @@ -29,25 +140,18 @@ public class InternalFlags { return NULLABLE_PROVIDES; } - private static IncludeStackTraceOption parseIncludeStackTraceOption() { - return getSystemOption("guice_include_stack_traces", - IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE); + public static boolean isBytecodeGenEnabled() { + return BYTECODE_GEN_OPTION == BytecodeGenOption.ENABLED; } - private static CustomClassLoadingOption parseCustomClassLoadingOption() { - return getSystemOption("guice_custom_class_loading", - CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF); - } - - private static NullableProvidesOption parseNullableProvidesOption( - NullableProvidesOption defaultValue) { - return getSystemOption("guice_check_nullable_provides_params", defaultValue); + public static boolean enableColorizeErrorMessages() { + return COLORIZE_OPTION.enabled(); } /** * Gets the system option indicated by the specified key; runs as a privileged action. * - * @param name of the system option + * @param name of the system option * @param defaultValue if the option is not set * @return value of the option, defaultValue if not set */ @@ -58,69 +162,34 @@ public class InternalFlags { /** * Gets the system option indicated by the specified key; runs as a privileged action. * - * @param name of the system option + * @param name of the system option * @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 */ private static > T getSystemOption(final String name, T defaultValue, T secureValue) { Class enumType = defaultValue.getDeclaringClass(); + String value = null; try { - String value = AccessController.doPrivileged((PrivilegedAction) () - -> System.getProperty(name)); + value = + AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public String run() { + return System.getProperty(name); + } + }); return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) : defaultValue; } catch (SecurityException e) { return secureValue; } 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; } } - /** - * 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 - } + private InternalFlags() {} } diff --git a/src/main/java/com/google/inject/internal/InternalInjectorCreator.java b/src/main/java/com/google/inject/internal/InternalInjectorCreator.java index e33087b..f0d41d8 100644 --- a/src/main/java/com/google/inject/internal/InternalInjectorCreator.java +++ b/src/main/java/com/google/inject/internal/InternalInjectorCreator.java @@ -1,5 +1,6 @@ package com.google.inject.internal; +import com.google.common.base.Stopwatch; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; @@ -9,12 +10,11 @@ import com.google.inject.Provider; import com.google.inject.Scope; import com.google.inject.Stage; 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.Element; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.TypeConverterBinding; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; @@ -28,24 +28,28 @@ import java.util.Set; * top-level injector. * *

Injector construction happens in two phases. + * *

    - *
  1. 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. - * No user code is executed in this phase.
  2. - *
  3. 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 - * create eager singletons. In this phase, user code may have started other threads. This phase - * is not executed for injectors created using {@link Stage#TOOL the tool stage}
  4. + *
  5. 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. No user code is executed in this phase. + *
  6. 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 + * create eager singletons. In this phase, user code may have started other threads. This + * phase is not executed for injectors created using {@link Stage#TOOL the tool stage} *
* + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) */ 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 Initializer initializer = new Initializer(); - private final ProcessedBindingData bindingData; + private final ProcessedBindingData processedBindingData; private final InjectionRequestProcessor injectionRequestProcessor; private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder(); @@ -53,7 +57,7 @@ public final class InternalInjectorCreator { public InternalInjectorCreator() { injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer); - bindingData = new ProcessedBindingData(); + processedBindingData = new ProcessedBindingData(); } 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 - * 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); return this; } @@ -76,11 +81,14 @@ public final class InternalInjectorCreator { } 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 synchronized (shellBuilder.lock()) { - shells = shellBuilder.build(initializer, bindingData, stopwatch, errors); + shells = shellBuilder.build(initializer, processedBindingData, stopwatch, errors); stopwatch.resetAndLog("Injector construction"); initializeStatically(); @@ -97,44 +105,49 @@ public final class InternalInjectorCreator { } } - /** - * Initialize and validate everything. - */ + /** Initialize and validate everything. */ private void initializeStatically() { - bindingData.initializeBindings(); + processedBindingData.initializeBindings(); stopwatch.resetAndLog("Binding initialization"); + for (InjectorShell shell : shells) { - shell.getInjector().index(); + shell.getInjector().getBindingData().indexBindingsByType(); } stopwatch.resetAndLog("Binding indexing"); + injectionRequestProcessor.process(shells); stopwatch.resetAndLog("Collecting injection requests"); - bindingData.runCreationListeners(errors); + + processedBindingData.runCreationListeners(errors); stopwatch.resetAndLog("Binding validation"); + injectionRequestProcessor.validate(); stopwatch.resetAndLog("Static validation"); + initializer.validateOustandingInjections(errors); stopwatch.resetAndLog("Instance member validation"); + new LookupProcessor(errors).process(shells); for (InjectorShell shell : shells) { ((DeferredLookups) shell.getInjector().lookups).initialize(errors); } stopwatch.resetAndLog("Provider verification"); + // This needs to come late since some user bindings rely on requireBinding calls to create // jit bindings during the LookupProcessor. - bindingData.initializeDelayedBindings(); + processedBindingData.initializeDelayedBindings(); stopwatch.resetAndLog("Delayed Binding initialization"); + for (InjectorShell shell : shells) { if (!shell.getElements().isEmpty()) { throw new AssertionError("Failed to execute " + shell.getElements()); } } + 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() { return shells.get(0).getInjector(); } @@ -166,28 +179,29 @@ public final class InternalInjectorCreator { * while we're binding these singletons are not be eager. */ void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) { + List> candidateBindings = new ArrayList<>(); @SuppressWarnings("unchecked") // casting Collection to Collection is safe Collection> bindingsAtThisLevel = - (Collection) injector.state.getExplicitBindingsThisLevel().values(); - List> candidateBindings = new ArrayList<>(bindingsAtThisLevel); - synchronized (injector.state.lock()) { + (Collection) injector.getBindingData().getExplicitBindingsThisLevel().values(); + candidateBindings.addAll(bindingsAtThisLevel); + synchronized (injector.getJitBindingData().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) { if (isEagerSingleton(injector, binding, stage)) { Dependency dependency = Dependency.get(binding.getKey()); - Dependency previous = context.pushDependency(dependency, binding.getSource()); try { binding.getInternalFactory().get(context, dependency, false); } catch (InternalProvisionException e) { errors.withSource(dependency).merge(e); - } finally { - context.popStateAndSetDependency(previous); } } } + } finally { + context.close(); } } @@ -201,16 +215,14 @@ public final class InternalInjectorCreator { // bindings. This only applies if the linked binding is not itself scoped. if (binding instanceof LinkedBindingImpl) { Key linkedBinding = ((LinkedBindingImpl) binding).getLinkedKey(); - return binding.getScoping().isNoScope() && - isEagerSingleton(injector, injector.getBinding(linkedBinding), stage); + return binding.getScoping().isNoScope() + && isEagerSingleton(injector, injector.getBinding(linkedBinding), stage); } 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 { private final Injector delegateInjector; diff --git a/src/main/java/com/google/inject/internal/InternalProvisionException.java b/src/main/java/com/google/inject/internal/InternalProvisionException.java index a209618..ab976b3 100644 --- a/src/main/java/com/google/inject/internal/InternalProvisionException.java +++ b/src/main/java/com/google/inject/internal/InternalProvisionException.java @@ -48,169 +48,192 @@ import java.util.logging.Logger; * #errorInUserCode} is called with an exception that holds multiple errors (like * ProvisionException). */ -@SuppressWarnings("serial") public final class InternalProvisionException extends Exception { - - private static final Logger logger = Logger.getLogger(Guice.class.getName()); - - private static final Set> warnedDependencies = - Collections.newSetFromMap(new ConcurrentHashMap<>()); + private static final Logger logger = Logger.getLogger(Guice.class.getName()); + private static final Set> warnedDependencies = + Collections.newSetFromMap(new ConcurrentHashMap, Boolean>()); - public static InternalProvisionException circularDependenciesDisabled(Class expectedType) { - return create( - "Found a circular dependency involving %s, and circular dependencies are disabled.", - expectedType); - } - - public static InternalProvisionException cannotProxyClass(Class expectedType) { - return create( - "Tried proxying %s to support a circular dependency, but it is not an interface.", - expectedType); - } - - public static InternalProvisionException create(String format, Object... arguments) { - return new InternalProvisionException(Messages.create(format, arguments)); - } - - public static InternalProvisionException errorInUserCode( - Throwable cause, String messageFormat, Object... arguments) { - Collection messages = Errors.getMessagesFromThrowable(cause); - if (!messages.isEmpty()) { - // TODO(lukes): it seems like we are dropping some valuable context here.. - // consider eliminating this special case - return new InternalProvisionException(messages); - } else { - return new InternalProvisionException(Messages.create(cause, messageFormat, arguments)); + public static InternalProvisionException circularDependenciesDisabled(Class expectedType) { + return create( + ErrorId.CIRCULAR_PROXY_DISABLED, + "Found a circular dependency involving %s, and circular dependencies are disabled.", + expectedType); } - } - public static InternalProvisionException subtypeNotProvided( - Class> providerType, Class type) { - return create("%s doesn't provide instances of %s.", providerType, type); - } + public static InternalProvisionException cannotProxyClass(Class expectedType) { + return create( + ErrorId.CAN_NOT_PROXY_CLASS, + "Tried proxying %s to support a circular dependency, but it is not an interface.", + expectedType); + } - public static InternalProvisionException errorInProvider(Throwable cause) { - return errorInUserCode(cause, "Error in custom provider, %s", cause); - } + public static InternalProvisionException create( + ErrorId errorId, String format, Object... arguments) { + return new InternalProvisionException(Messages.create(errorId, format, arguments)); + } - public static InternalProvisionException errorInjectingMethod(Throwable cause) { - return errorInUserCode(cause, "Error injecting method, %s", cause); - } - - public static InternalProvisionException errorInjectingConstructor(Throwable cause) { - return errorInUserCode(cause, "Error injecting constructor, %s", cause); - } - - public static InternalProvisionException errorInUserInjector( - MembersInjector listener, TypeLiteral type, RuntimeException cause) { - return errorInUserCode( - cause, "Error injecting %s using %s.%n Reason: %s", type, listener, cause); - } - - public static InternalProvisionException jitDisabled(Key key) { - return create("Explicit bindings are required and %s is not explicitly bound.", key); - } - - public static InternalProvisionException errorNotifyingInjectionListener( - InjectionListener listener, TypeLiteral type, RuntimeException cause) { - return errorInUserCode( - cause, "Error notifying InjectionListener %s of %s.%n Reason: %s", listener, type, cause); - } - - /** - * Returns {@code value} if it is non-null or allowed to be null. Otherwise a message is added and - * an {@code InternalProvisionException} is thrown. - */ - static void onNullInjectedIntoNonNullableDependency(Object source, Dependency dependency) - throws InternalProvisionException { - // Hack to allow null parameters to @Provides methods, for backwards compatibility. - if (dependency.getInjectionPoint().getMember() instanceof Method) { - Method annotated = (Method) dependency.getInjectionPoint().getMember(); - if (annotated.isAnnotationPresent(Provides.class)) { - switch (InternalFlags.getNullableProvidesOption()) { - case ERROR: - break; // break out & let the below exception happen - case IGNORE: - return; // user doesn't care about injecting nulls to non-@Nullables. - case WARN: - // Warn only once, otherwise we spam logs too much. - if (warnedDependencies.add(dependency)) { - logger.log( - Level.WARNING, - "Guice injected null into {0} (a {1}), please mark it @Nullable." - + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" - + " error.", - new Object[] { - Messages.formatParameter(dependency), Messages.convert(dependency.getKey()) - }); - } - return; + public static InternalProvisionException errorInUserCode( + ErrorId errorId, Throwable cause, String messageFormat, Object... arguments) { + Collection messages = Errors.getMessagesFromThrowable(cause); + if (!messages.isEmpty()) { + // TODO(lukes): it seems like we are dropping some valuable context here.. + // consider eliminating this special case + return new InternalProvisionException(messages); + } else { + return new InternalProvisionException( + Messages.create(errorId, cause, messageFormat, arguments)); } - } } - Object formattedDependency = - (dependency.getParameterIndex() != -1) - ? Messages.formatParameter(dependency) - : StackTraceElements.forMember(dependency.getInjectionPoint().getMember()); - - throw InternalProvisionException.create( - "null returned by binding at %s%n but %s is not @Nullable", source, formattedDependency) - .addSource(source); - } - - private final List sourcesToPrepend = new ArrayList<>(); - private final ImmutableList errors; - - private InternalProvisionException(Message error) { - this(ImmutableList.of(error)); - } - - private InternalProvisionException(Iterable errors) { - this.errors = ImmutableList.copyOf(errors); - checkArgument(!this.errors.isEmpty(), "Can't create a provision exception with no errors"); - } - - /** - * Prepends the given {@code source} to the stack of binding sources for the errors reported in - * this exception. - * - *

See {@link Errors#withSource(Object)} - * - *

It is expected that this method is called as the exception propagates up the stack. - * - * @param source - * @return {@code this} - */ - InternalProvisionException addSource(Object source) { - if (source == SourceProvider.UNKNOWN_SOURCE) { - return this; + public static InternalProvisionException subtypeNotProvided( + Class> providerType, Class type) { + return create( + ErrorId.SUBTYPE_NOT_PROVIDED, "%s doesn't provide instances of %s.", providerType, type); } - int sz = sourcesToPrepend.size(); - if (sz > 0 && sourcesToPrepend.get(sz - 1) == source) { - // This is for when there are two identical sources added in a row. This behavior is copied - // from Errors.withSource where it can happen when an constructor/provider method throws an - // exception - return this; - } - sourcesToPrepend.add(source); - return this; - } - ImmutableList getErrors() { - ImmutableList.Builder builder = ImmutableList.builder(); - // reverse them since sources are added as the exception propagates (so the first source is the - // last one added) - List newSources = Lists.reverse(sourcesToPrepend); - for (Message error : errors) { - builder.add(Messages.mergeSources(newSources, error)); + public static InternalProvisionException errorInProvider(Throwable cause) { + return errorInUserCode(ErrorId.ERROR_IN_CUSTOM_PROVIDER, cause, "%s", cause); } - return builder.build(); - } - /** Returns this exception convered to a ProvisionException. */ - public ProvisionException toProvisionException() { - return new ProvisionException(getErrors()); - } + public static InternalProvisionException errorInjectingMethod(Throwable cause) { + return errorInUserCode(ErrorId.ERROR_INJECTING_METHOD, cause, "%s", cause); + } + + public static InternalProvisionException errorInjectingConstructor(Throwable cause) { + return errorInUserCode(ErrorId.ERROR_INJECTING_CONSTRUCTOR, cause, "%s", cause); + } + + public static InternalProvisionException errorInUserInjector( + MembersInjector listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode( + ErrorId.ERROR_IN_USER_INJECTOR, + cause, + "Error injecting %s using %s.%n Reason: %s", + type, + listener, + cause); + } + + public static InternalProvisionException jitDisabled(Key key) { + return create( + ErrorId.JIT_DISABLED, + "Explicit bindings are required and %s is not explicitly bound.", + key); + } + + public static InternalProvisionException errorNotifyingInjectionListener( + InjectionListener listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode( + ErrorId.OTHER, + cause, + "Error notifying InjectionListener %s of %s.%n Reason: %s", + listener, + type, + cause); + } + + /** + * Returns {@code value} if it is non-null or allowed to be null. Otherwise a message is added and + * an {@code InternalProvisionException} is thrown. + */ + static void onNullInjectedIntoNonNullableDependency(Object source, Dependency dependency) + throws InternalProvisionException { + // Hack to allow null parameters to @Provides methods, for backwards compatibility. + if (dependency.getInjectionPoint().getMember() instanceof Method) { + Method annotated = (Method) dependency.getInjectionPoint().getMember(); + if (annotated.isAnnotationPresent(Provides.class)) { + switch (InternalFlags.getNullableProvidesOption()) { + case ERROR: + break; // break out & let the below exception happen + case IGNORE: + return; // user doesn't care about injecting nulls to non-@Nullables. + case WARN: + // Warn only once, otherwise we spam logs too much. + if (warnedDependencies.add(dependency)) { + logger.log( + Level.WARNING, + "Guice injected null into {0} (a {1}), please mark it @Nullable." + + " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an" + + " error.", + new Object[] { + SourceFormatter.getParameterName(dependency), + Messages.convert(dependency.getKey()) + }); + } + return; + } + } + } + + String parameterName = + (dependency.getParameterIndex() != -1) ? SourceFormatter.getParameterName(dependency) : ""; + Object memberStackTraceElement = + StackTraceElements.forMember(dependency.getInjectionPoint().getMember()); + Object formattedDependency = + parameterName.isEmpty() + ? memberStackTraceElement + : "the " + parameterName + " of " + memberStackTraceElement; + throw InternalProvisionException.create( + ErrorId.NULL_INJECTED_INTO_NON_NULLABLE, + "null returned by binding at %s%n but %s is not @Nullable", + source, + formattedDependency) + .addSource(source); + } + + private final List sourcesToPrepend = new ArrayList<>(); + private final ImmutableList errors; + + InternalProvisionException(Message error) { + this(ImmutableList.of(error)); + } + + private InternalProvisionException(Iterable errors) { + this.errors = ImmutableList.copyOf(errors); + checkArgument(!this.errors.isEmpty(), "Can't create a provision exception with no errors"); + } + + /** + * Prepends the given {@code source} to the stack of binding sources for the errors reported in + * this exception. + * + *

See {@link Errors#withSource(Object)} + * + *

It is expected that this method is called as the exception propagates up the stack. + * + * @param source + * @return {@code this} + */ + InternalProvisionException addSource(Object source) { + if (source == SourceProvider.UNKNOWN_SOURCE) { + return this; + } + int sz = sourcesToPrepend.size(); + if (sz > 0 && sourcesToPrepend.get(sz - 1) == source) { + // This is for when there are two identical sources added in a row. This behavior is copied + // from Errors.withSource where it can happen when an constructor/provider method throws an + // exception + return this; + } + sourcesToPrepend.add(source); + return this; + } + + ImmutableList getErrors() { + ImmutableList.Builder builder = ImmutableList.builder(); + // reverse them since sources are added as the exception propagates (so the first source is the + // last one added) + List newSources = Lists.reverse(sourcesToPrepend); + for (Message error : errors) { + builder.add(Messages.mergeSources(newSources, error)); + } + return builder.build(); + } + + /** Returns this exception convered to a ProvisionException. */ + public ProvisionException toProvisionException() { + ProvisionException exception = new ProvisionException(getErrors()); + return exception; + } } diff --git a/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java b/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java index ba77ef2..8633568 100644 --- a/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java +++ b/src/main/java/com/google/inject/internal/ListenerBindingProcessor.java @@ -14,13 +14,13 @@ final class ListenerBindingProcessor extends AbstractProcessor { @Override public Boolean visit(TypeListenerBinding binding) { - injector.state.addTypeListener(binding); + injector.getBindingData().addTypeListener(binding); return true; } @Override public Boolean visit(ProvisionListenerBinding binding) { - injector.state.addProvisionListener(binding); + injector.getBindingData().addProvisionListener(binding); return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/inject/internal/Messages.java b/src/main/java/com/google/inject/internal/Messages.java index 560491a..a3da7e2 100644 --- a/src/main/java/com/google/inject/internal/Messages.java +++ b/src/main/java/com/google/inject/internal/Messages.java @@ -1,39 +1,30 @@ 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.Objects; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; 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 com.google.inject.spi.ErrorDetail; import com.google.inject.spi.Message; -import java.lang.reflect.Field; import java.lang.reflect.Member; import java.util.Arrays; import java.util.Collection; import java.util.Formatter; import java.util.List; 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 { + 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 sources, Message message) { List messageSources = message.getSources(); // 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, // due to the fact that InternalFactoryToProviderAdapter applies the binding source when // merging errors. - if (!sources.isEmpty() && !messageSources.isEmpty() && - Objects.equal(messageSources.get(0), sources.get(sources.size() - 1))) { + if (!sources.isEmpty() + && !messageSources.isEmpty() + && Objects.equal(messageSources.get(0), sources.get(sources.size() - 1))) { messageSources = messageSources.subList(1, messageSources.size()); } - return new Message(ImmutableList.builder().addAll(sources).addAll(messageSources).build(), - message.getMessage(), message.getCause()); + return message.withSource( + ImmutableList.builder().addAll(sources).addAll(messageSources).build()); } /** @@ -60,30 +52,33 @@ public final class Messages { 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 errorMessages) { Formatter fmt = new Formatter().format(heading).format(":%n%n"); int index = 1; boolean displayCauses = getOnlyCause(errorMessages) == null; + List> remainingErrors = + errorMessages.stream().map(Message::getErrorDetail).collect(Collectors.toList()); + Map, Integer> causes = Maps.newHashMap(); - for (Message errorMessage : errorMessages) { - int thisIdx = index++; - fmt.format("%s) %s%n", thisIdx, errorMessage.getMessage()); + while (!remainingErrors.isEmpty()) { + ErrorDetail currentError = remainingErrors.get(0); + // 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>> partitionedByMergeable = + remainingErrors.subList(1, remainingErrors.size()).stream() + .collect(Collectors.partitioningBy(currentError::isMergeable)); - List dependencies = errorMessage.getSources(); - for (int i = dependencies.size() - 1; i >= 0; i--) { - Object source = dependencies.get(i); - formatSource(fmt, source); - } + remainingErrors = partitionedByMergeable.get(false); - Throwable cause = errorMessage.getCause(); + currentError.format(index, partitionedByMergeable.get(true), fmt); + + Throwable cause = currentError.getCause(); if (displayCauses && cause != null) { Equivalence.Wrapper causeEquivalence = ThrowableEquivalence.INSTANCE.wrap(cause); if (!causes.containsKey(causeEquivalence)) { - causes.put(causeEquivalence, thisIdx); + causes.put(causeEquivalence, index); fmt.format("Caused by: %s", Throwables.getStackTraceAsString(cause)); } else { int causeIdx = causes.get(causeEquivalence); @@ -92,57 +87,64 @@ public final class Messages { cause.getClass().getName(), causeIdx); } } - fmt.format("%n"); + index++; } - if (errorMessages.size() == 1) { + if (index == 2) { fmt.format("1 error"); } 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. * + * @param errorId The enum id for the error * @param messageFormat Format string - * @param arguments format string arguments + * @param arguments format string arguments */ - public static Message create(String messageFormat, Object... arguments) { - return create(null, messageFormat, arguments); + public static Message create(ErrorId errorId, String messageFormat, Object... arguments) { + return create(errorId, null, messageFormat, arguments); } /** * Creates a new Message with the given cause. * - * @param cause The exception that caused the error + * @param errorId The enum id for the error + * @param cause The exception that caused the error * @param messageFormat Format string - * @param arguments format string arguments + * @param arguments format string arguments */ - public static Message create(Throwable cause, String messageFormat, Object... arguments) { - return create(cause, ImmutableList.of(), messageFormat, arguments); + public static Message create( + 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. * - * @param cause The exception that caused the error - * @param sources The binding sources for the source stack + * @param errorId The enum id for the error + * @param cause The exception that caused the error + * @param sources The binding sources for the source stack * @param messageFormat Format string - * @param arguments format string arguments + * @param arguments format string arguments */ - public static Message create(Throwable cause, List sources, String messageFormat, Object... arguments) { + public static Message create( + ErrorId errorId, + Throwable cause, + List sources, + String messageFormat, + Object... 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. - */ - public static Object convert(Object o) { + /** Formats an object in a user friendly way. */ + static Object convert(Object o) { ElementSource source = null; if (o instanceof ElementSource) { source = (ElementSource) o; @@ -151,7 +153,7 @@ public final class Messages { return convert(o, source); } - public static Object convert(Object o, ElementSource source) { + static Object convert(Object o, ElementSource source) { for (Converter converter : converters) { if (converter.appliesTo(o)) { return appendModules(converter.convert(o), source); @@ -161,145 +163,11 @@ public final class Messages { } private static Object appendModules(Object source, ElementSource elementSource) { - String modules = moduleSourceString(elementSource); + String modules = SourceFormatter.getModuleStack(elementSource); if (modules.length() == 0) { return source; } else { - return source + 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 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 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 - * https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers - */ - 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"; - } + return source + " (installed by: " + modules + ")"; } } @@ -322,22 +190,22 @@ public final class Messages { abstract String toString(T t); } - @SuppressWarnings({"rawtypes"}) // rawtypes aren't avoidable + @SuppressWarnings({"unchecked", "rawtypes"}) // rawtypes aren't avoidable private static final Collection> converters = ImmutableList.of( - new Converter<>(Class.class) { + new Converter(Class.class) { @Override public String toString(Class c) { return c.getName(); } }, - new Converter<>(Member.class) { + new Converter(Member.class) { @Override public String toString(Member member) { return Classes.toString(member); } }, - new Converter<>(Key.class) { + new Converter(Key.class) { @Override public String toString(Key key) { if (key.getAnnotationType() != null) { @@ -388,4 +256,46 @@ public final class Messages { 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); + } } diff --git a/src/main/java/com/google/inject/internal/MissingConstructorError.java b/src/main/java/com/google/inject/internal/MissingConstructorError.java new file mode 100644 index 0000000..61fca20 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MissingConstructorError.java @@ -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 { + private final TypeLiteral type; + private final boolean atInjectRequired; + + MissingConstructorError(TypeLiteral type, boolean atInjectRequired, List 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> 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> 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 sources : sourcesList) { + ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter); + } + } + + @Override + public MissingConstructorError withSources(List newSources) { + return new MissingConstructorError(type, atInjectRequired, newSources); + } +} diff --git a/src/main/java/com/google/inject/internal/MissingImplementationError.java b/src/main/java/com/google/inject/internal/MissingImplementationError.java new file mode 100644 index 0000000..abfc040 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MissingImplementationError.java @@ -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 + extends InternalErrorDetail> { + + private final Key key; + private final ImmutableList suggestions; + + public MissingImplementationError(Key key, Injector injector, List sources) { + this(key, MissingImplementationErrorHints.getSuggestions(key, injector), sources); + } + + private MissingImplementationError( + Key key, ImmutableList suggestions, List 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> mergeableErrors, Formatter formatter) { + if (!suggestions.isEmpty()) { + suggestions.forEach(formatter::format); + } + List> sourcesList = new ArrayList<>(); + sourcesList.add(getSources()); + sourcesList.addAll( + mergeableErrors.stream().map(ErrorDetail::getSources).collect(Collectors.toList())); + + List> 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 sources : filteredSourcesList) { + ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter); + } + } + } + + @Override + public MissingImplementationError withSources(List newSources) { + return new MissingImplementationError(key, suggestions, newSources); + } + + /** Omit the key itself in the source list since the information is redundant. */ + private List trimSource(List sources) { + return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/google/inject/internal/MissingImplementationErrorHints.java b/src/main/java/com/google/inject/internal/MissingImplementationErrorHints.java new file mode 100644 index 0000000..9bcbe09 --- /dev/null +++ b/src/main/java/com/google/inject/internal/MissingImplementationErrorHints.java @@ -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> COMMON_AMBIGUOUS_TYPES = + ImmutableSet.>builder() + .add(Object.class) + .add(String.class) + .addAll(Primitives.allWrapperTypes()) + .build(); + + static ImmutableList getSuggestions(Key key, Injector injector) { + ImmutableList.Builder suggestions = ImmutableList.builder(); + TypeLiteral type = key.getTypeLiteral(); + + BindingSourceRestriction.getMissingImplementationSuggestion(GuiceInternal.GUICE_INTERNAL, key) + .ifPresent(suggestions::add); + + // Keys which have similar strings as the desired key + List possibleMatches = new ArrayList<>(); + List> 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, 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(); + } +} diff --git a/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java b/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java index f3e48c2..3ad1258 100644 --- a/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java +++ b/src/main/java/com/google/inject/internal/ModuleAnnotatedMethodScannerProcessor.java @@ -4,6 +4,7 @@ import com.google.inject.spi.ModuleAnnotatedMethodScannerBinding; /** * Handles {@code Binder.scanModulesForAnnotatedMethods} commands. + * */ final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor { @@ -13,7 +14,7 @@ final class ModuleAnnotatedMethodScannerProcessor extends AbstractProcessor { @Override public Boolean visit(ModuleAnnotatedMethodScannerBinding command) { - injector.state.addScanner(command); + injector.getBindingData().addScanner(command); return true; } } diff --git a/src/main/java/com/google/inject/internal/PackageNameCompressor.java b/src/main/java/com/google/inject/internal/PackageNameCompressor.java new file mode 100644 index 0000000..edcb5e6 --- /dev/null +++ b/src/main/java/com/google/inject/internal/PackageNameCompressor.java @@ -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 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 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 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 replacementMap = shortenNames(names); + + // If we have nothing to replace, just return the original. + if (replacementMap.isEmpty()) { + return input; + } + + StringBuilder output = new StringBuilder(); + Set 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. + * + *

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 replaceFullNames( + String input, Map replacementMap, StringBuilder output) { + ImmutableSet.Builder 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 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 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 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 shortenNames(Collection names) { + HashMultimap> shortNameToPartsMap = HashMultimap.create(); + for (String name : names) { + List 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 conflictingShortNames = new ArrayList<>(); + for (Map.Entry>> 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> partsCollection = shortNameToPartsMap.removeAll(conflictingShortName); + for (List 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 replacementMap = new TreeMap<>(); + for (Map.Entry>> entry + : shortNameToPartsMap.asMap().entrySet()) { + replacementMap.put( + entry.getKey(), + PACKAGE_JOINER.join(Iterables.getOnlyElement(entry.getValue())) + "." + entry.getKey()); + } + return replacementMap; + } + + private PackageNameCompressor() {} +} diff --git a/src/main/java/com/google/inject/internal/ProviderMethodsModule.java b/src/main/java/com/google/inject/internal/ProviderMethodsModule.java index 5971a44..1445b55 100644 --- a/src/main/java/com/google/inject/internal/ProviderMethodsModule.java +++ b/src/main/java/com/google/inject/internal/ProviderMethodsModule.java @@ -1,5 +1,9 @@ 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.ImmutableList; 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.ModuleAnnotatedMethodScanner; import com.google.inject.util.Modules; - import java.lang.annotation.Annotation; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -22,49 +25,38 @@ import java.util.ArrayList; import java.util.Arrays; 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 * binding annotations on the provider method to configure the binding. * */ public final class ProviderMethodsModule implements Module { - private final Object delegate; - private final TypeLiteral typeLiteral; - private final boolean skipFastClassGeneration; - private final ModuleAnnotatedMethodScanner scanner; - private ProviderMethodsModule(Object delegate, boolean skipFastClassGeneration, - ModuleAnnotatedMethodScanner scanner) { + private ProviderMethodsModule( + Object delegate, boolean skipFastClassGeneration, ModuleAnnotatedMethodScanner scanner) { this.delegate = checkNotNull(delegate, "delegate"); - this.typeLiteral = TypeLiteral.get(this.delegate.getClass()); + this.typeLiteral = TypeLiteral.get(getDelegateModuleClass()); this.skipFastClassGeneration = skipFastClassGeneration; 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) { 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) { return forObject(module, false, scanner); } /** - * Returns a module which creates bindings for provider methods from the given object. - * This is useful notably for GIN + * Returns a module which creates bindings for provider methods from the given object. This is + * useful notably for GIN * *

This will skip bytecode generation for provider methods, since it is assumed that callers * are only interested in Module metadata. @@ -73,12 +65,13 @@ public final class ProviderMethodsModule implements Module { return forObject(object, true, ProvidesMethodScanner.INSTANCE); } - private static Module forObject(Object object, boolean skipFastClassGeneration, - ModuleAnnotatedMethodScanner scanner) { + private static Module forObject( + Object object, boolean skipFastClassGeneration, ModuleAnnotatedMethodScanner scanner) { // avoid infinite recursion, since installing a module always installs itself if (object instanceof ProviderMethodsModule) { return Modules.EMPTY_MODULE; } + return new ProviderMethodsModule(object, skipFastClassGeneration, scanner); } @@ -86,6 +79,10 @@ public final class ProviderMethodsModule implements Module { return isStaticModule() ? (Class) delegate : delegate.getClass(); } + private boolean isStaticModule() { + return delegate instanceof Class; + } + @Override public void configure(Binder 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> getProviderMethods(Binder binder) { List> result = null; List methodsAndAnnotations = null; @@ -196,6 +185,16 @@ public final class ProviderMethodsModule implements Module { 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. */ private Annotation getAnnotation(Binder binder, Method method) { if (method.isBridge() || method.isSynthetic()) { @@ -218,30 +217,7 @@ public final class ProviderMethodsModule implements Module { return annotation; } - @Override - 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 { + private static final class Signature { final Class[] parameters; final String name; final int hashCode; @@ -277,7 +253,6 @@ public final class ProviderMethodsModule implements Module { } } - /** Returns true if a overrides b, assumes that the signatures match */ private static boolean overrides(Method a, Method b) { // See JLS section 8.4.8.1 @@ -300,30 +275,29 @@ public final class ProviderMethodsModule implements Module { // prepare the parameter providers InjectionPoint point = InjectionPoint.forMethod(method, typeLiteral); @SuppressWarnings("unchecked") // Define T as the method's return type. - TypeLiteral returnType = (TypeLiteral) typeLiteral.getReturnType(method); + TypeLiteral returnType = (TypeLiteral) typeLiteral.getReturnType(method); Key key = getKey(errors, returnType, method, method.getAnnotations()); + boolean prepareMethodError = false; try { key = scanner.prepareMethod(binder, annotation, key, point); } catch (Throwable t) { + prepareMethodError = true; binder.addError(t); } if (Modifier.isAbstract(method.getModifiers())) { checkState( - key == null, + prepareMethodError || key == null, "%s returned a non-null key (%s) for %s. prepareMethod() must return null for abstract" + " methods", scanner, key, method); return null; - } else { - checkState( - key != null, - "%s returned a null key for %s. prepareMethod() can only return null for abstract" - + " methods", - scanner, - method); + } + + if (key == null) { // scanner returned null. Skipping the binding. + return null; } Class scopeAnnotation = @@ -335,7 +309,7 @@ public final class ProviderMethodsModule implements Module { return ProviderMethod.create( key, method, - isStaticModule() ? null : delegate, + isStaticModule() || Modifier.isStatic(method.getModifiers()) ? null : delegate, ImmutableSet.copyOf(point.getDependencies()), scopeAnnotation, skipFastClassGeneration, @@ -347,4 +321,24 @@ public final class ProviderMethodsModule implements Module { 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; + } } diff --git a/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java b/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java index aa7c839..274839a 100644 --- a/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java +++ b/src/main/java/com/google/inject/internal/ProvisionListenerStackCallback.java @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.spi.ProvisionListener; - import java.util.List; import java.util.Set; @@ -23,6 +22,11 @@ final class ProvisionListenerStackCallback { private final ProvisionListener[] listeners; private final Binding binding; + @SuppressWarnings("unchecked") + public static ProvisionListenerStackCallback emptyListener() { + return (ProvisionListenerStackCallback) EMPTY_CALLBACK; + } + public ProvisionListenerStackCallback(Binding binding, List listeners) { this.binding = binding; if (listeners.isEmpty()) { @@ -33,18 +37,13 @@ final class ProvisionListenerStackCallback { } } - @SuppressWarnings("unchecked") - public static ProvisionListenerStackCallback emptyListener() { - return (ProvisionListenerStackCallback) EMPTY_CALLBACK; - } - public boolean hasListeners() { return listeners.length > 0; } public T provision(InternalContext context, ProvisionCallback callable) throws InternalProvisionException { - Provision provision = new Provision(context, callable); + Provision provision = new Provision(callable); RuntimeException caught = null; try { provision.provision(); @@ -58,6 +57,7 @@ final class ProvisionListenerStackCallback { Object listener = provision.erredListener != null ? provision.erredListener.getClass() : "(unknown)"; throw InternalProvisionException.errorInUserCode( + ErrorId.OTHER, caught, "Error notifying ProvisionListener %s of %s.%n Reason: %s", listener, @@ -70,21 +70,18 @@ final class ProvisionListenerStackCallback { // TODO(sameb): Can this be more InternalFactory-like? public interface ProvisionCallback { - T call() throws InternalProvisionException; + public T call() throws InternalProvisionException; } private class Provision extends ProvisionListener.ProvisionInvocation { - - final InternalContext context; final ProvisionCallback callable; int index = -1; T result; InternalProvisionException exceptionDuringProvision; ProvisionListener erredListener; - public Provision(InternalContext context, ProvisionCallback callable) { + public Provision(ProvisionCallback callable) { this.callable = callable; - this.context = context; } @Override diff --git a/src/main/java/com/google/inject/internal/RealMapBinder.java b/src/main/java/com/google/inject/internal/RealMapBinder.java index 0daa793..071ab27 100644 --- a/src/main/java/com/google/inject/internal/RealMapBinder.java +++ b/src/main/java/com/google/inject/internal/RealMapBinder.java @@ -38,6 +38,7 @@ import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.util.Types; import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -297,7 +298,7 @@ public final class RealMapBinder implements Module { // Binds a Map> RealProviderMapProvider providerMapProvider = - new RealProviderMapProvider(bindingSelection); + new RealProviderMapProvider<>(bindingSelection); binder.bind(bindingSelection.getProviderMapKey()).toProvider(providerMapProvider); // The map this exposes is internally an ImmutableMap, so it's OK to massage @@ -309,7 +310,12 @@ public final class RealMapBinder implements Module { binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider); RealMapProvider mapProvider = new RealMapProvider<>(bindingSelection); - binder.bind(bindingSelection.getMapKey()).toProvider(mapProvider); + // Bind Map to the provider w/ extension support. + binder + .bind(bindingSelection.getMapKey()) + .toProvider(new ExtensionRealMapProvider<>(mapProvider)); + // Bind Map 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 // safe to massage the return type like this @@ -364,6 +370,7 @@ public final class RealMapBinder implements Module { private Key>>> providerCollectionMultimapKey; private Key>>> javaxProviderCollectionMultimapKey; private Key>>> entrySetJavaxProviderKey; + private Key> mapOfKeyExtendsValueKey; private final RealMultibinder>> entrySetBinder; @@ -485,7 +492,7 @@ public final class RealMapBinder implements Module { // we don't build up this data structure if (duplicates != null) { initializationState = InitializationState.HAS_ERRORS; - reportDuplicateKeysError(duplicates, errors); + reportDuplicateKeysError(mapKey, duplicates, errors); return false; } @@ -509,26 +516,9 @@ public final class RealMapBinder implements Module { } private static void reportDuplicateKeysError( - Multimap> duplicates, Errors errors) { - StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key "); - boolean first = true; - for (Map.Entry>> 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 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()); + Key> mapKey, Multimap> duplicates, Errors errors) { + errors.duplicateMapKey(mapKey, duplicates); + return; } private boolean containsElement(Element element) { @@ -553,6 +543,7 @@ public final class RealMapBinder implements Module { || key.equals(getJavaxProviderCollectionMultimapKey()) || key.equals(entrySetBinder.getSetKey()) || key.equals(getEntrySetJavaxProviderKey()) + || key.equals(getMapOfKeyExtendsValueKey()) || matchesValueKey(key); } @@ -638,6 +629,19 @@ public final class RealMapBinder implements Module { return local; } + @SuppressWarnings("unchecked") + private Key> getMapOfKeyExtendsValueKey() { + Key> local = mapOfKeyExtendsValueKey; + if (local == null) { + Type extendsValue = Types.subtypeOf(valueType.getType()); + Type mapOfKeyAndExtendsValue = Types.mapOf(keyType.getType(), extendsValue); + local = + mapOfKeyExtendsValueKey = + (Key>) mapKey.ofType(mapOfKeyAndExtendsValue); + } + return local; + } + private ImmutableMap> getMapBindings() { checkConfiguration(isInitialized(), "MapBinder has not yet been initialized"); return mapBindings; @@ -727,24 +731,23 @@ public final class RealMapBinder implements Module { } private static final class RealMapProvider - extends RealMapBinderProviderWithDependencies> - implements ProviderWithExtensionVisitor>, MapBinderBinding> { - private Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; + extends RealMapBinderProviderWithDependencies> { + Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; /** * An array of all the injectors. * *

This is parallel to array of keys below */ - private SingleParameterInjector[] injectors; + SingleParameterInjector[] injectors; - private K[] keys; + K[] keys; - private RealMapProvider(BindingSelection bindingSelection) { + RealMapProvider(BindingSelection bindingSelection) { super(bindingSelection); } - private BindingSelection getBindingSelection() { + BindingSelection getBindingSelection() { return bindingSelection; } @@ -805,6 +808,42 @@ public final class RealMapBinder implements Module { public Set> getDependencies() { 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 + extends RealMapBinderProviderWithDependencies> + implements ProviderWithExtensionVisitor>, MapBinderBinding> { + final RealMapProvider delegate; + + ExtensionRealMapProvider(RealMapProvider delegate) { + super(delegate.bindingSelection); + this.delegate = delegate; + } + + BindingSelection getBindingSelection() { + return bindingSelection; + } + + @Override + protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException { + delegate.doInitialize(injector, errors); + } + + @Override + protected Map doProvision(InternalContext context, Dependency dependency) + throws InternalProvisionException { + return delegate.doProvision(context, dependency); + } + + @Override + public Set> getDependencies() { + return delegate.getDependencies(); + } @Override @SuppressWarnings("unchecked") @@ -831,7 +870,8 @@ public final class RealMapBinder implements Module { (Key) bindingSelection.getJavaxProviderSetMultimapKey(), (Key) bindingSelection.getProviderCollectionMultimapKey(), (Key) bindingSelection.getJavaxProviderCollectionMultimapKey(), - (Key) bindingSelection.getMultimapKey()); + (Key) bindingSelection.getMultimapKey(), + (Key) bindingSelection.getMapOfKeyExtendsValueKey()); } @Override @@ -925,18 +965,18 @@ public final class RealMapBinder implements Module { if (!keysOnlyFromBindings.isEmpty()) { 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 key : keysOnlyFromBindings) { 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()) { - 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 key : keysOnlyFromProviderMapEntrys) { sb.append( - Messages.format( + Errors.format( " '%s' bound at: %s%n", valueKeyToKey.get(key), valueKeyToEntryBinding.get(key).getSource())); } @@ -1314,8 +1354,8 @@ public final class RealMapBinder implements Module { ProviderInstanceBinding> providerInstanceBinding = (ProviderInstanceBinding>) mapBinding; @SuppressWarnings("unchecked") - RealMapProvider mapProvider = - (RealMapProvider) providerInstanceBinding.getUserSuppliedProvider(); + ExtensionRealMapProvider mapProvider = + (ExtensionRealMapProvider) providerInstanceBinding.getUserSuppliedProvider(); this.bindingSelection = mapProvider.getBindingSelection(); @@ -1346,7 +1386,9 @@ public final class RealMapBinder implements Module { private static InternalProvisionException createNullValueException( K key, Binding binding) { return InternalProvisionException.create( + ErrorId.NULL_VALUE_IN_MAP, "Map injection failed due to null value for key \"%s\", bound at: %s", - key, binding.getSource()); + key, + binding.getSource()); } } diff --git a/src/main/java/com/google/inject/internal/RealMultibinder.java b/src/main/java/com/google/inject/internal/RealMultibinder.java index 2da18bd..1874a85 100644 --- a/src/main/java/com/google/inject/internal/RealMultibinder.java +++ b/src/main/java/com/google/inject/internal/RealMultibinder.java @@ -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.name.Names.named; -import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; 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.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; +import com.google.inject.spi.Message; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.util.Types; @@ -31,6 +31,7 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.function.Function; /** * The actual multibinder plays several roles: @@ -81,6 +82,13 @@ public final class RealMultibinder implements Module { return (TypeLiteral>>) TypeLiteral.get(type); } + @SuppressWarnings("unchecked") + static TypeLiteral> setOfExtendsOf(TypeLiteral elementType) { + Type extendsType = Types.subtypeOf(elementType.getType()); + Type setOfExtendsType = Types.setOf(extendsType); + return (TypeLiteral>) TypeLiteral.get(setOfExtendsType); + } + private final BindingSelection bindingSelection; private final Binder binder; @@ -92,9 +100,17 @@ public final class RealMultibinder implements Module { @Override public void configure(Binder binder) { checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized"); + + RealMultibinderProvider setProvider = new RealMultibinderProvider(bindingSelection); + // Bind the setKey to the provider wrapped w/ extension support. binder .bind(bindingSelection.getSetKey()) - .toProvider(new RealMultibinderProvider(bindingSelection)); + .toProvider(new ExtensionRealMultibinderProvider<>(setProvider)); + // Bind the 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>> collectionOfProvidersProvider = new RealMultibinderCollectionOfProvidersProvider(bindingSelection); binder @@ -148,15 +164,19 @@ public final class RealMultibinder implements Module { return bindingSelection.containsElement(element); } - private static final class RealMultibinderProvider - extends InternalProviderInstanceBindingImpl.Factory> - implements ProviderWithExtensionVisitor>, MultibinderBinding> { - private final BindingSelection bindingSelection; - private List> bindings; - private SingleParameterInjector[] injectors; - private boolean permitDuplicates; + /** + * Base implement of {@link InternalProviderInstanceBindingImpl.Factory} that works based on a + * {@link BindingSelection}, allowing provider instances for various bindings to be implemented + * with less duplication. + */ + private abstract static class BaseFactory + extends InternalProviderInstanceBindingImpl.Factory { + final Function, ImmutableSet>> dependenciesFn; + final BindingSelection bindingSelection; - RealMultibinderProvider(BindingSelection bindingSelection) { + BaseFactory( + BindingSelection bindingSelection, + Function, ImmutableSet>> dependenciesFn) { // While Multibinders only depend on bindings created in modules so we could theoretically // initialize eagerly, they also depend on // 1. findBindingsByType returning results @@ -164,23 +184,58 @@ public final class RealMultibinder implements Module { // neither of those is available during eager initialization, so we use DELAYED super(InitializationTiming.DELAYED); this.bindingSelection = bindingSelection; - } - - @Override - public Set> getDependencies() { - return bindingSelection.getDependencies(); + this.dependenciesFn = dependenciesFn; } @Override void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { bindingSelection.initialize(injector, errors); - this.bindings = bindingSelection.getBindings(); - this.injectors = bindingSelection.getParameterInjectors(); - this.permitDuplicates = bindingSelection.permitsDuplicates(); + doInitialize(); + } + + abstract void doInitialize(); + + @Override + public Set> getDependencies() { + return dependenciesFn.apply(bindingSelection); } @Override - protected Set 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 and Set, the latter being useful for Kotlin + * support. + */ + private static final class RealMultibinderProvider extends BaseFactory> { + List> bindings; + SingleParameterInjector[] injectors; + boolean permitDuplicates; + + RealMultibinderProvider(BindingSelection 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 doProvision(InternalContext context, Dependency dependency) throws InternalProvisionException { SingleParameterInjector[] localInjectors = injectors; if (localInjectors == null) { @@ -204,14 +259,52 @@ public final class RealMultibinder implements Module { ImmutableSet set = ImmutableSet.copyOf(values); // There are fewer items in the set than the array. Figure out which one got dropped. if (!permitDuplicates && set.size() < values.length) { - throw newDuplicateValuesException(set, values); + throw newDuplicateValuesException(values); } return set; } private InternalProvisionException newNullEntryException(int i) { 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( + 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 extends BaseFactory> + implements ProviderWithExtensionVisitor>, MultibinderBinding> { + final RealMultibinderProvider delegate; + + ExtensionRealMultibinderProvider(RealMultibinderProvider 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 doProvision(InternalContext context, Dependency dependency) + throws InternalProvisionException { + return delegate.doProvision(context, dependency); } @SuppressWarnings("unchecked") @@ -225,69 +318,17 @@ public final class RealMultibinder implements Module { } } - private InternalProvisionException newDuplicateValuesException( - ImmutableSet 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 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 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 public Key> getSetKey() { return bindingSelection.getSetKey(); } @Override - public Set> getAlternateSetKeys() { + public ImmutableSet> getAlternateSetKeys() { return ImmutableSet.of( (Key) bindingSelection.getCollectionOfProvidersKey(), - (Key) bindingSelection.getCollectionOfJavaxProvidersKey()); + (Key) bindingSelection.getCollectionOfJavaxProvidersKey(), + (Key) bindingSelection.getSetOfExtendsKey()); } @Override @@ -311,6 +352,34 @@ public final class RealMultibinder implements Module { } } + /** + * Implementation of BaseFactory that exposes a collection of providers of the values in the set. + */ + private static final class RealMultibinderCollectionOfProvidersProvider + extends BaseFactory>> { + ImmutableList> providers; + + RealMultibinderCollectionOfProvidersProvider(BindingSelection 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> providers = ImmutableList.builder(); + for (Binding binding : bindingSelection.getBindings()) { + providers.add(binding.getProvider()); + } + this.providers = providers.build(); + } + + @Override + protected ImmutableList> doProvision( + InternalContext context, Dependency dependency) { + return providers; + } + } + private static final class BindingSelection { // 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. @@ -323,6 +392,7 @@ public final class RealMultibinder implements Module { private String setName; private Key>> collectionOfProvidersKey; private Key>> collectionOfJavaxProvidersKey; + private Key> setOfExtendsKey; private Key permitDuplicatesKey; private boolean isInitialized; @@ -358,7 +428,7 @@ public final class RealMultibinder implements Module { for (Binding entry : injector.findBindingsByType(elementType)) { if (keyMatches(entry.getKey())) { @SuppressWarnings("unchecked") // protected by findBindingsByType() - Binding binding = (Binding) entry; + Binding binding = (Binding) entry; if (index.add(binding.acceptTargetVisitor(indexer))) { // TODO(lukes): most of these are linked bindings since user bindings are linked to // a user binding through the @Element annotation. Since this is an implementation @@ -448,6 +518,14 @@ public final class RealMultibinder implements Module { return local; } + Key> getSetOfExtendsKey() { + Key> local = setOfExtendsKey; + if (local == null) { + local = setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType)); + } + return local; + } + boolean isInitialized() { return isInitialized; } @@ -487,7 +565,8 @@ public final class RealMultibinder implements Module { || binding.getKey().equals(getPermitDuplicatesKey()) || binding.getKey().equals(setKey) || binding.getKey().equals(collectionOfProvidersKey) - || binding.getKey().equals(collectionOfJavaxProvidersKey); + || binding.getKey().equals(collectionOfJavaxProvidersKey) + || binding.getKey().equals(setOfExtendsKey); } else { return false; } @@ -533,51 +612,6 @@ public final class RealMultibinder implements Module { return bindingSelection.hashCode(); } - private static final class RealMultibinderCollectionOfProvidersProvider - extends InternalProviderInstanceBindingImpl.Factory>> { - - private final BindingSelection bindingSelection; - private ImmutableList> collectionOfProviders; - - RealMultibinderCollectionOfProvidersProvider(BindingSelection 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> providers = ImmutableList.builder(); - for (Binding binding : bindingSelection.getBindings()) { - providers.add(binding.getProvider()); - } - this.collectionOfProviders = providers.build(); - } - - @Override - protected Collection> doProvision( - InternalContext context, Dependency dependency) { - return collectionOfProviders; - } - - @Override - public Set> 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 * only one of a multibinder's users remember to call permitDuplicates(), they're still permitted. diff --git a/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java b/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java index b702190..1d30581 100644 --- a/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java +++ b/src/main/java/com/google/inject/internal/ScopeBindingProcessor.java @@ -1,12 +1,11 @@ package com.google.inject.internal; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.inject.Scope; import com.google.inject.spi.ScopeBinding; - import java.lang.annotation.Annotation; -import static com.google.common.base.Preconditions.checkNotNull; - /** * Handles {@code Binder.bindScope} commands. * @@ -20,7 +19,8 @@ final class ScopeBindingProcessor extends AbstractProcessor { @Override public Boolean visit(ScopeBinding command) { Scope scope = checkNotNull(command.getScope(), "scope"); - Class annotationType = checkNotNull(command.getAnnotationType(), "annotation type"); + Class annotationType = + checkNotNull(command.getAnnotationType(), "annotation type"); if (!Annotations.isScopeAnnotation(annotationType)) { errors.missingScopeAnnotation(annotationType); @@ -32,15 +32,15 @@ final class ScopeBindingProcessor extends AbstractProcessor { // 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 (!scope.equals(existing.getScope())) { errors.duplicateScopes(existing, annotationType, scope); } } else { - injector.state.putScopeBinding(annotationType, command); + injector.getBindingData().putScopeBinding(annotationType, command); } return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/google/inject/internal/ScopeNotFoundError.java b/src/main/java/com/google/inject/internal/ScopeNotFoundError.java new file mode 100644 index 0000000..90a7ad8 --- /dev/null +++ b/src/main/java/com/google/inject/internal/ScopeNotFoundError.java @@ -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 { + + private final Class scopeAnnotation; + + ScopeNotFoundError(Class scopeAnnotation, List 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> mergeableErrors, Formatter formatter) { + List> 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 sources : sourcesSet) { + ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter); + } + } + + @Override + public ScopeNotFoundError withSources(List newSources) { + return new ScopeNotFoundError(scopeAnnotation, newSources); + } +} diff --git a/src/main/java/com/google/inject/internal/Scoping.java b/src/main/java/com/google/inject/internal/Scoping.java index cc126f3..36874d3 100644 --- a/src/main/java/com/google/inject/internal/Scoping.java +++ b/src/main/java/com/google/inject/internal/Scoping.java @@ -10,7 +10,6 @@ import com.google.inject.Stage; import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.spi.BindingScopingVisitor; import com.google.inject.spi.ScopeBinding; - import java.lang.annotation.Annotation; /** @@ -24,100 +23,127 @@ public abstract class Scoping { * 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. */ - public static final Scoping UNSCOPED = new Scoping() { - @Override - public V acceptVisitor(BindingScopingVisitor visitor) { - return visitor.visitNoScoping(); - } + public static final Scoping UNSCOPED = + new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitNoScoping(); + } - @Override - public Scope getScopeInstance() { - return Scopes.NO_SCOPE; - } + @Override + public Scope getScopeInstance() { + return Scopes.NO_SCOPE; + } - @Override - public String toString() { - return Scopes.NO_SCOPE.toString(); - } + @Override + public String toString() { + return Scopes.NO_SCOPE.toString(); + } - @Override - public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { - // do nothing - } - }; + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + // do nothing + } + }; - public static final Scoping SINGLETON_ANNOTATION = new Scoping() { - @Override - public V acceptVisitor(BindingScopingVisitor visitor) { - return visitor.visitScopeAnnotation(Singleton.class); - } + /** + * 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 acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitNoScoping(); + } - @Override - public Class getScopeAnnotation() { - return Singleton.class; - } + @Override + public Scope getScopeInstance() { + return Scopes.NO_SCOPE; + } - @Override - public String toString() { - return Singleton.class.getName(); - } + @Override + public String toString() { + return Scopes.NO_SCOPE.toString(); + } - @Override - public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { - scopedBindingBuilder.in(Singleton.class); - } - }; + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Scopes.NO_SCOPE); + } + }; - public static final Scoping SINGLETON_INSTANCE = new Scoping() { - @Override - public V acceptVisitor(BindingScopingVisitor visitor) { - return visitor.visitScope(Scopes.SINGLETON); - } + public static final Scoping SINGLETON_ANNOTATION = + new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScopeAnnotation(Singleton.class); + } - @Override - public Scope getScopeInstance() { - return Scopes.SINGLETON; - } + @Override + public Class getScopeAnnotation() { + return Singleton.class; + } - @Override - public String toString() { - return Scopes.SINGLETON.toString(); - } + @Override + public String toString() { + return Singleton.class.getName(); + } - @Override - public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { - scopedBindingBuilder.in(Scopes.SINGLETON); - } - }; + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Singleton.class); + } + }; - public static final Scoping EAGER_SINGLETON = new Scoping() { - @Override - public V acceptVisitor(BindingScopingVisitor visitor) { - return visitor.visitEagerSingleton(); - } + public static final Scoping SINGLETON_INSTANCE = + new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScope(Scopes.SINGLETON); + } - @Override - public Scope getScopeInstance() { - return Scopes.SINGLETON; - } + @Override + public Scope getScopeInstance() { + return Scopes.SINGLETON; + } - @Override - public String toString() { - return "eager singleton"; - } + @Override + public String toString() { + return Scopes.SINGLETON.toString(); + } - @Override - public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { - scopedBindingBuilder.asEagerSingleton(); - } - }; + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Scopes.SINGLETON); + } + }; - private Scoping() { - } + public static final Scoping EAGER_SINGLETON = + new Scoping() { + @Override + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitEagerSingleton(); + } + + @Override + public Scope getScopeInstance() { + return Scopes.SINGLETON; + } + + @Override + public String toString() { + return "eager singleton"; + } + + @Override + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.asEagerSingleton(); + } + }; public static Scoping forAnnotation(final Class scopingAnnotation) { - if (scopingAnnotation == Singleton.class - || scopingAnnotation == javax.inject.Singleton.class) { + if (scopingAnnotation == Singleton.class || scopingAnnotation == javax.inject.Singleton.class) { return SINGLETON_ANNOTATION; } @@ -147,6 +173,8 @@ public abstract class Scoping { public static Scoping forInstance(final Scope scope) { if (scope == Scopes.SINGLETON) { return SINGLETON_INSTANCE; + } else if (scope == Scopes.NO_SCOPE) { + return EXPLICITLY_UNSCOPED; } return new Scoping() { @@ -172,42 +200,6 @@ public abstract class Scoping { }; } - /** - * Scopes an internal factory. - */ - static InternalFactory scope(Key key, InjectorImpl injector, - InternalFactory creator, Object source, Scoping scoping) { - - if (scoping.isNoScope()) { - return creator; - } - - Scope scope = scoping.getScopeInstance(); - - Provider scoped = scope.scope(key, new ProviderToInternalFactoryAdapter(injector, creator)); - return new InternalFactoryToProviderAdapter(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 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 * scoping annotation will be used. @@ -224,28 +216,25 @@ public abstract class Scoping { 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) { if (this == EAGER_SINGLETON) { 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() { 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 getScopeAnnotation() { return null; } @@ -269,4 +258,48 @@ public abstract class Scoping { public abstract V acceptVisitor(BindingScopingVisitor visitor); public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder); + + private Scoping() {} + + /** Scopes an internal factory. */ + static InternalFactory scope( + Key key, + InjectorImpl injector, + InternalFactory 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 scoped = + scope.scope(key, new ProviderToInternalFactoryAdapter(injector, creator)); + return new InternalFactoryToProviderAdapter(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 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; + } } diff --git a/src/main/java/com/google/inject/internal/SourceFormatter.java b/src/main/java/com/google/inject/internal/SourceFormatter.java new file mode 100644 index 0000000..28466a1 --- /dev/null +++ b/src/main/java/com/google/inject/internal/SourceFormatter.java @@ -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 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 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 + * https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers + */ + 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"; + } + } + } +} diff --git a/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java b/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java index 95c9103..2d6200c 100644 --- a/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java +++ b/src/main/java/com/google/inject/internal/TypeConverterBindingProcessor.java @@ -7,7 +7,6 @@ import com.google.inject.matcher.Matcher; import com.google.inject.matcher.Matchers; import com.google.inject.spi.TypeConverter; import com.google.inject.spi.TypeConverterBinding; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -22,9 +21,7 @@ final class TypeConverterBindingProcessor extends AbstractProcessor { 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) { // Configure type converters. convertToPrimitiveType(injector, int.class, Integer.class); @@ -35,34 +32,44 @@ final class TypeConverterBindingProcessor extends AbstractProcessor { convertToPrimitiveType(injector, float.class, Float.class); convertToPrimitiveType(injector, double.class, Double.class); - convertToClass(injector, Character.class, new TypeConverter() { - public Object convert(String value, TypeLiteral toType) { - value = value.trim(); - if (value.length() != 1) { - throw new RuntimeException("Length != 1."); - } - return value.charAt(0); - } + convertToClass( + injector, + Character.class, + new TypeConverter() { + @Override + public Object convert(String value, TypeLiteral toType) { + value = value.trim(); + if (value.length() != 1) { + throw new RuntimeException("Length != 1."); + } + return value.charAt(0); + } - @Override - public String toString() { - return "TypeConverter"; - } - }); + @Override + public String toString() { + return "TypeConverter"; + } + }); - convertToClasses(injector, Matchers.subclassesOf(Enum.class), new TypeConverter() { - @SuppressWarnings("unchecked") - public Object convert(String value, TypeLiteral toType) { - return Enum.valueOf((Class) toType.getRawType(), value); - } + convertToClasses( + injector, + Matchers.subclassesOf(Enum.class), + new TypeConverter() { + @SuppressWarnings("rawtypes") // Unavoidable, only way to use Enum.valueOf + @Override + public Object convert(String value, TypeLiteral toType) { + return Enum.valueOf((Class) toType.getRawType(), value); + } - @Override - public String toString() { - return "TypeConverter>"; - } - }); + @Override + public String toString() { + return "TypeConverter>"; + } + }); - internalConvertToTypes(injector, new AbstractMatcher<>() { + internalConvertToTypes( + injector, + new AbstractMatcher>() { @Override public boolean matches(TypeLiteral typeLiteral) { return typeLiteral.getRawType() == Class.class; @@ -74,6 +81,7 @@ final class TypeConverterBindingProcessor extends AbstractProcessor { } }, new TypeConverter() { + @Override public Object convert(String value, TypeLiteral toType) { try { return Class.forName(value); @@ -86,32 +94,33 @@ final class TypeConverterBindingProcessor extends AbstractProcessor { public String toString() { return "TypeConverter>"; } - } - ); + }); } - private static void convertToPrimitiveType(InjectorImpl injector, Class primitiveType, - final Class wrapperType) { + private static void convertToPrimitiveType( + InjectorImpl injector, Class primitiveType, final Class wrapperType) { try { - final Method parser = wrapperType.getMethod( - "parse" + capitalize(primitiveType.getName()), String.class); + final Method parser = + wrapperType.getMethod("parse" + capitalize(primitiveType.getName()), String.class); - TypeConverter typeConverter = new TypeConverter() { - public Object convert(String value, TypeLiteral toType) { - try { - return parser.invoke(null, value); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e.getTargetException().getMessage()); - } - } + TypeConverter typeConverter = + new TypeConverter() { + @Override + public Object convert(String value, TypeLiteral toType) { + try { + return parser.invoke(null, value); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException().getMessage()); + } + } - @Override - public String toString() { - return "TypeConverter<" + wrapperType.getSimpleName() + ">"; - } - }; + @Override + public String toString() { + return "TypeConverter<" + wrapperType.getSimpleName() + ">"; + } + }; convertToClass(injector, wrapperType, typeConverter); } catch (NoSuchMethodException e) { @@ -119,52 +128,58 @@ final class TypeConverterBindingProcessor extends AbstractProcessor { } } - private static void convertToClass(InjectorImpl injector, Class type, - TypeConverter converter) { + private static void convertToClass( + InjectorImpl injector, Class type, TypeConverter converter) { convertToClasses(injector, Matchers.identicalTo(type), converter); } - private static void convertToClasses(InjectorImpl injector, - final Matcher> typeMatcher, TypeConverter converter) { - internalConvertToTypes(injector, new AbstractMatcher<>() { - public boolean matches(TypeLiteral typeLiteral) { - Type type = typeLiteral.getType(); - if (!(type instanceof Class)) { - return false; - } - Class clazz = (Class) type; - return typeMatcher.matches(clazz); - } + private static void convertToClasses( + InjectorImpl injector, final Matcher> typeMatcher, TypeConverter converter) { + internalConvertToTypes( + injector, + new AbstractMatcher>() { + @Override + public boolean matches(TypeLiteral typeLiteral) { + Type type = typeLiteral.getType(); + if (!(type instanceof Class)) { + return false; + } + Class clazz = (Class) type; + return typeMatcher.matches(clazz); + } - @Override - public String toString() { - return typeMatcher.toString(); - } - }, converter); + @Override + public String toString() { + return typeMatcher.toString(); + } + }, + converter); + } + + private static void internalConvertToTypes( + InjectorImpl injector, Matcher> typeMatcher, TypeConverter converter) { + injector + .getBindingData() + .addConverter( + new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter)); } @Override public Boolean visit(TypeConverterBinding command) { - injector.state.addConverter(new TypeConverterBinding( - command.getSource(), command.getTypeMatcher(), command.getTypeConverter())); + injector + .getBindingData() + .addConverter( + new TypeConverterBinding( + command.getSource(), command.getTypeMatcher(), command.getTypeConverter())); return true; } - private static void internalConvertToTypes(InjectorImpl injector, - Matcher> typeMatcher, - TypeConverter converter) { - injector.state.addConverter( - new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter)); - } - private static String capitalize(String s) { if (s.length() == 0) { return s; } char first = s.charAt(0); char capitalized = Character.toUpperCase(first); - return (first == capitalized) - ? s - : capitalized + s.substring(1); + return (first == capitalized) ? s : capitalized + s.substring(1); } } diff --git a/src/main/java/com/google/inject/internal/WeakKeySet.java b/src/main/java/com/google/inject/internal/WeakKeySet.java index b61676d..353371c 100644 --- a/src/main/java/com/google/inject/internal/WeakKeySet.java +++ b/src/main/java/com/google/inject/internal/WeakKeySet.java @@ -5,14 +5,13 @@ import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; 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.Maps; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import com.google.inject.Key; import com.google.inject.internal.util.SourceProvider; - import java.util.Map; import java.util.Set; @@ -21,38 +20,29 @@ import java.util.Set; */ final class WeakKeySet { + private Map, Multiset> backingMap; + /** * This is already locked externally on add and getSources but we need it to handle clean up in * the evictionCache's RemovalListener. */ private final Object lock; - private Map, Multiset> 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. */ - private final Cache> evictionCache = CacheBuilder.newBuilder() - .weakKeys() - .removalListener( - (RemovalListener>) notification -> { - Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause())); + private final Cache> evictionCache = + CacheBuilder.newBuilder().weakKeys().removalListener(this::cleanupOnRemoval).build(); - cleanUpForCollectedState(notification.getValue()); - }) - .build(); + private void cleanupOnRemoval( + RemovalNotification> notification) { + Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause())); - 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 keysAndSources) { + // There may be multiple child injectors banning a certain key so only remove the source + // that's relevant. synchronized (lock) { - for (KeyAndSource keyAndSource : keysAndSources) { + for (KeyAndSource keyAndSource : notification.getValue()) { Multiset set = backingMap.get(keyAndSource.key); if (set != null) { 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) { backingMap = Maps.newHashMap(); } @@ -73,11 +67,11 @@ final class WeakKeySet { if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) { source = null; } - Object convertedSource = Messages.convert(source); + Object convertedSource = Errors.convert(source); backingMap.computeIfAbsent(key, k -> LinkedHashMultiset.create()).add(convertedSource); // Avoid all the extra work if we can. - if (state.parent() != State.NONE) { + if (state.parent().isPresent()) { Set keyAndSources = evictionCache.getIfPresent(state); if (keyAndSources == null) { evictionCache.put(state, keyAndSources = Sets.newHashSet()); @@ -122,8 +116,7 @@ final class WeakKeySet { } KeyAndSource other = (KeyAndSource) obj; - return Objects.equal(key, other.key) - && Objects.equal(source, other.source); + return Objects.equal(key, other.key) && Objects.equal(source, other.source); } } } diff --git a/src/main/java/com/google/inject/internal/util/ContinuousStopwatch.java b/src/main/java/com/google/inject/internal/util/ContinuousStopwatch.java new file mode 100644 index 0000000..057158c --- /dev/null +++ b/src/main/java/com/google/inject/internal/util/ContinuousStopwatch.java @@ -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"); + } +} diff --git a/src/main/java/com/google/inject/spi/BindingSourceRestriction.java b/src/main/java/com/google/inject/spi/BindingSourceRestriction.java new file mode 100644 index 0000000..714b009 --- /dev/null +++ b/src/main/java/com/google/inject/spi/BindingSourceRestriction.java @@ -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}. + * + *

Enforcement happens in two phases: + * + *

    + *
  1. Data structures for enforcement are built during Binder configuration. {@link + * PermitMapConstruction} encapsulates this process, and the {@link PermitMap} is the end + * result. + *
  2. Restrictions are enforced by checking each binding for violations with {@link #check}, + * which uses the {@link PermitMap}(s) built during Binder configuration. + *
+ * + *

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> getPermits(ElementSource elementSource); + + void clear(); + } + + /** Returns a suggestion for how a restricted binding should be created in case it's missing. */ + public static Optional 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. + * + *

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: + * + *

    + *
  • 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. + *
  • {@link PrivateElements}, which represent the recursive case of this check. They contain a + * list of elements that this check is recursively called on. + *
+ */ + public static ImmutableList check(GuiceInternal guiceInternal, List elements) { + checkNotNull(guiceInternal); + ImmutableList errorMessages = check(elements); + // Clear all the permit maps after the checks are done. + elements.forEach(BindingSourceRestriction::clear); + return errorMessages; + } + + private static ImmutableList check(List elements) { + ImmutableList.Builder errorMessagesBuilder = ImmutableList.builder(); + for (Element element : elements) { + errorMessagesBuilder.addAll(check(element)); + } + return errorMessagesBuilder.build(); + } + + private static ImmutableList check(Element element) { + return element.acceptVisitor( + new DefaultElementVisitor>() { + // Base case. + @Override + protected ImmutableList visitOther(Element element) { + return ImmutableList.of(); + } + // Base case. + @Override + public ImmutableList visit(Binding binding) { + Optional errorMessage = check(binding); + if (errorMessage.isPresent()) { + return ImmutableList.of(errorMessage.get()); + } + return ImmutableList.of(); + } + // Recursive case. + @Override + public ImmutableList visit(PrivateElements privateElements) { + return check(privateElements.getElements()); + } + }); + } + + private static Optional 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> permits = getAllPermits(elementSource); + ImmutableSet> 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> 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> getAllPermits( + ElementSource elementSource) { + ImmutableSet.Builder> 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 getAllModules(ElementSource elementSource) { + List 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() { + // 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. + * + *

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. + * + *

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>> modulePermits; + + @Override + public ImmutableSet> getPermits(ElementSource elementSource) { + return modulePermits.get(elementSource.moduleSource); + } + + @Override + public void clear() { + modulePermits = null; + } + } + + final Map>> modulePermits = + new HashMap<>(); + // Maintains the permits on the current module installation path. + ImmutableSet> currentModulePermits = ImmutableSet.of(); + // Stack tracking the currentModulePermits during module traversal. + final Deque>> 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> 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.>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> getPermits(Class clazz) { + Stream 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)); + } +} diff --git a/src/main/java/com/google/inject/spi/ElementSource.java b/src/main/java/com/google/inject/spi/ElementSource.java index d14eb71..754f054 100644 --- a/src/main/java/com/google/inject/spi/ElementSource.java +++ b/src/main/java/com/google/inject/spi/ElementSource.java @@ -4,41 +4,35 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.inject.internal.util.StackTraceElements; import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; - import java.util.List; /** - * Contains information about where and how an {@link Element element} was - * bound. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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()}). + * Contains information about where and how an {@link Element element} was bound. * + *

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. + * + *

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. + * + *

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. + * + *

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 { @@ -46,36 +40,63 @@ public final class ElementSource { * The {@link ElementSource source} of element that this element created from (if there is any), * otherwise {@code null}. */ - private final ElementSource originalElementSource; + final ElementSource originalElementSource; /** - * The {@link ModuleSource source} of module creates the element. - */ - private final ModuleSource moduleSource; - - private final InMemoryStackTraceElement[] partialCallStack; - - private final Object declaringSource; - - /** - * Creates a new {@link ElementSource} from the given parameters. + * Wheather the originalElementSource was set externaly (untrusted) or by Guice internals + * (trusted). * - * @param originalSource The source of element that this element created from (if there is - * any), otherwise {@code null}. - * @param declaringSource the source (in)directly declared the element. - * @param moduleSource the moduleSource when the element is bound - * @param partialCallStack the partial call stack from the top module to where the element is - * bound + *

External code can set the originalElementSource to an arbitrary ElementSource via + * Binder.withSource(ElementSource), thereby spoofing the element origin. */ - ElementSource(ElementSource originalSource, Object declaringSource, - ModuleSource moduleSource, StackTraceElement[] partialCallStack) { + 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}. + * @param declaringSource the source (in)directly declared the element. + * @param moduleSource the moduleSource when the element is bound + * @param partialCallStack the partial call stack from the top module to where the element is + * bound + */ + ElementSource( + ElementSource originalSource, + boolean trustedOriginalSource, + Object declaringSource, + ModuleSource moduleSource, + StackTraceElement[] partialCallStack, + ModuleAnnotatedMethodScanner scanner) { Preconditions.checkNotNull(declaringSource, "declaringSource cannot be null."); Preconditions.checkNotNull(moduleSource, "moduleSource cannot be null."); Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); this.originalElementSource = originalSource; + this.trustedOriginalElementSource = trustedOriginalSource; this.declaringSource = declaringSource; this.moduleSource = moduleSource; 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 - * such as {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method}, - * {@link java.lang.reflect.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. + * Returns a single location in source code that defines the element. It can be any object such as + * {@link java.lang.reflect.Constructor}, {@link java.lang.reflect.Method}, {@link + * java.lang.reflect.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. */ public Object getDeclaringSource() { return declaringSource; } /** - * Returns the class names of modules involved in creating this {@link Element}. The first - * element (index 0) is the class name of module that defined the element, and the last element - * is the class name of root module. + * Returns the class names of modules involved in creating this {@link Element}. The first element + * (index 0) is the class name of module that defined the element, and the last element is the + * class name of root module. */ public List 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 * 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: - *

- * {@code - * 0 - Binder.bind(), - * 1 - ModuleTwo.configure(), - * 2 - Binder.install(), - * 3 - ModuleOne.configure(), - * 4 - theRest(). - * } - *

- * 1 and 3 are returned. - *

- * In the cases where stack trace is not available (i.e., the stack trace was not collected), + * + *

    + *
  1. {@code Binder.bind()} + *
  2. {@code ModuleTwo.configure()} + *
  3. {@code Binder.install()} + *
  4. {@code ModuleOne.configure()} + *
  5. {@code theRest(). + *
+ * + *

1 and 3 are returned. + * + *

In the cases where stack trace is not available (i.e., the stack trace was not collected), * it returns -1 for all module positions. */ public List 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} - * {@code bindXXX()} methods and eventually defines the element. Note that - * this method 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. In the cases where stack trace is not available - * (i.e.,the stack trace was not collected), it returns an empty array. + * {@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. In the cases where stack trace is not available (i.e.,the stack trace was not + * collected), it returns an empty array. */ public StackTraceElement[] getStackTrace() { int modulesCallStackSize = moduleSource.getStackTraceSize(); @@ -152,15 +173,16 @@ public final class ElementSource { int size = moduleSource.getStackTraceSize() + chunkSize; StackTraceElement[] callStack = new StackTraceElement[size]; System.arraycopy( - StackTraceElements.convertToStackTraceElement(partialCallStack), 0, callStack, 0, + StackTraceElements.convertToStackTraceElement(partialCallStack), + 0, + callStack, + 0, chunkSize); System.arraycopy(moduleSource.getStackTrace(), 0, callStack, chunkSize, modulesCallStackSize); return callStack; } - /** - * Returns {@code getDeclaringSource().toString()} value. - */ + /** Returns {@code getDeclaringSource().toString()} value. */ @Override public String toString() { return getDeclaringSource().toString(); diff --git a/src/main/java/com/google/inject/spi/Elements.java b/src/main/java/com/google/inject/spi/Elements.java index f2b930d..5f0b07d 100644 --- a/src/main/java/com/google/inject/spi/Elements.java +++ b/src/main/java/com/google/inject/spi/Elements.java @@ -1,6 +1,11 @@ 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.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; 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.Errors; import com.google.inject.internal.ExposureBuilder; +import com.google.inject.internal.GuiceInternal; import com.google.inject.internal.InternalFlags.IncludeStackTraceOption; -import com.google.inject.internal.Messages; import com.google.inject.internal.MoreTypes; import com.google.inject.internal.PrivateElementsImpl; +import com.google.inject.internal.ProviderMethod; import com.google.inject.internal.ProviderMethodsModule; import com.google.inject.internal.util.SourceProvider; import com.google.inject.internal.util.StackTraceElements; import com.google.inject.matcher.Matcher; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collection; @@ -41,9 +46,6 @@ import java.util.List; import java.util.Map; 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 * Element#applyTo(Binder) rewritten}. @@ -52,66 +54,68 @@ import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOptio public final class Elements { private static final BindingTargetVisitor GET_INSTANCE_VISITOR = - new DefaultBindingTargetVisitor<>() { - @Override - public Object visit(InstanceBinding binding) { - return binding.getInstance(); - } + new DefaultBindingTargetVisitor() { + @Override + public Object visit(InstanceBinding binding) { + return binding.getInstance(); + } - @Override - protected Object visitOther(Binding binding) { - throw new IllegalArgumentException(); - } - }; + @Override + protected Object visitOther(Binding binding) { + throw new IllegalArgumentException(); + } + }; - /** - * Records the elements executed by {@code modules}. - */ + /** Records the elements executed by {@code modules}. */ public static List getElements(Module... 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 getElements(Stage stage, Module... modules) { return getElements(stage, Arrays.asList(modules)); } - /** - * Records the elements executed by {@code modules}. - */ + /** Records the elements executed by {@code modules}. */ public static List getElements(Iterable modules) { return getElements(Stage.DEVELOPMENT, modules); } - /** - * Records the elements executed by {@code modules}. - */ + /** Records the elements executed by {@code modules}. */ public static List getElements(Stage stage, Iterable modules) { RecordingBinder binder = new RecordingBinder(stage); for (Module module : modules) { binder.install(module); } binder.scanForAnnotatedMethods(); - for (RecordingBinder child : binder.privateBinders) { + for (RecordingBinder child : binder.privateBindersForScanning) { child.scanForAnnotatedMethods(); } + binder.permitMapConstruction.finish(); // Free the memory consumed by the stack trace elements cache StackTraceElements.clearCache(); 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}. + * + *

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 elements) { - return new ElementsAsModule(elements); - } - - @SuppressWarnings("unchecked") - static BindingTargetVisitor getInstanceVisitor() { - return (BindingTargetVisitor) GET_INSTANCE_VISITOR; + public static Binder withTrustedSource( + 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. + return binder.withSource(source); } 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 elements) { + return new ElementsAsModule(elements); + } + + @SuppressWarnings("unchecked") + static BindingTargetVisitor getInstanceVisitor() { + return (BindingTargetVisitor) GET_INSTANCE_VISITOR; + } + private static class ModuleInfo { - private final Binder binder; private final ModuleSource moduleSource; private final boolean skipScanning; - private ModuleInfo(Binder binder, ModuleSource moduleSource, boolean skipScanning) { - this.binder = binder; + private ModuleInfo(ModuleSource moduleSource, boolean skipScanning) { this.moduleSource = moduleSource; this.skipScanning = skipScanning; } @@ -148,19 +160,28 @@ public final class Elements { private final Object source; private final SourceProvider sourceProvider; private final Set scanners; - /** - * The binder where exposed bindings will be created - */ + /** The binder where exposed bindings will be created */ private final RecordingBinder parent; + private final PrivateElementsImpl privateElements; - /** - * All children private binders, so we can scan through them. - */ - private final List privateBinders; - /** - * The current modules stack - */ + /** All children private binders, so we can scan through them. */ + private final List privateBindersForScanning; + + private final BindingSourceRestriction.PermitMapConstruction permitMapConstruction; + + /** The current modules stack */ private ModuleSource moduleSource = null; + /** + * The current scanner. + * + *

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) { this.stage = stage; @@ -168,47 +189,59 @@ public final class Elements { this.scanners = Sets.newLinkedHashSet(); this.elements = Lists.newArrayList(); this.source = null; - this.sourceProvider = SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses( - Elements.class, RecordingBinder.class, AbstractModule.class, - ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class); + this.sourceProvider = + SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses( + Elements.class, + RecordingBinder.class, + AbstractModule.class, + ConstantBindingBuilderImpl.class, + AbstractBindingBuilder.class, + BindingBuilder.class); this.parent = 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( - RecordingBinder prototype, Object source, SourceProvider sourceProvider) { + RecordingBinder prototype, + Object source, + SourceProvider sourceProvider, + boolean trustedSource) { checkArgument(source == null ^ sourceProvider == null); this.stage = prototype.stage; this.modules = prototype.modules; this.elements = prototype.elements; this.scanners = prototype.scanners; + this.currentScanner = prototype.currentScanner; this.source = source; + this.trustedSource = trustedSource; this.moduleSource = prototype.moduleSource; this.sourceProvider = sourceProvider; this.parent = prototype.parent; 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) { this.stage = parent.stage; this.modules = Maps.newLinkedHashMap(); - this.scanners = Sets.newLinkedHashSet(parent.scanners); + this.scanners = Sets.newLinkedHashSet(); + this.currentScanner = parent.currentScanner; this.elements = privateElements.getElementsMutable(); this.source = parent.source; this.moduleSource = parent.moduleSource; this.sourceProvider = parent.sourceProvider; this.parent = parent; this.privateElements = privateElements; - this.privateBinders = parent.privateBinders; + this.privateBindersForScanning = parent.privateBindersForScanning; + this.permitMapConstruction = parent.permitMapConstruction; + this.scannerSource = parent.scannerSource; } @Override @@ -219,19 +252,23 @@ public final class Elements { @Override @SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type public void requestInjection(Object instance) { + checkNotNull(instance, "instance"); requestInjection((TypeLiteral) TypeLiteral.get(instance.getClass()), instance); } @Override public void requestInjection(TypeLiteral type, T instance) { - elements.add(new InjectionRequest<>(getElementSource(), MoreTypes.canonicalizeForKey(type), - instance)); + checkNotNull(instance, "instance"); + elements.add( + new InjectionRequest( + getElementSource(), MoreTypes.canonicalizeForKey(type), instance)); } @Override public MembersInjector getMembersInjector(final TypeLiteral typeLiteral) { - final MembersInjectorLookup element = new MembersInjectorLookup<>(getElementSource(), - MoreTypes.canonicalizeForKey(typeLiteral)); + final MembersInjectorLookup element = + new MembersInjectorLookup( + getElementSource(), MoreTypes.canonicalizeForKey(typeLiteral)); elements.add(element); return element.getMembersInjector(); } @@ -247,8 +284,8 @@ public final class Elements { } @Override - public void bindListener(Matcher> bindingMatcher, - ProvisionListener... listeners) { + public void bindListener( + Matcher> bindingMatcher, ProvisionListener... listeners) { elements.add(new ProvisionListenerBinding(getElementSource(), bindingMatcher, listeners)); } @@ -260,24 +297,26 @@ public final class Elements { } /** - * Applies all scanners to the modules we've installed. We skip certain - * PrivateModules because store them in more than one Modules map and only - * want to process them through one of the maps. (They're stored in both - * maps to prevent a module from being installed more than once.) + * Applies all scanners to the modules we've installed. We skip certain PrivateModules because + * store them in more than one Modules map and only want to process them through one of the + * maps. (They're stored in both maps to prevent a module from being installed more than once.) */ void scanForAnnotatedMethods() { - for (ModuleAnnotatedMethodScanner scanner : scanners) { - // Note: we must iterate over a copy of the modules because calling install(..) - // will mutate modules, otherwise causing a ConcurrentModificationException. - for (Map.Entry entry : Maps.newLinkedHashMap(modules).entrySet()) { - Module module = entry.getKey(); - ModuleInfo info = entry.getValue(); - if (info.skipScanning) { - continue; - } + Iterable scanners = getAllScanners(); + // Note: we must iterate over a copy of the modules because calling install(..) + // will mutate modules, otherwise causing a ConcurrentModificationException. + for (Map.Entry entry : Maps.newLinkedHashMap(modules).entrySet()) { + Module module = entry.getKey(); + ModuleInfo info = entry.getValue(); + if (info.skipScanning) { + continue; + } + for (ModuleAnnotatedMethodScanner scanner : scanners) { + currentScanner = scanner; moduleSource = entry.getValue().moduleSource; + permitMapConstruction.restoreCurrentModulePermits(moduleSource); try { - info.binder.install(ProviderMethodsModule.forModule(module, scanner)); + install(ProviderMethodsModule.forModule(module, scanner)); } catch (RuntimeException e) { Collection messages = Errors.getMessagesFromThrowable(e); if (!messages.isEmpty()) { @@ -293,51 +332,100 @@ public final class Elements { @Override public void install(Module module) { - if (!modules.containsKey(module)) { - RecordingBinder binder = this; - boolean unwrapModuleSource = false; - // Update the module source for the new module - if (module instanceof 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 - // 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. - Object delegate = ((ProviderMethodsModule) module).getDelegateModule(); - if (moduleSource == null - || !moduleSource.getModuleClassName().equals(delegate.getClass().getName())) { - moduleSource = getModuleSource(delegate); - unwrapModuleSource = true; - } + // 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; + // Update the module source for the new module + 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. + // 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. + // Also, if they pass something other than 'this' to it, we'd have the wrong source. + Class delegateClass = providerMethodsModule.getDelegateModuleClass(); + if (moduleSource == null + || !moduleSource.getModuleClassName().equals(delegateClass.getName())) { + newModuleClass = delegateClass; + } + } else { + if (moduleScanning()) { + forbidNestedScannerMethods(module); + } + newModuleClass = module.getClass(); + } + if (newModuleClass != null) { + moduleSource = getModuleSource(newModuleClass); + permitMapConstruction.pushModule(newModuleClass, moduleSource); + } + boolean skipScanning = false; + if (module instanceof PrivateModule) { + binder = (RecordingBinder) binder.newPrivateBinder(); + // Store the module in the private binder too so we scan for it. + binder.modules.put(module, new ModuleInfo(moduleSource, false)); + 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) + // so that we know not to process it again, and so that scanners inherit down. + modules.put(module, new ModuleInfo(moduleSource, skipScanning)); + try { + module.configure(binder); + } catch (RuntimeException e) { + Collection messages = Errors.getMessagesFromThrowable(e); + if (!messages.isEmpty()) { + elements.addAll(messages); } else { - moduleSource = getModuleSource(module); - unwrapModuleSource = true; - } - boolean skipScanning = false; - if (module instanceof PrivateModule) { - binder = (RecordingBinder) binder.newPrivateBinder(); - // Store the module in the private binder too so we scan for it. - binder.modules.put(module, new ModuleInfo(binder, moduleSource, false)); - 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) - // so that we know not to process it again, and so that scanners inherit down. - modules.put(module, new ModuleInfo(binder, moduleSource, skipScanning)); - try { - module.configure(binder); - } catch (RuntimeException e) { - Collection messages = Errors.getMessagesFromThrowable(e); - if (!messages.isEmpty()) { - elements.addAll(messages); - } else { - addError(e); - } - } - binder.install(ProviderMethodsModule.forModule(module)); - // We are done with this module, so undo module source change - if (unwrapModuleSource) { - moduleSource = moduleSource.getParent(); + addError(e); } } + binder.install(ProviderMethodsModule.forModule(module)); + // We are done with this module, so undo module source change + if (newModuleClass != null) { + 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. + * + *

Should only be called during module scanning, because at that point registering new + * scanners is forbidden. + */ + private Iterable 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 @@ -347,13 +435,13 @@ public final class Elements { @Override 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 public void addError(Throwable t) { 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 @@ -363,7 +451,9 @@ public final class Elements { @Override public AnnotatedBindingBuilder bind(Key key) { - return new BindingBuilder<>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key)); + BindingBuilder builder = + new BindingBuilder(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key)); + return builder; } @Override @@ -399,14 +489,24 @@ public final class Elements { } @Override - public void convertToTypes(Matcher> typeMatcher, - TypeConverter converter) { + public void convertToTypes( + Matcher> typeMatcher, TypeConverter converter) { elements.add(new TypeConverterBinding(getElementSource(), typeMatcher, converter)); } @Override 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 @@ -417,15 +517,19 @@ public final class Elements { } SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip); - return new RecordingBinder(this, null, newSourceProvider); + return new RecordingBinder( + this, /* source = */ null, newSourceProvider, /* trustedSource = */ false); } @Override public PrivateBinder newPrivateBinder() { PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource()); RecordingBinder binder = new RecordingBinder(this, privateElements); - privateBinders.add(binder); elements.add(privateElements); + // Don't want to scan private modules installed by scanners. + if (!moduleScanning()) { + privateBindersForScanning.add(binder); + } return binder; } @@ -451,6 +555,13 @@ public final class Elements { @Override 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); elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner)); } @@ -472,26 +583,26 @@ public final class Elements { private AnnotatedElementBuilder exposeInternal(Key key) { if (privateElements == null) { - addError("Cannot expose %s on a standard binder. " - + "Exposed bindings are only applicable to private binders.", key); + addError( + "Cannot expose %s on a standard binder. " + + "Exposed bindings are only applicable to private binders.", + key); return new AnnotatedElementBuilder() { @Override - public void annotatedWith(Class annotationType) { - } + public void annotatedWith(Class annotationType) {} @Override - public void annotatedWith(Annotation annotation) { - } + public void annotatedWith(Annotation annotation) {} }; } ExposureBuilder builder = - new ExposureBuilder<>(this, getElementSource(), MoreTypes.canonicalizeKey(key)); + new ExposureBuilder(this, getElementSource(), MoreTypes.canonicalizeKey(key)); privateElements.addExposureBuilder(builder); return builder; } - private ModuleSource getModuleSource(Object module) { + private ModuleSource getModuleSource(Class module) { StackTraceElement[] partialCallStack; if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) { partialCallStack = getPartialCallStack(new Throwable().getStackTrace()); @@ -499,7 +610,7 @@ public final class Elements { partialCallStack = new StackTraceElement[0]; } if (moduleSource == null) { - return new ModuleSource(module, partialCallStack); + return new ModuleSource(module, partialCallStack, permitMapConstruction.getPermitMap()); } return moduleSource.createChild(module, partialCallStack); } @@ -518,9 +629,9 @@ public final class Elements { declaringSource = originalSource.getDeclaringSource(); } IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption(); - if (stackTraceOption == IncludeStackTraceOption.COMPLETE || - (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE - && declaringSource == null)) { + if (stackTraceOption == IncludeStackTraceOption.COMPLETE + || (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE + && declaringSource == null)) { callStack = new Throwable().getStackTrace(); } if (stackTraceOption == IncludeStackTraceOption.COMPLETE) { @@ -528,10 +639,20 @@ public final class Elements { } if (declaringSource == null) { // So 'source' and 'originalSource' are null otherwise declaringSource has some value - if (stackTraceOption == IncludeStackTraceOption.COMPLETE || - stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) { + if (stackTraceOption == IncludeStackTraceOption.COMPLETE + || stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) { // 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) // As neither 'declaring source' nor 'call stack' is available use 'module source' declaringSource = sourceProvider.getFromClassNames(moduleSource.getModuleClassNames()); @@ -539,13 +660,18 @@ public final class Elements { } // Build the binding call stack 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 - * also removes the last two elements in order to make {@link #install(Module)} the last call - * in the call stack. + * also removes the last two elements in order to make {@link #install(Module)} the last call in + * the call stack. */ private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) { int toSkip = 0; @@ -560,6 +686,11 @@ public final class Elements { return partialCallStack; } + /** Returns if the binder is in the module scanning phase. */ + private boolean moduleScanning() { + return currentScanner != null; + } + @Override public String toString() { return "Binder"; diff --git a/src/main/java/com/google/inject/spi/ErrorDetail.java b/src/main/java/com/google/inject/spi/ErrorDetail.java new file mode 100644 index 0000000..48657f6 --- /dev/null +++ b/src/main/java/com/google/inject/spi/ErrorDetail.java @@ -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. + * + *

WARNING: The class and its APIs are still experimental and subject to change. + * + * @since 5.0 + */ +public abstract class ErrorDetail> implements Serializable { + private final String message; + private final ImmutableList sources; + private final Throwable cause; + + protected ErrorDetail(String message, List 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. + * + *

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. + * + *

{@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. + * + *

Formatted error has the following structure: + * + *

    + *
  • Summary of the error + *
  • Details about the error such as the source of the error + *
  • Hints for fixing the error if available + *
  • 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> 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 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}. + * + *

    {@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> mergeableErrors, Formatter formatter); + + /** + * Returns an optional link to additional documentation about this error to be included in the + * formatted error message. + */ + protected Optional getLearnMoreLink() { + return Optional.empty(); + } + + /** Returns an optional string identifier for this error. */ + protected Optional getErrorIdentifier() { + return Optional.empty(); + } + + public String getMessage() { + return message; + } + + public List 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 newSources); +} diff --git a/src/main/java/com/google/inject/spi/InjectionPoint.java b/src/main/java/com/google/inject/spi/InjectionPoint.java index e577e96..53f0eb3 100644 --- a/src/main/java/com/google/inject/spi/InjectionPoint.java +++ b/src/main/java/com/google/inject/spi/InjectionPoint.java @@ -1,7 +1,10 @@ package com.google.inject.spi; +import static com.google.inject.internal.MoreTypes.getRawType; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.inject.ConfigurationException; import com.google.inject.Inject; @@ -11,27 +14,27 @@ import com.google.inject.internal.Annotations; import com.google.inject.internal.DeclaredMembers; import com.google.inject.internal.Errors; import com.google.inject.internal.ErrorsException; -import com.google.inject.internal.Nullability; import com.google.inject.internal.util.Classes; - import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; - -import static com.google.inject.internal.MoreTypes.getRawType; +import java.util.stream.Collectors; /** * A constructor, field or method that can receive injections. Typically this is a member with the @@ -51,15 +54,30 @@ public final class InjectionPoint { this.member = method; this.declaringType = declaringType; this.optional = optional; - this.dependencies = forMember(method, declaringType, method.getParameterAnnotations()); + this.dependencies = + forMember( + new Errors(method), + method, + declaringType, + method.getAnnotatedParameterTypes(), + method.getParameterAnnotations(), + integer-> false); } InjectionPoint(TypeLiteral declaringType, Constructor constructor) { this.member = constructor; this.declaringType = declaringType; this.optional = false; - this.dependencies = forMember( - constructor, declaringType, constructor.getParameterAnnotations()); + Errors errors = new Errors(constructor); + + this.dependencies = + forMember( + errors, + constructor, + declaringType, + constructor.getAnnotatedParameterTypes(), + constructor.getParameterAnnotations(), + integer -> false); } InjectionPoint(TypeLiteral declaringType, Field field, boolean optional) { @@ -67,8 +85,8 @@ public final class InjectionPoint { this.declaringType = declaringType; this.optional = optional; - Annotation[] annotations = field.getAnnotations(); - + Annotation[] annotations = getAnnotations(field); + Annotation[] typeUseAnnotations = field.getAnnotatedType().getAnnotations(); Errors errors = new Errors(field); Key key = null; try { @@ -79,9 +97,104 @@ public final class InjectionPoint { errors.merge(e.getErrors()); } errors.throwConfigurationExceptionIfErrorsExist(); + this.dependencies = ImmutableList.>of(newDependency(key, false, -1)); + } - this.dependencies = ImmutableList.of( - newDependency(key, Nullability.allowsNull(annotations), -1)); + private ImmutableList> forMember( + Errors errors, + Member member, + TypeLiteral type, + AnnotatedType[] annotatedTypes, + Annotation[][] parameterAnnotationsPerParameter, + Predicate isParameterKotlinNullable) { + List> dependencies = Lists.newArrayList(); + int index = 0; + + for (TypeLiteral parameterType : type.getParameterTypes(member)) { + try { + Annotation[] typeAnnotations = annotatedTypes[index].getAnnotations(); + Annotation[] parameterAnnotations = parameterAnnotationsPerParameter[index]; + Key key = Annotations.getKey(parameterType, member, parameterAnnotations, errors); + dependencies.add(newDependency(key, false, index)); + index++; + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + errors.throwConfigurationExceptionIfErrorsExist(); + return ImmutableList.copyOf(dependencies); + } + + // This metohd is necessary to create a Dependency with proper generic type information + private Dependency newDependency(Key key, boolean allowsNull, int parameterIndex) { + return new Dependency(this, key, allowsNull, parameterIndex); + } + + /** Returns the injected constructor, field, or method. */ + public Member getMember() { + // TODO: Don't expose the original member (which probably has setAccessible(true)). + return member; + } + + /** + * Returns the dependencies for this injection point. If the injection point is for a method or + * constructor, the dependencies will correspond to that member's parameters. Field injection + * points always have a single dependency for the field itself. + * + * @return a possibly-empty list + */ + public List> getDependencies() { + return dependencies; + } + + /** + * Returns true if this injection point shall be skipped if the injector cannot resolve bindings + * for all required dependencies. Both explicit bindings (as specified in a module), and implicit + * bindings ({@literal @}{@link com.google.inject.ImplementedBy ImplementedBy}, default + * constructors etc.) may be used to satisfy optional injection points. + */ + public boolean isOptional() { + return optional; + } + + /** + * Returns true if the element is annotated with {@literal @}{@link Toolable}. + * + * @since 3.0 + */ + public boolean isToolable() { + return ((AnnotatedElement) member).isAnnotationPresent(Toolable.class); + } + + /** + * Returns the generic type that defines this injection point. If the member exists on a + * parameterized type, the result will include more type information than the member's {@link + * Member#getDeclaringClass() raw declaring class}. + * + * @since 3.0 + */ + public TypeLiteral getDeclaringType() { + return declaringType; + } + + @Override + public boolean equals(Object o) { + return o instanceof InjectionPoint + && member.equals(((InjectionPoint) o).member) + && declaringType.equals(((InjectionPoint) o).declaringType); + } + + @Override + public int hashCode() { + return member.hashCode() ^ declaringType.hashCode(); + } + + @Override + public String toString() { + return Classes.toString(member); } /** @@ -100,9 +213,11 @@ public final class InjectionPoint { * Returns a new injection point for the specified constructor of {@code type}. * * @param constructor any single constructor present on {@code type}. - * @param type the concrete type that defines {@code constructor}. + * @param type the concrete type that defines {@code constructor}. + * @since 3.0 */ - public static InjectionPoint forConstructor(Constructor constructor, TypeLiteral type) { + public static InjectionPoint forConstructor( + Constructor constructor, TypeLiteral type) { if (type.getRawType() != constructor.getDeclaringClass()) { new Errors(type) .constructorNotDefinedByType(constructor, type) @@ -115,44 +230,64 @@ public final class InjectionPoint { /** * Returns a new injection point for the injectable constructor of {@code type}. * + *

    Either a {@code @Inject} annotated constructor or a non-private no arg constructor is + * required to be defined by the class corresponding to {@code type}. + * * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, - * or a no-arguments constructor that is not private. + * or a no-arguments constructor that is not private. * @throws ConfigurationException if there is no injectable constructor, more than one injectable - * constructor, or if parameters of the injectable constructor are malformed, such as - * a - * parameter with multiple binding annotations. + * constructor, or if parameters of the injectable constructor are malformed, such as a + * parameter with multiple binding annotations. */ public static InjectionPoint forConstructorOf(TypeLiteral type) { + return forConstructorOf(type, false); + } + + /** + * Returns a new injection point for the injectable constructor of {@code type}. + * + *

    If {@code atInjectRequired} is true, the constructor must be annotated with {@code @Inject}. + * If {@code atInjectRequired} is false, either a {@code @Inject} annotated constructor or a + * non-private no arg constructor is required to be defined by the class corresponding to {@code + * type}. + * + * @param type a concrete type with exactly one constructor annotated {@code @Inject}, or a + * no-arguments constructor that is not private. + * @param atInjectRequired whether the constructor must be annotated with {@code Inject}. + * @throws ConfigurationException if there is no injectable constructor, more than one injectable + * constructor, or if parameters of the injectable constructor are malformed, such as a + * parameter with multiple binding annotations. + * @since 5.0 + */ + public static InjectionPoint forConstructorOf(TypeLiteral type, boolean atInjectRequired) { Class rawType = getRawType(type.getType()); Errors errors = new Errors(rawType); + List> atInjectConstructors = + Arrays.stream(rawType.getDeclaredConstructors()) + .filter( + constructor -> + constructor.isAnnotationPresent(Inject.class) + || constructor.isAnnotationPresent(javax.inject.Inject.class)) + .collect(Collectors.toList()); + Constructor injectableConstructor = null; - for (Constructor constructor : rawType.getDeclaredConstructors()) { - - boolean optional; - Inject guiceInject = constructor.getAnnotation(Inject.class); - if (guiceInject == null) { - javax.inject.Inject javaxInject = constructor.getAnnotation(javax.inject.Inject.class); - if (javaxInject == null) { - continue; - } - optional = false; - } else { - optional = guiceInject.optional(); - } - - if (optional) { - errors.optionalConstructor(constructor); - } + atInjectConstructors.stream() + .filter(constructor -> constructor.isAnnotationPresent(Inject.class)) + .filter(constructor -> constructor.getAnnotation(Inject.class).optional()) + .forEach(errors::optionalConstructor); + if (atInjectConstructors.size() > 1) { + errors.tooManyConstructors(rawType); + } else { + injectableConstructor = Iterables.getOnlyElement(atInjectConstructors, null); if (injectableConstructor != null) { - errors.tooManyConstructors(rawType); + checkForMisplacedBindingAnnotations(injectableConstructor, errors); } - - injectableConstructor = constructor; - checkForMisplacedBindingAnnotations(injectableConstructor, errors); } - + if (atInjectRequired && injectableConstructor == null) { + errors.atInjectRequired(type); + } errors.throwConfigurationExceptionIfErrorsExist(); if (injectableConstructor != null) { @@ -182,23 +317,22 @@ public final class InjectionPoint { * Returns a new injection point for the injectable constructor of {@code type}. * * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, - * or a no-arguments constructor that is not private. + * or a no-arguments constructor that is not private. * @throws ConfigurationException if there is no injectable constructor, more than one injectable - * constructor, or if parameters of the injectable constructor are malformed, such as - * a - * parameter with multiple binding annotations. + * constructor, or if parameters of the injectable constructor are malformed, such as a + * parameter with multiple binding annotations. */ public static InjectionPoint forConstructorOf(Class type) { return forConstructorOf(TypeLiteral.get(type)); } /** - * Returns a new injection point for the specified method of {@code type}. - * This is useful for extensions that need to build dependency graphs from - * arbitrary methods. + * Returns a new injection point for the specified method of {@code type}. This is useful for + * extensions that need to build dependency graphs from arbitrary methods. * * @param method any single method present on {@code type}. - * @param type the concrete type that defines {@code method}. + * @param type the concrete type that defines {@code method}. + * @since 4.0 */ public static InjectionPoint forMethod(Method method, TypeLiteral type) { return new InjectionPoint(type, method, false); @@ -208,13 +342,12 @@ public final class InjectionPoint { * Returns all static method and field injection points on {@code type}. * * @return a possibly empty set of injection points. The set has a specified iteration order. All - * fields are returned and then all methods. Within the fields, supertype fields are returned - * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as - * a field with multiple binding annotations. The exception's {@link - * ConfigurationException#getPartialValue() partial value} is a {@code - * Set} - * of the valid injection points. + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} of + * the valid injection points. */ public static Set forStaticMethodsAndFields(TypeLiteral type) { Errors errors = new Errors(); @@ -238,13 +371,12 @@ public final class InjectionPoint { * Returns all static method and field injection points on {@code type}. * * @return a possibly empty set of injection points. The set has a specified iteration order. All - * fields are returned and then all methods. Within the fields, supertype fields are returned - * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as - * a field with multiple binding annotations. The exception's {@link - * ConfigurationException#getPartialValue() partial value} is a {@code - * Set} - * of the valid injection points. + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} of + * the valid injection points. */ public static Set forStaticMethodsAndFields(Class type) { return forStaticMethodsAndFields(TypeLiteral.get(type)); @@ -254,13 +386,12 @@ public final class InjectionPoint { * Returns all instance method and field injection points on {@code type}. * * @return a possibly empty set of injection points. The set has a specified iteration order. All - * fields are returned and then all methods. Within the fields, supertype fields are returned - * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as - * a field with multiple binding annotations. The exception's {@link - * ConfigurationException#getPartialValue() partial value} is a {@code - * Set} - * of the valid injection points. + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} of + * the valid injection points. */ public static Set forInstanceMethodsAndFields(TypeLiteral type) { Errors errors = new Errors(); @@ -275,24 +406,22 @@ public final class InjectionPoint { * Returns all instance method and field injection points on {@code type}. * * @return a possibly empty set of injection points. The set has a specified iteration order. All - * fields are returned and then all methods. Within the fields, supertype fields are returned - * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as - * a field with multiple binding annotations. The exception's {@link - * ConfigurationException#getPartialValue() partial value} is a {@code - * Set} - * of the valid injection points. + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} of + * the valid injection points. */ public static Set forInstanceMethodsAndFields(Class type) { return forInstanceMethodsAndFields(TypeLiteral.get(type)); } - /** - * Returns true if the binding annotation is in the wrong place. - */ + /** Returns true if the binding annotation is in the wrong place. */ private static boolean checkForMisplacedBindingAnnotations(Member member, Errors errors) { - Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation( - errors, member, ((AnnotatedElement) member).getAnnotations()); + Annotation misplacedBindingAnnotation = + Annotations.findBindingAnnotation( + errors, member, ((AnnotatedElement) member).getAnnotations()); if (misplacedBindingAnnotation == null) { return false; } @@ -312,21 +441,230 @@ public final class InjectionPoint { return true; } + /** Node in the doubly-linked list of injectable members (fields and methods). */ + abstract static class InjectableMember { + final TypeLiteral declaringType; + final boolean optional; + final boolean jsr330; + InjectableMember previous; + InjectableMember next; + + InjectableMember(TypeLiteral declaringType, Annotation atInject) { + this.declaringType = declaringType; + + if (atInject.annotationType() == javax.inject.Inject.class) { + optional = false; + jsr330 = true; + return; + } + + jsr330 = false; + optional = ((Inject) atInject).optional(); + } + + abstract InjectionPoint toInjectionPoint(); + } + + static class InjectableField extends InjectableMember { + final Field field; + + InjectableField(TypeLiteral declaringType, Field field, Annotation atInject) { + super(declaringType, atInject); + this.field = field; + } + + @Override + InjectionPoint toInjectionPoint() { + return new InjectionPoint(declaringType, field, optional); + } + } + + static class InjectableMethod extends InjectableMember { + final Method method; + /** + * true if this method overrode a method that was annotated with com.google.inject.Inject. used + * to allow different override behavior for guice inject vs javax.inject.Inject + */ + boolean overrodeGuiceInject; + + InjectableMethod(TypeLiteral declaringType, Method method, Annotation atInject) { + super(declaringType, atInject); + this.method = method; + } + + @Override + InjectionPoint toInjectionPoint() { + return new InjectionPoint(declaringType, method, optional); + } + + public boolean isFinal() { + return Modifier.isFinal(method.getModifiers()); + } + } + static Annotation getAtInject(AnnotatedElement member) { Annotation a = member.getAnnotation(javax.inject.Inject.class); return a == null ? member.getAnnotation(Inject.class) : a; } + /** Linked list of injectable members. */ + static class InjectableMembers { + InjectableMember head; + InjectableMember tail; + + void add(InjectableMember member) { + if (head == null) { + head = tail = member; + } else { + member.previous = tail; + tail.next = member; + tail = member; + } + } + + void remove(InjectableMember member) { + if (member.previous != null) { + member.previous.next = member.next; + } + if (member.next != null) { + member.next.previous = member.previous; + } + if (head == member) { + head = member.next; + } + if (tail == member) { + tail = member.previous; + } + } + + boolean isEmpty() { + return head == null; + } + } + + /** Position in type hierarchy. */ + enum Position { + TOP, // No need to check for overridden methods + MIDDLE, + BOTTOM // Methods won't be overridden + } + + /** + * Keeps track of injectable methods so we can remove methods that get overridden in O(1) time. + * Uses our position in the type hierarchy to perform optimizations. + */ + static class OverrideIndex { + final InjectableMembers injectableMembers; + Map> bySignature; + Position position = Position.TOP; + + OverrideIndex(InjectableMembers injectableMembers) { + this.injectableMembers = injectableMembers; + } + + /* Caches the signature for the last method. */ + Method lastMethod; + Signature lastSignature; + + /** + * Removes a method overridden by the given method, if present. In order to remain backwards + * compatible with prior Guice versions, this will *not* remove overridden methods if + * 'alwaysRemove' is false and the overridden signature was annotated with a + * com.google.inject.Inject. + * + * @param method The method used to determine what is overridden and should be removed. + * @param alwaysRemove true if overridden methods should be removed even if they were + * guice @Inject + * @param injectableMethod if this method overrode any guice @Inject methods, {@link + * InjectableMethod#overrodeGuiceInject} is set to true + */ + boolean removeIfOverriddenBy( + Method method, boolean alwaysRemove, InjectableMethod injectableMethod) { + if (position == Position.TOP) { + // If we're at the top of the hierarchy, there's nothing to override. + return false; + } + + if (bySignature == null) { + // We encountered a method in a subclass. Time to index the + // methods in the parent class. + bySignature = new HashMap<>(); + for (InjectableMember member = injectableMembers.head; + member != null; + member = member.next) { + if (!(member instanceof InjectableMethod)) { + continue; + } + InjectableMethod im = (InjectableMethod) member; + if (im.isFinal()) { + continue; + } + List methods = new ArrayList<>(); + methods.add(im); + bySignature.put(new Signature(im.method), methods); + } + } + + lastMethod = method; + Signature signature = lastSignature = new Signature(method); + List methods = bySignature.get(signature); + boolean removed = false; + if (methods != null) { + for (Iterator iterator = methods.iterator(); iterator.hasNext(); ) { + InjectableMethod possiblyOverridden = iterator.next(); + if (overrides(method, possiblyOverridden.method)) { + boolean wasGuiceInject = + !possiblyOverridden.jsr330 || possiblyOverridden.overrodeGuiceInject; + if (injectableMethod != null) { + injectableMethod.overrodeGuiceInject = wasGuiceInject; + } + // Only actually remove the methods if we want to force + // remove or if the signature never specified @com.google.inject.Inject + // somewhere. + if (alwaysRemove || !wasGuiceInject) { + removed = true; + iterator.remove(); + injectableMembers.remove(possiblyOverridden); + } + } + } + } + return removed; + } + + /** + * Adds the given method to the list of injection points. Keeps track of it in this index in + * case it gets overridden. + */ + void add(InjectableMethod injectableMethod) { + injectableMembers.add(injectableMethod); + if (position == Position.BOTTOM || injectableMethod.isFinal()) { + // This method can't be overridden, so there's no need to index it. + return; + } + if (bySignature != null) { + // Try to reuse the signature we created during removal + @SuppressWarnings("ReferenceEquality") + Signature signature = + injectableMethod.method == lastMethod + ? lastSignature + : new Signature(injectableMethod.method); + bySignature.computeIfAbsent(signature, k -> new ArrayList<>()).add(injectableMethod); + } + } + } + /** * Returns an ordered, immutable set of injection points for the given type. Members in * superclasses come before members in subclasses. Within a class, fields come before methods. - * Overridden methods are filtered out. + * Overridden methods are filtered out. The order of fields/methods within a class is consistent + * but undefined. * * @param statics true is this method should return static members, false for instance members - * @param errors used to record errors + * @param errors used to record errors */ - private static Set getInjectionPoints(final TypeLiteral type, - boolean statics, Errors errors) { + private static Set getInjectionPoints( + final TypeLiteral type, boolean statics, Errors errors) { InjectableMembers injectableMembers = new InjectableMembers(); OverrideIndex overrideIndex = null; @@ -362,18 +700,20 @@ public final class InjectionPoint { Annotation atInject = getAtInject(method); if (atInject != null) { InjectableMethod injectableMethod = new InjectableMethod(current, method, atInject); - if (checkForMisplacedBindingAnnotations(method, errors) || - !isValidMethod(injectableMethod, errors)) { + if (checkForMisplacedBindingAnnotations(method, errors) + || !isValidMethod(injectableMethod, errors)) { if (overrideIndex != null) { - boolean removed = overrideIndex.removeIfOverriddenBy(method, false, injectableMethod); + boolean removed = + overrideIndex.removeIfOverriddenBy(method, false, injectableMethod); if (removed) { logger.log( Level.WARNING, - "Method " + method + " is not a valid injectable method (" + "Method: {0} is not a valid injectable method (" + "because it either has misplaced binding annotations " + "or specifies type parameters) but is overriding a method that is " + "valid. Because it is not valid, the method will not be injected. " - + "To fix this, make the method a valid injectable method."); + + "To fix this, make the method a valid injectable method.", + method); } } continue; @@ -390,7 +730,7 @@ public final class InjectionPoint { */ overrideIndex = new OverrideIndex(injectableMembers); } else { - // Forcibly remove the overriden method, otherwise we'll inject + // Forcibly remove the overridden method, otherwise we'll inject // it twice. overrideIndex.removeIfOverriddenBy(method, true, injectableMethod); } @@ -398,7 +738,16 @@ public final class InjectionPoint { } } else { if (overrideIndex != null) { - overrideIndex.removeIfOverriddenBy(method, false, null); + boolean removed = overrideIndex.removeIfOverriddenBy(method, false, null); + if (removed) { + logger.log( + Level.WARNING, + "Method: {0} is not annotated with @Inject but " + + "is overriding a method that is annotated with @javax.inject.Inject." + + "Because it is not annotated with @Inject, the method will not be " + + "injected. To fix this, annotate the method with @Inject.", + method); + } } } } @@ -431,20 +780,20 @@ public final class InjectionPoint { } /** - * Returns true if the method is eligible to be injected. This is different than - * {@link #isValidMethod}, because ineligibility will not drop a method - * from being injected if a superclass was eligible & valid. - * Bridge & synthetic methods are excluded from eligibility for two reasons: + * Returns true if the method is eligible to be injected. This is different than {@link + * #isValidMethod}, because ineligibility will not drop a method from being injected if a + * superclass was eligible & valid. Bridge & synthetic methods are excluded from eligibility for + * two reasons: * - *

    Prior to Java8, javac would generate these methods in subclasses without - * annotations, which means this would accidentally stop injecting a method - * annotated with {@link javax.inject.Inject}, since the spec says to stop - * injecting if a subclass isn't annotated with it. + *

    Prior to Java8, javac would generate these methods in subclasses without annotations, which + * means this would accidentally stop injecting a method annotated with {@link + * javax.inject.Inject}, since the spec says to stop injecting if a subclass isn't annotated with + * it. * - *

    Starting at Java8, javac copies the annotations to the generated subclass - * method, except it leaves out the generic types. If this considered it a valid - * injectable method, this would eject the parent's overridden method that had the - * proper generic types, and would use invalid injectable parameters as a result. + *

    Starting at Java8, javac copies the annotations to the generated subclass method, except it + * leaves out the generic types. If this considered it a valid injectable method, this would eject + * the parent's overridden method that had the proper generic types, and would use invalid + * injectable parameters as a result. * *

    The fix for both is simply to ignore these synthetic bridge methods. */ @@ -497,317 +846,16 @@ public final class InjectionPoint { return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage()); } - - private ImmutableList> forMember(Member member, TypeLiteral type, - Annotation[][] paramterAnnotations) { - Errors errors = new Errors(member); - - List> dependencies = Lists.newArrayList(); - int index = 0; - - for (TypeLiteral parameterType : type.getParameterTypes(member)) { - try { - Annotation[] parameterAnnotations = paramterAnnotations[index]; - Key key = Annotations.getKey(parameterType, member, parameterAnnotations, errors); - dependencies.add(newDependency(key, Nullability.allowsNull(parameterAnnotations), index)); - index++; - } catch (ConfigurationException e) { - errors.merge(e.getErrorMessages()); - } catch (ErrorsException e) { - errors.merge(e.getErrors()); - } - } - - errors.throwConfigurationExceptionIfErrorsExist(); - return ImmutableList.copyOf(dependencies); - } - - // This method is necessary to create a Dependency with proper generic type information - private Dependency newDependency(Key key, boolean allowsNull, int parameterIndex) { - return new Dependency<>(this, key, allowsNull, parameterIndex); - } - /** - * Returns the injected constructor, field, or method. - */ - public Member getMember() { - // TODO: Don't expose the original member (which probably has setAccessible(true)). - return member; - } - - /** - * Returns the dependencies for this injection point. If the injection point is for a method or - * constructor, the dependencies will correspond to that member's parameters. Field injection - * points always have a single dependency for the field itself. + * Returns all the annotations on a field. If Kotlin-support is enabled, the annotations will + * include annotations on the related Kotlin-property. * - * @return a possibly-empty list */ - public List> getDependencies() { - return dependencies; + public static Annotation[] getAnnotations(Field field) { + return field.getAnnotations(); } - /** - * Returns true if this injection point shall be skipped if the injector cannot resolve bindings - * for all required dependencies. Both explicit bindings (as specified in a module), and implicit - * bindings ({@literal @}{@link com.google.inject.ImplementedBy ImplementedBy}, default - * constructors etc.) may be used to satisfy optional injection points. - */ - public boolean isOptional() { - return optional; - } - - /** - * Returns true if the element is annotated with {@literal @}{@link Toolable}. - */ - public boolean isToolable() { - return ((AnnotatedElement) member).isAnnotationPresent(Toolable.class); - } - - /** - * Returns the generic type that defines this injection point. If the member exists on a - * parameterized type, the result will include more type information than the member's {@link - * Member#getDeclaringClass() raw declaring class}. - */ - public TypeLiteral getDeclaringType() { - return declaringType; - } - - @Override - public boolean equals(Object o) { - return o instanceof InjectionPoint - && member.equals(((InjectionPoint) o).member) - && declaringType.equals(((InjectionPoint) o).declaringType); - } - - @Override - public int hashCode() { - return member.hashCode() ^ declaringType.hashCode(); - } - - @Override - public String toString() { - return Classes.toString(member); - } - - /** - * Position in type hierarchy. - */ - enum Position { - TOP, // No need to check for overridden methods - MIDDLE, - BOTTOM // Methods won't be overridden - } - - /** - * Node in the doubly-linked list of injectable members (fields and methods). - */ - static abstract class InjectableMember { - final TypeLiteral declaringType; - final boolean optional; - final boolean jsr330; - InjectableMember previous; - InjectableMember next; - - InjectableMember(TypeLiteral declaringType, Annotation atInject) { - this.declaringType = declaringType; - - if (atInject.annotationType() == javax.inject.Inject.class) { - optional = false; - jsr330 = true; - return; - } - - jsr330 = false; - optional = ((Inject) atInject).optional(); - } - - abstract InjectionPoint toInjectionPoint(); - } - - static class InjectableField extends InjectableMember { - final Field field; - - InjectableField(TypeLiteral declaringType, Field field, - Annotation atInject) { - super(declaringType, atInject); - this.field = field; - } - - @Override - InjectionPoint toInjectionPoint() { - return new InjectionPoint(declaringType, field, optional); - } - } - - static class InjectableMethod extends InjectableMember { - final Method method; - /** - * true if this method overrode a method that was annotated - * with com.google.inject.Inject. used to allow different - * override behavior for guice inject vs javax.inject.Inject - */ - boolean overrodeGuiceInject; - - InjectableMethod(TypeLiteral declaringType, Method method, - Annotation atInject) { - super(declaringType, atInject); - this.method = method; - } - - @Override - InjectionPoint toInjectionPoint() { - return new InjectionPoint(declaringType, method, optional); - } - - public boolean isFinal() { - return Modifier.isFinal(method.getModifiers()); - } - } - - /** - * Linked list of injectable members. - */ - static class InjectableMembers { - InjectableMember head; - InjectableMember tail; - - void add(InjectableMember member) { - if (head == null) { - head = tail = member; - } else { - member.previous = tail; - tail.next = member; - tail = member; - } - } - - void remove(InjectableMember member) { - if (member.previous != null) { - member.previous.next = member.next; - } - if (member.next != null) { - member.next.previous = member.previous; - } - if (head == member) { - head = member.next; - } - if (tail == member) { - tail = member.previous; - } - } - - boolean isEmpty() { - return head == null; - } - } - - /** - * Keeps track of injectable methods so we can remove methods that get overridden in O(1) time. - * Uses our position in the type hierarchy to perform optimizations. - */ - static class OverrideIndex { - final InjectableMembers injectableMembers; - Map> bySignature; - Position position = Position.TOP; - /* Caches the signature for the last method. */ - Method lastMethod; - Signature lastSignature; - OverrideIndex(InjectableMembers injectableMembers) { - this.injectableMembers = injectableMembers; - } - - /** - * Removes a method overridden by the given method, if present. In order to - * remain backwards compatible with prior Guice versions, this will *not* - * remove overridden methods if 'alwaysRemove' is false and the overridden - * signature was annotated with a com.google.inject.Inject. - * - * @param method The method used to determine what is overridden and should be - * removed. - * @param alwaysRemove true if overridden methods should be removed even if they were - * guice @Inject - * @param injectableMethod if this method overrode any guice @Inject methods, - * {@link InjectableMethod#overrodeGuiceInject} is set to true - */ - boolean removeIfOverriddenBy(Method method, boolean alwaysRemove, - InjectableMethod injectableMethod) { - if (position == Position.TOP) { - // If we're at the top of the hierarchy, there's nothing to override. - return false; - } - - if (bySignature == null) { - // We encountered a method in a subclass. Time to index the - // methods in the parent class. - bySignature = new HashMap<>(); - for (InjectableMember member = injectableMembers.head; member != null; - member = member.next) { - if (!(member instanceof InjectableMethod)) { - continue; - } - InjectableMethod im = (InjectableMethod) member; - if (im.isFinal()) { - continue; - } - List methods = new ArrayList<>(); - methods.add(im); - bySignature.put(new Signature(im.method), methods); - } - } - - lastMethod = method; - Signature signature = lastSignature = new Signature(method); - List methods = bySignature.get(signature); - boolean removed = false; - if (methods != null) { - for (Iterator iterator = methods.iterator(); - iterator.hasNext(); ) { - InjectableMethod possiblyOverridden = iterator.next(); - if (overrides(method, possiblyOverridden.method)) { - boolean wasGuiceInject = - !possiblyOverridden.jsr330 || possiblyOverridden.overrodeGuiceInject; - if (injectableMethod != null) { - injectableMethod.overrodeGuiceInject = wasGuiceInject; - } - // Only actually remove the methods if we want to force - // remove or if the signature never specified @com.google.inject.Inject - // somewhere. - if (alwaysRemove || !wasGuiceInject) { - removed = true; - iterator.remove(); - injectableMembers.remove(possiblyOverridden); - } - } - } - } - return removed; - } - - /** - * Adds the given method to the list of injection points. Keeps track of it in this index - * in case it gets overridden. - */ - void add(InjectableMethod injectableMethod) { - injectableMembers.add(injectableMethod); - if (position == Position.BOTTOM - || injectableMethod.isFinal()) { - // This method can't be overridden, so there's no need to index it. - return; - } - if (bySignature != null) { - // Try to reuse the signature we created during removal - Signature signature = injectableMethod.method == lastMethod - ? lastSignature : new Signature(injectableMethod.method); - List methods = - bySignature.computeIfAbsent(signature, k -> new ArrayList<>()); - methods.add(injectableMethod); - } - } - } - - /** - * A method signature. Used to handle method overridding. - */ + /** A method signature. Used to handle method overridding. */ static class Signature { final String name; diff --git a/src/main/java/com/google/inject/spi/Message.java b/src/main/java/com/google/inject/spi/Message.java index 5e8018a..b4fce21 100644 --- a/src/main/java/com/google/inject/spi/Message.java +++ b/src/main/java/com/google/inject/spi/Message.java @@ -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; -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 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 * Guice and its extensions. Messages can be created explicitly in a module using {@link * com.google.inject.Binder#addError(Throwable) addError()} statements: + * *

      *     try {
      *       bindPropertiesFromFile();
    @@ -21,21 +42,36 @@ import static com.google.common.base.Preconditions.checkNotNull;
      *       addError(e);
      *     }
    * + * @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; - - private final Throwable cause; - - private final List sources; - - public Message(List sources, String message, Throwable cause) { - this.sources = ImmutableList.copyOf(sources); - this.message = checkNotNull(message, "message"); - this.cause = cause; + /** @since 5.0 */ + public Message(GuiceInternal internalOnly, ErrorId errorId, ErrorDetail errorDetail) { + checkNotNull(internalOnly); + this.errorId = errorId; + this.errorDetail = errorDetail; } + private Message(ErrorId errorId, ErrorDetail errorDetail) { + this.errorId = errorId; + this.errorDetail = errorDetail; + } + + /** @since 2.0 */ + public Message(ErrorId errorId, List sources, String message, Throwable cause) { + this.errorId = errorId; + this.errorDetail = new GenericErrorDetail(errorId, message, sources, cause); + } + + /** @since 2.0 */ + public Message(List sources, String message, Throwable cause) { + this(ErrorId.OTHER, sources, message, cause); + } + + /** @since 4.0 */ public Message(String message, Throwable cause) { this(ImmutableList.of(), message, cause); } @@ -48,45 +84,57 @@ public final class Message implements Element { this(ImmutableList.of(), message, null); } + /** + * Returns details about this error message. + * + * @since 5.0 + */ + public ErrorDetail getErrorDetail() { + return errorDetail; + } + @Override public String getSource() { + List sources = errorDetail.getSources(); return sources.isEmpty() ? SourceProvider.UNKNOWN_SOURCE.toString() - : Messages.convert(sources.get(sources.size() - 1)).toString(); + : Errors.convert(Iterables.getLast(sources)).toString(); } + /** @since 2.0 */ public List getSources() { - return sources; + return errorDetail.getSources(); } - /** - * Gets the error message text. - */ + /** Gets the error message text. */ public String getMessage() { - return message; + return errorDetail.getMessage(); } + /** @since 2.0 */ @Override public T acceptVisitor(ElementVisitor visitor) { return visitor.visit(this); } /** - * Returns the throwable that caused this message, or {@code null} if this - * message was not caused by a throwable. + * Returns the throwable that caused this message, or {@code null} if this message was not caused + * by a throwable. + * + * @since 2.0 */ public Throwable getCause() { - return cause; + return errorDetail.getCause(); } @Override public String toString() { - return message; + return errorDetail.getMessage(); } @Override public int hashCode() { - return Objects.hashCode(message, cause, sources); + return errorDetail.hashCode(); } @Override @@ -95,12 +143,39 @@ public final class Message implements Element { return false; } 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 public void applyTo(Binder binder) { binder.withSource(getSource()).addError(this); } + /** + * Returns a copy of this {@link Message} with its sources replaced. + * + * @since 5.0 + */ + public Message withSource(List 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; } diff --git a/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java index b6b7ae4..a963299 100644 --- a/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java +++ b/src/main/java/com/google/inject/spi/ModuleAnnotatedMethodScannerBinding.java @@ -1,10 +1,9 @@ package com.google.inject.spi; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.inject.Binder; 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. @@ -18,6 +17,7 @@ public final class ModuleAnnotatedMethodScannerBinding implements Element { this.scanner = checkNotNull(scanner, "scanner"); } + @Override public Object getSource() { return source; } @@ -26,17 +26,23 @@ public final class ModuleAnnotatedMethodScannerBinding implements Element { return scanner; } + @Override public T acceptVisitor(ElementVisitor visitor) { return visitor.visit(this); } + @Override public void applyTo(Binder binder) { binder.withSource(getSource()).scanModulesForAnnotatedMethods(scanner); } @Override public String toString() { - return scanner + " which scans for " + scanner.annotationClasses() - + " (bound at " + Messages.convert(source) + ")"; + return scanner + + " which scans for " + + scanner.annotationClasses() + + " (bound at " + + Errors.convert(source) + + ")"; } } diff --git a/src/main/java/com/google/inject/spi/ModuleSource.java b/src/main/java/com/google/inject/spi/ModuleSource.java index 9ca8a77..2e2a886 100644 --- a/src/main/java/com/google/inject/spi/ModuleSource.java +++ b/src/main/java/com/google/inject/spi/ModuleSource.java @@ -2,30 +2,33 @@ package com.google.inject.spi; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.inject.Binder; import com.google.inject.Module; import com.google.inject.internal.util.StackTraceElements; import com.google.inject.internal.util.StackTraceElements.InMemoryStackTraceElement; - import java.util.List; /** * 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 - * Module#configure(Binder)} method invocation. + * Module#configure(Binder) configure(Binder)} method invocation. */ 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; - /** - * The parent {@link ModuleSource module source}. - */ + /** The parent {@link ModuleSource module source}. */ private final ModuleSource parent; + /** + * Permit map created by the binder that installed this module. + * + *

    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) * 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. * - * @param module the corresponding module + * @param moduleClass the corresponding module * @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)} method invocation + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation */ - ModuleSource(Object module, StackTraceElement[] partialCallStack) { - this(null, module, partialCallStack); + ModuleSource( + Class moduleClass, + StackTraceElement[] partialCallStack, + BindingSourceRestriction.PermitMap permitMap) { + this(null, moduleClass, partialCallStack, permitMap); } /** * Creates a new {@link ModuleSource} Object. * - * @param parent the parent module {@link ModuleSource source} - * @param module the corresponding module + * @param parent the parent module {@link ModuleSource source} + * @param moduleClass the corresponding module * @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)} method invocation + * Module#configure(Binder) configure(Binder)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation */ private ModuleSource( - /* @Nullable */ ModuleSource parent, Object module, StackTraceElement[] partialCallStack) { - Preconditions.checkNotNull(module, "module cannot be null."); + ModuleSource parent, + Class moduleClass, + StackTraceElement[] partialCallStack, + BindingSourceRestriction.PermitMap permitMap) { + Preconditions.checkNotNull(moduleClass, "module cannot be null."); Preconditions.checkNotNull(partialCallStack, "partialCallStack cannot be null."); this.parent = parent; - this.moduleClassName = module.getClass().getName(); + this.moduleClassName = moduleClass.getName(); this.partialCallStack = StackTraceElements.convertToInMemoryStackTraceElement(partialCallStack); + this.permitMap = permitMap; } /** @@ -83,9 +93,7 @@ final class ModuleSource { 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() { return partialCallStack.length; } @@ -93,18 +101,16 @@ final class ModuleSource { /** * 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 - * 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)} call and ends just before the module {@link + * Module#configure(Binder) configure(Binder)} method invocation */ - ModuleSource createChild(Object module, StackTraceElement[] partialCallStack) { - return new ModuleSource(this, module, partialCallStack); + ModuleSource createChild(Class moduleClass, StackTraceElement[] partialCallStack) { + return new ModuleSource(this, moduleClass, partialCallStack, permitMap); } - /** - * Returns the parent module {@link ModuleSource source}. - */ + /** Returns the parent module {@link ModuleSource source}. */ ModuleSource getParent() { return parent; } @@ -167,4 +173,9 @@ final class ModuleSource { } return callStack; } + + /** Returns the permit map created by the binder that installed this module. */ + BindingSourceRestriction.PermitMap getPermitMap() { + return permitMap; + } } diff --git a/src/main/java/com/google/inject/spi/TypeConverterBinding.java b/src/main/java/com/google/inject/spi/TypeConverterBinding.java index d05caa4..09967c6 100644 --- a/src/main/java/com/google/inject/spi/TypeConverterBinding.java +++ b/src/main/java/com/google/inject/spi/TypeConverterBinding.java @@ -1,18 +1,20 @@ 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 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 - * explicitly in a module using {@link com.google.inject.Binder#convertToTypes(Matcher, - * TypeConverter) convertToTypes()} statements: + * Registration of type converters for matching target types. Instances are created explicitly in a + * module using {@link com.google.inject.Binder#convertToTypes(Matcher, TypeConverter) + * convertToTypes()} statements: + * *

    - *     convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());
    + * convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter()); + * * */ public final class TypeConverterBinding implements Element { @@ -20,13 +22,15 @@ public final class TypeConverterBinding implements Element { private final Matcher> typeMatcher; private final TypeConverter typeConverter; - public TypeConverterBinding(Object source, Matcher> typeMatcher, - TypeConverter typeConverter) { + /** @since 3.0 */ + public TypeConverterBinding( + Object source, Matcher> typeMatcher, TypeConverter typeConverter) { this.source = checkNotNull(source, "source"); this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher"); this.typeConverter = checkNotNull(typeConverter, "typeConverter"); } + @Override public Object getSource() { return source; } @@ -39,17 +43,23 @@ public final class TypeConverterBinding implements Element { return typeConverter; } + @Override public T acceptVisitor(ElementVisitor visitor) { return visitor.visit(this); } + @Override public void applyTo(Binder binder) { binder.withSource(getSource()).convertToTypes(typeMatcher, typeConverter); } @Override public String toString() { - return typeConverter + " which matches " + typeMatcher - + " (bound at " + Messages.convert(source) + ")"; + return typeConverter + + " which matches " + + typeMatcher + + " (bound at " + + Errors.convert(source) + + ")"; } } diff --git a/src/main/java/com/google/inject/util/Modules.java b/src/main/java/com/google/inject/util/Modules.java index 8adb794..f6a6220 100644 --- a/src/main/java/com/google/inject/util/Modules.java +++ b/src/main/java/com/google/inject/util/Modules.java @@ -9,13 +9,12 @@ import com.google.common.collect.Sets; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; -import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.PrivateBinder; import com.google.inject.PrivateModule; 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.DefaultElementVisitor; 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.PrivateElements; import com.google.inject.spi.ScopeBinding; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.LinkedHashSet; @@ -37,18 +35,23 @@ import java.util.Set; * */ public final class Modules { + private Modules() {} + 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 - * modules. If a key is bound in both sets of modules, only the binding from the override modules - * is kept. If a single {@link PrivateModule} is supplied or all elements are from - * a single {@link PrivateBinder}, then this will overwrite the private bindings. - * Otherwise, private bindings will not be overwritten unless they are exposed. - * This can be used to replace the bindings of a production module with test bindings: + * Returns a builder that creates a module that overlays override modules over the given modules. + * If a key is bound in both sets of modules, only the binding from the override modules is kept. + * If a single {@link PrivateModule} is supplied or all elements are from a single {@link + * PrivateBinder}, then this will overwrite the private bindings. Otherwise, private bindings will + * not be overwritten unless they are exposed. This can be used to replace the bindings of a + * production module with test bindings: + * *
          * Module functionalTestModule
          *     = Modules.override(new ProductionModule()).with(new TestModule());
    @@ -62,13 +65,20 @@ public final class 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
    -     * modules. If a key is bound in both sets of modules, only the binding from the override modules
    -     * is kept. If a single {@link PrivateModule} is supplied or all elements are from
    -     * a single {@link PrivateBinder}, then this will overwrite the private bindings.
    -     * Otherwise, private bindings will not be overwritten unless they are exposed.
    -     * This can be used to replace the bindings of a production module with test bindings:
    +     * Returns a builder that creates a module that overlays override modules over the given modules.
    +     * If a key is bound in both sets of modules, only the binding from the override modules is kept.
    +     * If a single {@link PrivateModule} is supplied or all elements are from a single {@link
    +     * PrivateBinder}, then this will overwrite the private bindings. Otherwise, private bindings will
    +     * not be overwritten unless they are exposed. This can be used to replace the bindings of a
    +     * production module with test bindings:
    +     *
          * 
          * Module functionalTestModule
          *     = Modules.override(getProductionModules()).with(getTestModules());
    @@ -84,61 +94,38 @@ public final class Modules {
     
         /**
          * Returns a new module that installs all of {@code modules}.
    +     *
    +     * 

    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) { 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}. + * + *

    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 modules) { return new CombinedModule(modules); } - private static Module extractScanners(Iterable elements) { - final List scanners = Lists.newArrayList(); - ElementVisitor 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 overrides); - } - - private static class EmptyModule implements Module { - public void configure(Binder binder) { - } - } - private static class CombinedModule implements Module { final Set modulesSet; @@ -146,6 +133,7 @@ public final class Modules { this.modulesSet = ImmutableSet.copyOf(modules); } + @Override public void configure(Binder binder) { binder = binder.skipSources(getClass()); 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 overrides); + } + private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder { private final ImmutableSet baseModules; + // TODO(diamondm) checkArgument(!baseModules.isEmpty())? private RealOverriddenModuleBuilder(Iterable baseModules) { this.baseModules = ImmutableSet.copyOf(baseModules); } + @Override public Module with(Module... overrides) { return with(Arrays.asList(overrides)); } + @Override + public Module with() { + return with(Arrays.asList()); + } + + @Override public Module with(Iterable overrides) { return new OverrideModule(overrides, baseModules); } @@ -174,6 +184,7 @@ public final class Modules { private final ImmutableSet overrides; private final ImmutableSet baseModules; + // TODO(diamondm) checkArgument(!overrides.isEmpty())? OverrideModule(Iterable overrides, ImmutableSet baseModules) { this.overrides = ImmutableSet.copyOf(overrides); this.baseModules = baseModules; @@ -191,7 +202,8 @@ public final class Modules { Element element = Iterables.getOnlyElement(baseElements); if (element instanceof PrivateElements) { PrivateElements privateElements = (PrivateElements) element; - PrivateBinder privateBinder = baseBinder.newPrivateBinder().withSource(privateElements.getSource()); + PrivateBinder privateBinder = + baseBinder.newPrivateBinder().withSource(privateElements.getSource()); for (Key exposed : privateElements.getExposedKeys()) { privateBinder.withSource(privateElements.getExposedSource(exposed)).expose(exposed); } @@ -203,8 +215,10 @@ public final class Modules { final Binder binder = baseBinder.skipSources(this.getClass()); final LinkedHashSet elements = new LinkedHashSet<>(baseElements); final Module scannersModule = extractScanners(elements); - final List overrideElements = Elements.getElements(currentStage(), - ImmutableList.builder().addAll(overrides).add(scannersModule).build()); + final List overrideElements = + Elements.getElements( + currentStage(), + ImmutableList.builder().addAll(overrides).add(scannersModule).build()); final Set> overriddenKeys = Sets.newHashSet(); final Map, ScopeBinding> overridesScopeAnnotations = @@ -245,7 +259,9 @@ public final class Modules { // Record when a scope instance is used in a binding Scope scope = getScopeInstanceOrNull(binding); 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> keysToSkip) { - PrivateBinder privateBinder = binder.withSource(privateElements.getSource()) - .newPrivateBinder(); + PrivateBinder privateBinder = + binder.withSource(privateElements.getSource()).newPrivateBinder(); Set> skippedExposes = Sets.newHashSet(); @@ -267,8 +283,7 @@ public final class Modules { } for (Element element : privateElements.getElements()) { - if (element instanceof Binding - && skippedExposes.remove(((Binding) element).getKey())) { + if (element instanceof Binding && skippedExposes.remove(((Binding) element).getKey())) { continue; } if (element instanceof PrivateElements) { @@ -304,13 +319,15 @@ public final class Modules { } else { List usedSources = scopeInstancesInUse.get(scopeBinding.getScope()); if (usedSources != null) { - StringBuilder sb = new StringBuilder( - "The scope for @%s is bound directly and cannot be overridden."); - sb.append("%n original binding at ").append(Messages.convert(scopeBinding.getSource())); + StringBuilder sb = + new StringBuilder( + "The scope for @%s is bound directly and cannot be overridden."); + sb.append("%n original binding at " + Errors.convert(scopeBinding.getSource())); 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()); } } @@ -320,12 +337,13 @@ public final class Modules { } private Scope getScopeInstanceOrNull(Binding binding) { - return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<>() { - @Override - public Scope visitScope(Scope scope) { - return scope; - } - }); + return binding.acceptScopingVisitor( + new DefaultBindingScopingVisitor() { + @Override + public Scope visitScope(Scope 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 elements) { + final List scanners = Lists.newArrayList(); + ElementVisitor 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()); + } + } + }; + } + + /** + * Returns a module that will configure the injector to require explicit bindings. + * + * @since 4.2.3 + */ public static Module requireExplicitBindingsModule() { return new RequireExplicitBindingsModule(); } @@ -362,8 +407,8 @@ public final class Modules { } /** - * Returns a module that will configure the injector to require - * {@link Inject} on constructors. + * Returns a module that will configure the injector to require {@literal @}{@link Inject} on + * constructors. * * @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 * annotation. * + * @since 4.2.3 * @see Binder#requireExactBindingAnnotations */ 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() { return new DisableCircularProxiesModule(); }