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 extends Annotation> 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 extends Annotation> 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 extends Annotation> annotationType) {
- return new Key(type, strategyFor(annotationType));
- }
-
- /**
- * Gets a key for an injection type and an annotation.
- */
- public static Key> get(Type type, Annotation annotation) {
- return new Key(type, strategyFor(annotation));
- }
-
- /**
- * Gets a key for an injection type.
- */
- public static 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 extends Annotation> 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 extends Annotation> annotationType = annotation.annotationType();
- ensureRetainedAtRuntime(annotationType);
- ensureIsBindingAnnotation(annotationType);
-
- if (Annotations.isMarker(annotationType)) {
- return new AnnotationTypeStrategy(annotationType, annotation);
- }
-
- return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
- }
-
- /**
- * Gets the strategy for an annotation type.
- */
- static AnnotationStrategy strategyFor(Class extends Annotation> annotationType) {
- annotationType = Annotations.canonicalizeIfNamed(annotationType);
- if (isAllDefaultMethods(annotationType)) {
- return strategyFor(generateAnnotation(annotationType));
- }
-
- checkNotNull(annotationType, "annotation type");
- ensureRetainedAtRuntime(annotationType);
- ensureIsBindingAnnotation(annotationType);
- return new AnnotationTypeStrategy(annotationType, null);
-
- }
-
- private static void ensureRetainedAtRuntime(
- Class extends Annotation> annotationType) {
- checkArgument(Annotations.isRetainedAtRuntime(annotationType),
- "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
- annotationType.getName());
- }
-
- private static void ensureIsBindingAnnotation(Class extends Annotation> annotationType) {
- checkArgument(Annotations.isBindingAnnotation(annotationType),
- "%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
- annotationType.getName());
- }
-
- /**
- * Computes the hash code for this key.
- */
+ /** 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> annotationType) {
+ return new Key<>(type, strategyFor(annotationType));
+ }
+
+ /** Gets a key for an injection type and an annotation. */
+ public static Key> get(Type type, Annotation annotation) {
+ return new Key<>(type, strategyFor(annotation));
+ }
+
+ /** Gets a key for an injection type. */
+ public static 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> getAnnotationType();
+
+ boolean hasAttributes();
+
+ AnnotationStrategy withoutAttributes();
+ }
+
+ /** Gets the strategy for an annotation. */
+ static AnnotationStrategy strategyFor(Annotation annotation) {
+ checkNotNull(annotation, "annotation");
+ Class extends Annotation> annotationType = annotation.annotationType();
+ ensureRetainedAtRuntime(annotationType);
+ ensureIsBindingAnnotation(annotationType);
+
+ if (Annotations.isMarker(annotationType)) {
+ return new AnnotationTypeStrategy(annotationType, annotation);
+ }
+
+ return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
+ }
+
+ /** Gets the strategy for an annotation type. */
+ static AnnotationStrategy strategyFor(Class extends Annotation> annotationType) {
+ annotationType = Annotations.canonicalizeIfNamed(annotationType);
+ if (isAllDefaultMethods(annotationType)) {
+ return strategyFor(generateAnnotation(annotationType));
+ }
+
+ checkNotNull(annotationType, "annotation type");
+ ensureRetainedAtRuntime(annotationType);
+ ensureIsBindingAnnotation(annotationType);
+ return new AnnotationTypeStrategy(annotationType, null);
+ }
+
+ private static void ensureRetainedAtRuntime(Class extends Annotation> annotationType) {
+ checkArgument(
+ Annotations.isRetainedAtRuntime(annotationType),
+ "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
+ annotationType.getName());
+ }
+
+ private static void ensureIsBindingAnnotation(Class extends Annotation> annotationType) {
+ checkArgument(
+ Annotations.isBindingAnnotation(annotationType),
+ "%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
+ annotationType.getName());
+ }
+
+ static enum NullAnnotationStrategy implements AnnotationStrategy {
INSTANCE;
@Override
@@ -392,17 +396,6 @@ public class Key {
}
}
- interface AnnotationStrategy {
-
- Annotation getAnnotation();
-
- Class extends Annotation> 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 extends Annotation> annotationType,
- Annotation annotation) {
+ AnnotationTypeStrategy(Class extends Annotation> 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:
+ *
+ *
+ * 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.
+ * 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 extends Annotation>[] 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 extends T> 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 extends T> 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 extends T> target) {
return implement(source, TypeLiteral.get(target));
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(TypeLiteral source,
- TypeLiteral extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ TypeLiteral source, TypeLiteral extends T> target) {
return implement(Key.get(source), target);
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(Class source, Annotation annotation,
- Class extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ Class source, Annotation annotation, Class extends T> target) {
return implement(source, annotation, TypeLiteral.get(target));
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(Class source, Annotation annotation,
- TypeLiteral extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ Class source, Annotation annotation, TypeLiteral extends T> target) {
return implement(TypeLiteral.get(source), annotation, target);
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(TypeLiteral source, Annotation annotation,
- Class extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ TypeLiteral source, Annotation annotation, Class extends T> target) {
return implement(source, annotation, TypeLiteral.get(target));
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(TypeLiteral source, Annotation annotation,
- TypeLiteral extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ TypeLiteral source, Annotation annotation, TypeLiteral extends T> target) {
return implement(Key.get(source, annotation), target);
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(Class source,
- Class extends Annotation> annotationType, Class extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ Class source, Class extends Annotation> annotationType, Class extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target));
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(Class source,
- Class extends Annotation> annotationType, TypeLiteral extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ Class source,
+ Class extends Annotation> annotationType,
+ TypeLiteral extends T> target) {
return implement(TypeLiteral.get(source), annotationType, target);
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(TypeLiteral source,
- Class extends Annotation> annotationType, Class extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ TypeLiteral source,
+ Class extends Annotation> annotationType,
+ Class extends T> target) {
return implement(source, annotationType, TypeLiteral.get(target));
}
- /**
- * See the factory configuration examples at {@link FactoryModuleBuilder}.
- */
- public FactoryModuleBuilder implement(TypeLiteral source,
- Class extends Annotation> annotationType, TypeLiteral extends T> target) {
+ /** See the factory configuration examples at {@link FactoryModuleBuilder}. */
+ public FactoryModuleBuilder implement(
+ TypeLiteral source,
+ Class extends Annotation> annotationType,
+ TypeLiteral extends T> 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 extends T> 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 extends T> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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