update to 5.0.1, part one (map binder, assisted inject, error messages)
This commit is contained in:
parent
9f72318aae
commit
7e942c813e
56 changed files with 6906 additions and 4022 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>For example, {@code Key.get(Service.class, Transactional.class)} will
|
||||
* match:
|
||||
* <p>For example, {@code Key.get(Service.class, Transactional.class)} will match:
|
||||
*
|
||||
* <pre>
|
||||
* {@literal @}Inject
|
||||
|
@ -27,21 +23,20 @@ import static com.google.inject.internal.Annotations.isAllDefaultMethods;
|
|||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>{@code Key} supports generic types via subclassing just like {@link
|
||||
* TypeLiteral}.
|
||||
* <p>{@code Key} supports generic types via subclassing just like {@link TypeLiteral}.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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<T> {
|
||||
|
||||
private final AnnotationStrategy annotationStrategy;
|
||||
|
||||
private final TypeLiteral<T> 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<T> {
|
|||
/**
|
||||
* Constructs a new key. Derives the type from this class's type parameter.
|
||||
*
|
||||
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
|
||||
* parameter in the anonymous class's type hierarchy so we can reconstitute it
|
||||
* at runtime despite erasure.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Example usage for a binding of type {@code Foo} annotated with
|
||||
* {@code @Bar}:
|
||||
* <p>Example usage for a binding of type {@code Foo} annotated with {@code @Bar}:
|
||||
*
|
||||
* <p>{@code new Key<Foo>(Bar.class) {}}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Key(Class<? extends Annotation> annotationType) {
|
||||
this.annotationStrategy = strategyFor(annotationType);
|
||||
this.typeLiteral = MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
|
||||
this.typeLiteral =
|
||||
MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
|
||||
this.hashCode = computeHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new key. Derives the type from this class's type parameter.
|
||||
*
|
||||
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
|
||||
* parameter in the anonymous class's type hierarchy so we can reconstitute it
|
||||
* at runtime despite erasure.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Example usage for a binding of type {@code Foo} annotated with
|
||||
* {@code @Bar}:
|
||||
* <p>Example usage for a binding of type {@code Foo} annotated with {@code @Bar}:
|
||||
*
|
||||
* <p>{@code new Key<Foo>(new Bar()) {}}.
|
||||
*/
|
||||
|
@ -83,17 +75,17 @@ public class Key<T> {
|
|||
protected Key(Annotation annotation) {
|
||||
// no usages, not test-covered
|
||||
this.annotationStrategy = strategyFor(annotation);
|
||||
this.typeLiteral = MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
|
||||
this.typeLiteral =
|
||||
MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
|
||||
this.hashCode = computeHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new key. Derives the type from this class's type parameter.
|
||||
*
|
||||
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
|
||||
* parameter in the anonymous class's type hierarchy so we can reconstitute it
|
||||
* at runtime despite erasure.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Example usage for a binding of type {@code Foo}:
|
||||
*
|
||||
|
@ -102,14 +94,13 @@ public class Key<T> {
|
|||
@SuppressWarnings("unchecked")
|
||||
protected Key() {
|
||||
this.annotationStrategy = NullAnnotationStrategy.INSTANCE;
|
||||
this.typeLiteral = MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass()));
|
||||
this.typeLiteral =
|
||||
MoreTypes.canonicalizeForKey(
|
||||
(TypeLiteral<T>) 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<T> {
|
|||
this.hashCode = computeHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a key from a manually specified type.
|
||||
*/
|
||||
/** Constructs a key from a manually specified type. */
|
||||
private Key(TypeLiteral<T> typeLiteral, AnnotationStrategy annotationStrategy) {
|
||||
this.annotationStrategy = annotationStrategy;
|
||||
this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral);
|
||||
this.hashCode = computeHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation strategy.
|
||||
*/
|
||||
static <T> Key<T> get(Class<T> type, AnnotationStrategy annotationStrategy) {
|
||||
return new Key<T>(type, annotationStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type.
|
||||
*/
|
||||
public static <T> Key<T> get(Class<T> type) {
|
||||
return new Key<T>(type, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation type.
|
||||
*/
|
||||
public static <T> Key<T> get(Class<T> type, Class<? extends Annotation> annotationType) {
|
||||
return new Key<T>(type, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation.
|
||||
*/
|
||||
public static <T> Key<T> get(Class<T> type, Annotation annotation) {
|
||||
return new Key<T>(type, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type.
|
||||
*/
|
||||
public static Key<?> get(Type type) {
|
||||
return new Key<Object>(type, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation type.
|
||||
*/
|
||||
public static Key<?> get(Type type, Class<? extends Annotation> annotationType) {
|
||||
return new Key<Object>(type, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation.
|
||||
*/
|
||||
public static Key<?> get(Type type, Annotation annotation) {
|
||||
return new Key<Object>(type, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type.
|
||||
*/
|
||||
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
|
||||
return new Key<T>(typeLiteral, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation type.
|
||||
*/
|
||||
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Class<? extends Annotation> annotationType) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key for an injection type and an annotation.
|
||||
*/
|
||||
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Annotation annotation) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strategy for an annotation.
|
||||
*/
|
||||
static AnnotationStrategy strategyFor(Annotation annotation) {
|
||||
checkNotNull(annotation, "annotation");
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
ensureRetainedAtRuntime(annotationType);
|
||||
ensureIsBindingAnnotation(annotationType);
|
||||
|
||||
if (Annotations.isMarker(annotationType)) {
|
||||
return new AnnotationTypeStrategy(annotationType, annotation);
|
||||
}
|
||||
|
||||
return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strategy for an annotation type.
|
||||
*/
|
||||
static AnnotationStrategy strategyFor(Class<? extends Annotation> annotationType) {
|
||||
annotationType = Annotations.canonicalizeIfNamed(annotationType);
|
||||
if (isAllDefaultMethods(annotationType)) {
|
||||
return strategyFor(generateAnnotation(annotationType));
|
||||
}
|
||||
|
||||
checkNotNull(annotationType, "annotation type");
|
||||
ensureRetainedAtRuntime(annotationType);
|
||||
ensureIsBindingAnnotation(annotationType);
|
||||
return new AnnotationTypeStrategy(annotationType, null);
|
||||
|
||||
}
|
||||
|
||||
private static void ensureRetainedAtRuntime(
|
||||
Class<? extends Annotation> annotationType) {
|
||||
checkArgument(Annotations.isRetainedAtRuntime(annotationType),
|
||||
"%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
|
||||
annotationType.getName());
|
||||
}
|
||||
|
||||
private static void ensureIsBindingAnnotation(Class<? extends Annotation> annotationType) {
|
||||
checkArgument(Annotations.isBindingAnnotation(annotationType),
|
||||
"%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
|
||||
annotationType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hash code for this key.
|
||||
*/
|
||||
/** 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<T> 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
|
||||
* <i>or</i> the key was constructed with a {@code Class<Annotation>}.
|
||||
*
|
||||
* <p><b>Warning:</b> this can return null even if this key is annotated. To check whether a
|
||||
* {@code Key} has an annotation use {@link #hasAnnotationType} instead.
|
||||
*/
|
||||
// TODO(diamondm) consider deprecating this in favor of a method that ISEs if hasAnnotationType()
|
||||
// is true but this would return null.
|
||||
public final Annotation getAnnotation() {
|
||||
return annotationStrategy.getAnnotation();
|
||||
}
|
||||
|
@ -287,9 +161,7 @@ public class Key<T> {
|
|||
return typeLiteral.getRawType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key of this key's provider.
|
||||
*/
|
||||
/** Gets the key of this key's provider. */
|
||||
Key<Provider<T>> providerKey() {
|
||||
return ofType(typeLiteral.providerType());
|
||||
}
|
||||
|
@ -324,46 +196,178 @@ public class Key<T> {
|
|||
return local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the specified type with the same annotation as this
|
||||
* key.
|
||||
*/
|
||||
public <T> Key<T> ofType(Class<T> type) {
|
||||
/** Gets a key for an injection type and an annotation strategy. */
|
||||
static <T> Key<T> get(Class<T> type, AnnotationStrategy annotationStrategy) {
|
||||
return new Key<T>(type, annotationStrategy);
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type. */
|
||||
public static <T> Key<T> get(Class<T> type) {
|
||||
return new Key<T>(type, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation type. */
|
||||
public static <T> Key<T> get(Class<T> type, Class<? extends Annotation> annotationType) {
|
||||
return new Key<T>(type, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation. */
|
||||
public static <T> Key<T> get(Class<T> type, Annotation annotation) {
|
||||
return new Key<T>(type, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type. */
|
||||
public static Key<?> get(Type type) {
|
||||
return new Key<>(type, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation type. */
|
||||
public static Key<?> get(Type type, Class<? extends Annotation> annotationType) {
|
||||
return new Key<>(type, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation. */
|
||||
public static Key<?> get(Type type, Annotation annotation) {
|
||||
return new Key<>(type, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type. */
|
||||
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
|
||||
return new Key<T>(typeLiteral, NullAnnotationStrategy.INSTANCE);
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation type. */
|
||||
public static <T> Key<T> get(
|
||||
TypeLiteral<T> typeLiteral, Class<? extends Annotation> annotationType) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/** Gets a key for an injection type and an annotation. */
|
||||
public static <T> Key<T> get(TypeLiteral<T> typeLiteral, Annotation annotation) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the specified type with the same annotation as this
|
||||
* key.
|
||||
* Returns a new key of the specified type with the same annotation as this key.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public <U> Key<U> ofType(Class<U> type) {
|
||||
return new Key<>(type, annotationStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the specified type with the same annotation as this key.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public Key<?> ofType(Type type) {
|
||||
return new Key<Object>(type, annotationStrategy);
|
||||
return new Key<>(type, annotationStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the specified type with the same annotation as this
|
||||
* key.
|
||||
* Returns a new key of the specified type with the same annotation as this key.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public <T> Key<T> ofType(TypeLiteral<T> type) {
|
||||
return new Key<T>(type, annotationStrategy);
|
||||
public <U> Key<U> ofType(TypeLiteral<U> type) {
|
||||
return new Key<U>(type, annotationStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the same type with the specified annotation.
|
||||
*
|
||||
* <p>This is equivalent to {@code Key.get(key.getTypeLiteral(), annotation)} but may be more
|
||||
* convenient to use in certain cases.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public Key<T> withAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotationType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new key of the same type with the specified annotation.
|
||||
*
|
||||
* <p>This is equivalent to {@code Key.get(key.getTypeLiteral(), annotation)} but may be more
|
||||
* convenient to use in certain cases.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public Key<T> withAnnotation(Annotation annotation) {
|
||||
return new Key<T>(typeLiteral, strategyFor(annotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key has annotation attributes.
|
||||
*
|
||||
* @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<T> withoutAttributes() {
|
||||
return new Key<T>(typeLiteral, annotationStrategy.withoutAttributes());
|
||||
}
|
||||
|
||||
enum NullAnnotationStrategy implements AnnotationStrategy {
|
||||
interface AnnotationStrategy {
|
||||
Annotation getAnnotation();
|
||||
|
||||
Class<? extends Annotation> getAnnotationType();
|
||||
|
||||
boolean hasAttributes();
|
||||
|
||||
AnnotationStrategy withoutAttributes();
|
||||
}
|
||||
|
||||
/** Gets the strategy for an annotation. */
|
||||
static AnnotationStrategy strategyFor(Annotation annotation) {
|
||||
checkNotNull(annotation, "annotation");
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
ensureRetainedAtRuntime(annotationType);
|
||||
ensureIsBindingAnnotation(annotationType);
|
||||
|
||||
if (Annotations.isMarker(annotationType)) {
|
||||
return new AnnotationTypeStrategy(annotationType, annotation);
|
||||
}
|
||||
|
||||
return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
|
||||
}
|
||||
|
||||
/** Gets the strategy for an annotation type. */
|
||||
static AnnotationStrategy strategyFor(Class<? extends Annotation> annotationType) {
|
||||
annotationType = Annotations.canonicalizeIfNamed(annotationType);
|
||||
if (isAllDefaultMethods(annotationType)) {
|
||||
return strategyFor(generateAnnotation(annotationType));
|
||||
}
|
||||
|
||||
checkNotNull(annotationType, "annotation type");
|
||||
ensureRetainedAtRuntime(annotationType);
|
||||
ensureIsBindingAnnotation(annotationType);
|
||||
return new AnnotationTypeStrategy(annotationType, null);
|
||||
}
|
||||
|
||||
private static void ensureRetainedAtRuntime(Class<? extends Annotation> annotationType) {
|
||||
checkArgument(
|
||||
Annotations.isRetainedAtRuntime(annotationType),
|
||||
"%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
|
||||
annotationType.getName());
|
||||
}
|
||||
|
||||
private static void ensureIsBindingAnnotation(Class<? extends Annotation> annotationType) {
|
||||
checkArgument(
|
||||
Annotations.isBindingAnnotation(annotationType),
|
||||
"%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
|
||||
annotationType.getName());
|
||||
}
|
||||
|
||||
static enum NullAnnotationStrategy implements AnnotationStrategy {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
|
@ -392,17 +396,6 @@ public class Key<T> {
|
|||
}
|
||||
}
|
||||
|
||||
interface AnnotationStrategy {
|
||||
|
||||
Annotation getAnnotation();
|
||||
|
||||
Class<? extends Annotation> getAnnotationType();
|
||||
|
||||
boolean hasAttributes();
|
||||
|
||||
AnnotationStrategy withoutAttributes();
|
||||
}
|
||||
|
||||
// this class not test-covered
|
||||
static class AnnotationInstanceStrategy implements AnnotationStrategy {
|
||||
|
||||
|
@ -460,8 +453,7 @@ public class Key<T> {
|
|||
// Keep the instance around if we have it so the client can request it.
|
||||
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;
|
||||
}
|
||||
|
|
120
src/main/java/com/google/inject/RestrictedBindingSource.java
Normal file
120
src/main/java/com/google/inject/RestrictedBindingSource.java
Normal file
|
@ -0,0 +1,120 @@
|
|||
package com.google.inject;
|
||||
|
||||
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation restricting the binding of the target type to permitted sources.
|
||||
*
|
||||
* <p>Bindings restricted by this annotation may only be created by sources annotated with a permit
|
||||
* from {@link #permits} -- otherwise, an error message including the {@link #explanation} is
|
||||
* issued.
|
||||
*
|
||||
* <p>There are two kinds of binding source:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Module: a module is the source of a binding if it creates it (either directly, or
|
||||
* indirectly by installing another module). For example: if module A creates restricted
|
||||
* binding X, and module C installs module B that installs A; then all 3 modules C,B,A are
|
||||
* sources of X, and it's enough for any one of them to be annotated with a permit from X's
|
||||
* restriction.
|
||||
* <li>Method Scanner ({@code ModuleAnnotatedMethodScanner}): If a binding was created by a
|
||||
* scanner, then that scanner is also a source of the binding (in addition to the module
|
||||
* sources) and a permit may be given to the scanner by annotating its class.
|
||||
* </ol>
|
||||
*
|
||||
* <p>Bindings with qualifier annotations are restricted solely by the annotation on their qualifier
|
||||
* (restrictions on the type are ignored for qualified bindings). Unqualified bindings are
|
||||
* restricted by the annotation on their type.
|
||||
*
|
||||
* <p>This allows libraries to prevent their clients from binding their keys, similar to how
|
||||
* declaring a class final prevents subtyping. For example, a library may want to prevent users from
|
||||
* creating mock bindings for tests, using the {@link #explanation} - included in the error message
|
||||
* - to point them to a supported testing module.
|
||||
*
|
||||
* <p>Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* @RestrictedBindingSource.Permit
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* @interface NetworkPermit {}
|
||||
*
|
||||
* @RestrictedBindingSource(
|
||||
* explanation = "Only NetworkModule can create network bindings.",
|
||||
* permits = {NetworkPermit.class})
|
||||
* @Qualifier
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* public @interface GatewayIpAdress {}
|
||||
*
|
||||
* @NetworkPermit
|
||||
* public final class NetworkModule extends AbstractModule {
|
||||
* @Provides
|
||||
* @GatewayIpAdress // Allowed because the module is annotated with @NetworkPermit.
|
||||
* int provideGatewayIp() { ... }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @author vzm@google.com (Vladimir Makaric)
|
||||
* @since 5.0
|
||||
*/
|
||||
@Inherited
|
||||
@Retention(RUNTIME)
|
||||
@Target(TYPE)
|
||||
public @interface RestrictedBindingSource {
|
||||
/**
|
||||
* Explanation of why binding this target type is restricted.
|
||||
*
|
||||
* <p>Will appear as the error message if the target type is bound by non-allowed modules.
|
||||
*/
|
||||
String explanation();
|
||||
|
||||
/**
|
||||
* Meta-annotation indicating that the target annotation is a permit for binding restricted
|
||||
* bindings. Annotating a binding source (defined in top-level javadoc) with a permit gives it
|
||||
* permission to bind the restricted bindings guarded by the permit (see {@link #permits}).
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target(ANNOTATION_TYPE)
|
||||
public @interface Permit {}
|
||||
|
||||
/**
|
||||
* List of {@code Permit} annotations (must be non-empty), one of which has has to be present on a
|
||||
* restricted binding's source (defined in top-level javadoc).
|
||||
*/
|
||||
Class<? extends Annotation>[] permits();
|
||||
|
||||
/**
|
||||
* Exempt modules whose fully qualified class names match this regex.
|
||||
*
|
||||
* <p>If any module on the binding's module stack matches this regex, the binding is allowed (no
|
||||
* permit necessary). No module is exempt by default (empty string).
|
||||
*
|
||||
* <p>Inteded to be used when retrofitting a binding with this restriction. When restricting an
|
||||
* existing binding, it's often practical to first restrict with exemptions for existing
|
||||
* violations (to prevent new violations), before updating the code in violation to use the
|
||||
* permitted module(s).
|
||||
*/
|
||||
String exemptModules() default "";
|
||||
|
||||
/**
|
||||
* Level of restriction. Determines how violations are handled.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public static enum RestrictionLevel {
|
||||
WARNING,
|
||||
ERROR;
|
||||
}
|
||||
|
||||
RestrictionLevel restrictionLevel() default RestrictionLevel.ERROR;
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <h3>Defining a factory</h3>
|
||||
*
|
||||
* 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 <i>newPayment</i>.
|
||||
*
|
||||
* <h3>Creating a type that accepts factory parameters</h3>
|
||||
*
|
||||
* {@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;
|
|||
* }</pre>
|
||||
*
|
||||
* <h3>Multiple factory methods for the same type</h3>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <pre>public interface PaymentFactory {
|
||||
* Payment create(Date startDate, Money amount);
|
||||
|
@ -72,8 +74,8 @@ import java.lang.annotation.Annotation;
|
|||
* }</pre>
|
||||
*
|
||||
* <h3>Configuring simple factories</h3>
|
||||
* In your {@link Module module}, install a {@code FactoryModuleBuilder} that creates the
|
||||
* factory:
|
||||
*
|
||||
* In your {@link Module module}, install a {@code FactoryModuleBuilder} that creates the factory:
|
||||
*
|
||||
* <pre>install(new FactoryModuleBuilder()
|
||||
* .implement(Payment.class, RealPayment.class)
|
||||
|
@ -83,8 +85,9 @@ import java.lang.annotation.Annotation;
|
|||
* factory cannot be used until the injector has been initialized.
|
||||
*
|
||||
* <h3>Configuring complex factories</h3>
|
||||
* Factories can create an arbitrary number of objects, one per each method. Each factory
|
||||
* method can be configured using <code>.implement</code>.
|
||||
*
|
||||
* Factories can create an arbitrary number of objects, one per each method. Each factory method can
|
||||
* be configured using <code>.implement</code>.
|
||||
*
|
||||
* <pre>public interface OrderFactory {
|
||||
* 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));</pre>
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Using the factory</h3>
|
||||
* Inject your factory into your application classes. When you use the factory, your arguments
|
||||
* will be combined with values from the injector to construct an instance.
|
||||
*
|
||||
* Inject your factory into your application classes. When you use the factory, your arguments will
|
||||
* be combined with values from the injector to construct an instance.
|
||||
*
|
||||
* <pre>public class PaymentAction {
|
||||
* {@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();
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Making parameter types distinct</h3>
|
||||
* The types of the factory method's parameters must be distinct. To use multiple parameters of
|
||||
* the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the
|
||||
* 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:
|
||||
*
|
||||
* <pre>public interface PaymentFactory {
|
||||
* Payment create(
|
||||
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate,
|
||||
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
|
||||
* Money amount);
|
||||
* Payment create(
|
||||
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate,
|
||||
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
|
||||
* Money amount);
|
||||
* } </pre>
|
||||
*
|
||||
* ...and to the concrete type's constructor parameters:
|
||||
*
|
||||
* <pre>public class RealPayment implements Payment {
|
||||
* {@literal @}Inject
|
||||
* public RealPayment(
|
||||
* CreditService creditService,
|
||||
* AuthService authService,
|
||||
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate,
|
||||
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
|
||||
* <strong>{@literal @}Assisted</strong> Money amount) {
|
||||
* ...
|
||||
* }
|
||||
* {@literal @}Inject
|
||||
* public RealPayment(
|
||||
* CreditService creditService,
|
||||
* AuthService authService,
|
||||
* <strong>{@literal @}Assisted("startDate")</strong> Date startDate,
|
||||
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
|
||||
* <strong>{@literal @}Assisted</strong> Money amount) {
|
||||
* ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Values are created by Guice</h3>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <h3>More configuration options</h3>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* <p>If you just want to return the types specified in the factory, do not configure any
|
||||
* implementations:
|
||||
*
|
||||
* <pre>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));
|
||||
* }</pre>
|
||||
*
|
||||
* Note that any type returned by the factory in this manner needs to be an implementation class.
|
||||
* To return two different implementations for the same interface from your factory, use binding
|
||||
*
|
||||
* <p>To return two different implementations for the same interface from your factory, use binding
|
||||
* annotations on your return types:
|
||||
*
|
||||
* <pre>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));
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Implementation limitations</h3>
|
||||
* As a limitation of the implementation, it is prohibited to declare a factory method that
|
||||
* accepts a {@code Provider} as one of its arguments.
|
||||
*
|
||||
* As a limitation of the implementation, it is prohibited to declare a factory method that accepts
|
||||
* a {@code Provider} as one of its arguments.
|
||||
*
|
||||
* @since 3.0
|
||||
* @author schmitt@google.com (Peter Schmitt)
|
||||
*/
|
||||
public final class FactoryModuleBuilder {
|
||||
|
||||
private final BindingCollector bindings = new BindingCollector();
|
||||
private MethodHandles.Lookup lookups;
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(Class<T> source, Class<? extends T> target) {
|
||||
return implement(source, TypeLiteral.get(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(Class<T> source, TypeLiteral<? extends T> target) {
|
||||
return implement(TypeLiteral.get(source), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Class<? extends T> target) {
|
||||
return implement(source, TypeLiteral.get(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
|
||||
TypeLiteral<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
TypeLiteral<T> source, TypeLiteral<? extends T> target) {
|
||||
return implement(Key.get(source), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
|
||||
Class<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
Class<T> source, Annotation annotation, Class<? extends T> target) {
|
||||
return implement(source, annotation, TypeLiteral.get(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(Class<T> source, Annotation annotation,
|
||||
TypeLiteral<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
Class<T> source, Annotation annotation, TypeLiteral<? extends T> target) {
|
||||
return implement(TypeLiteral.get(source), annotation, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
|
||||
Class<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
TypeLiteral<T> source, Annotation annotation, Class<? extends T> target) {
|
||||
return implement(source, annotation, TypeLiteral.get(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(TypeLiteral<T> source, Annotation annotation,
|
||||
TypeLiteral<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
TypeLiteral<T> source, Annotation annotation, TypeLiteral<? extends T> target) {
|
||||
return implement(Key.get(source, annotation), target);
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
public <T> FactoryModuleBuilder implement(Class<T> source,
|
||||
Class<? extends Annotation> annotationType, Class<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
Class<T> 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 <T> FactoryModuleBuilder implement(Class<T> source,
|
||||
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
Class<T> 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 <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
|
||||
Class<? extends Annotation> annotationType, Class<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
TypeLiteral<T> 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 <T> FactoryModuleBuilder implement(TypeLiteral<T> source,
|
||||
Class<? extends Annotation> annotationType, TypeLiteral<? extends T> target) {
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <T> FactoryModuleBuilder implement(
|
||||
TypeLiteral<T> 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 <T> FactoryModuleBuilder implement(Key<T> 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 <T> FactoryModuleBuilder implement(Key<T> 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).
|
||||
*
|
||||
* <p>Guice will try to work properly even if this method is not called (or called with a lookups
|
||||
* that doesn't have access to the factory), but doing so requires reflection into the JDK, which
|
||||
* may break at any time (and trigger unsafe access warnings).
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public <T> FactoryModuleBuilder withLookups(MethodHandles.Lookup lookups) {
|
||||
this.lookups = lookups;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <F> Module build(Class<F> factoryInterface) {
|
||||
return build(TypeLiteral.get(factoryInterface));
|
||||
}
|
||||
|
||||
/**
|
||||
* See the factory configuration examples at {@link FactoryModuleBuilder}.
|
||||
*/
|
||||
/** See the factory configuration examples at {@link FactoryModuleBuilder}. */
|
||||
public <F> Module build(TypeLiteral<F> factoryInterface) {
|
||||
return build(Key.get(factoryInterface));
|
||||
}
|
||||
|
||||
|
||||
public <F> Module build(final Key<F> factoryInterface) {
|
||||
return new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
Provider<F> provider = new FactoryProvider2<F>(factoryInterface, bindings);
|
||||
bind(factoryInterface).toProvider(provider);
|
||||
Provider<F> provider = new FactoryProvider2<>(factoryInterface, bindings, lookups);
|
||||
binder().skipSources(this.getClass()).bind(factoryInterface).toProvider(provider);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <strong>Obsolete.</strong> 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.
|
||||
*
|
||||
* <h3>Defining a factory</h3>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <pre>public interface PaymentFactory {
|
||||
* Payment create(Date startDate, Money amount);
|
||||
* }</pre>
|
||||
*
|
||||
* You can name your factory methods whatever you like, such as <i>create</i>, <i>createPayment</i>
|
||||
* or <i>newPayment</i>.
|
||||
*
|
||||
* <h3>Creating a type that accepts factory parameters</h3>
|
||||
*
|
||||
* {@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.
|
||||
*
|
||||
* <pre>public class RealPayment implements Payment {
|
||||
* {@literal @}Inject
|
||||
* public RealPayment(
|
||||
|
@ -62,19 +65,25 @@ import static com.google.inject.internal.Annotations.getKey;
|
|||
* ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* Any parameter that permits a null value should also be annotated {@code @Nullable}.
|
||||
*
|
||||
* <h3>Configuring factories</h3>
|
||||
*
|
||||
* In your {@link com.google.inject.Module module}, bind the factory interface to the returned
|
||||
* factory:
|
||||
*
|
||||
* <pre>bind(PaymentFactory.class).toProvider(
|
||||
* FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre>
|
||||
*
|
||||
* As a side-effect of this binding, Guice will inject the factory to initialize it for use. The
|
||||
* factory cannot be used until the injector has been initialized.
|
||||
*
|
||||
* <h3>Using the factory</h3>
|
||||
* Inject your factory into your application classes. When you use the factory, your arguments
|
||||
* will be combined with values from the injector to construct an instance.
|
||||
*
|
||||
* Inject your factory into your application classes. When you use the factory, your arguments will
|
||||
* be combined with values from the injector to construct an instance.
|
||||
*
|
||||
* <pre>public class PaymentAction {
|
||||
* {@literal @}Inject private PaymentFactory paymentFactory;
|
||||
*
|
||||
|
@ -85,9 +94,10 @@ import static com.google.inject.internal.Annotations.getKey;
|
|||
* }</pre>
|
||||
*
|
||||
* <h3>Making parameter types distinct</h3>
|
||||
* The types of the factory method's parameters must be distinct. To use multiple parameters of
|
||||
* the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the
|
||||
* 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:
|
||||
*
|
||||
* <pre>public interface PaymentFactory {
|
||||
* Payment create(
|
||||
|
@ -95,7 +105,9 @@ import static com.google.inject.internal.Annotations.getKey;
|
|||
* <strong>{@literal @}Assisted("dueDate")</strong> Date dueDate,
|
||||
* Money amount);
|
||||
* } </pre>
|
||||
*
|
||||
* ...and to the concrete type's constructor parameters:
|
||||
*
|
||||
* <pre>public class RealPayment implements Payment {
|
||||
* {@literal @}Inject
|
||||
* public RealPayment(
|
||||
|
@ -109,46 +121,42 @@ import static com.google.inject.internal.Annotations.getKey;
|
|||
* }</pre>
|
||||
*
|
||||
* <h3>Values are created by Guice</h3>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <h3>Backwards compatibility using {@literal @}AssistedInject</h3>
|
||||
*
|
||||
* Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with
|
||||
* {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode.
|
||||
*
|
||||
* <p>Instead of matching factory method arguments to constructor parameters using their names, the
|
||||
* <strong>parameters are matched by their order</strong>. The first factory method argument is
|
||||
* used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no
|
||||
* effect.
|
||||
* <strong>parameters are matched by their order</strong>. The first factory method argument is used
|
||||
* for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no effect.
|
||||
*
|
||||
* <p>Returned values are <strong>not created by Guice</strong>. These types are not eligible for
|
||||
* method interception. They do receive post-construction member injection.
|
||||
*
|
||||
* @param <F> The factory interface
|
||||
* @author jmourits@google.com (Jerome Mourits)
|
||||
* @author jessewilson@google.com (Jesse Wilson)
|
||||
* @author dtm@google.com (Daniel Martin)
|
||||
* @deprecated use {@link FactoryModuleBuilder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class FactoryProvider<F> implements Provider<F>, 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<F> factoryType;
|
||||
private final TypeLiteral<?> implementationType;
|
||||
private final Map<Method, AssistedConstructor<?>> factoryMethodToConstructor;
|
||||
private Injector injector;
|
||||
|
||||
private FactoryProvider(TypeLiteral<F> factoryType,
|
||||
TypeLiteral<?> implementationType,
|
||||
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) {
|
||||
this.factoryType = factoryType;
|
||||
this.implementationType = implementationType;
|
||||
this.factoryMethodToConstructor = factoryMethodToConstructor;
|
||||
checkDeclaredExceptionsMatch();
|
||||
}
|
||||
|
||||
public static <F> Provider<F> newFactory(Class<F> factoryType, Class<?> implementationType) {
|
||||
return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType));
|
||||
|
@ -156,8 +164,8 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
|
|||
|
||||
public static <F> Provider<F> newFactory(
|
||||
TypeLiteral<F> factoryType, TypeLiteral<?> implementationType) {
|
||||
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor
|
||||
= createMethodMapping(factoryType, implementationType);
|
||||
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor =
|
||||
createMethodMapping(factoryType, implementationType);
|
||||
|
||||
if (!factoryMethodToConstructor.isEmpty()) {
|
||||
return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor);
|
||||
|
@ -171,8 +179,8 @@ public class FactoryProvider<F> implements Provider<F>, 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<F> implements Provider<F>, HasDependencies {
|
|||
throw new ConfigurationException(e.getErrors().getMessages());
|
||||
}
|
||||
|
||||
return new FactoryProvider2<F>(Key.get(factoryType), collector);
|
||||
return new FactoryProvider2<F>(Key.get(factoryType), collector, /* userLookups= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Method, AssistedConstructor<?>> createMethodMapping(
|
||||
TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) {
|
||||
List<AssistedConstructor<?>> constructors = Lists.newArrayList();
|
||||
|
||||
for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) {
|
||||
if (constructor.isAnnotationPresent(AssistedInject.class)) {
|
||||
AssistedConstructor<?> assistedConstructor = AssistedConstructor.create(
|
||||
constructor, implementationType.getParameterTypes(constructor));
|
||||
constructors.add(assistedConstructor);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructors.isEmpty()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
|
||||
Method[] factoryMethods = factoryType.getRawType().getMethods();
|
||||
|
||||
if (constructors.size() != factoryMethods.length) {
|
||||
throw newConfigurationException("Constructor mismatch: %s has %s @AssistedInject "
|
||||
+ "constructors, factory %s has %s creation methods", implementationType,
|
||||
constructors.size(), factoryType, factoryMethods.length);
|
||||
}
|
||||
|
||||
Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap();
|
||||
|
||||
for (AssistedConstructor<?> c : constructors) {
|
||||
if (paramsToConstructor.containsKey(c.getAssistedParameters())) {
|
||||
throw new RuntimeException("Duplicate constructor, " + c);
|
||||
}
|
||||
paramsToConstructor.put(c.getAssistedParameters(), c);
|
||||
}
|
||||
|
||||
Map<Method, AssistedConstructor<?>> result = Maps.newHashMap();
|
||||
for (Method method : factoryMethods) {
|
||||
if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) {
|
||||
throw newConfigurationException("Return type of method %s is not assignable from %s",
|
||||
method, implementationType);
|
||||
}
|
||||
|
||||
List<Type> parameterTypes = Lists.newArrayList();
|
||||
for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) {
|
||||
parameterTypes.add(parameterType.getType());
|
||||
}
|
||||
ParameterListKey methodParams = new ParameterListKey(parameterTypes);
|
||||
|
||||
if (!paramsToConstructor.containsKey(methodParams)) {
|
||||
throw newConfigurationException("%s has no @AssistInject constructor that takes the "
|
||||
+ "@Assisted parameters %s in that order. @AssistInject constructors are %s",
|
||||
implementationType, methodParams, paramsToConstructor.values());
|
||||
}
|
||||
|
||||
method.getParameterAnnotations();
|
||||
for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
|
||||
for (Annotation parameterAnnotation : parameterAnnotations) {
|
||||
if (parameterAnnotation.annotationType() == Assisted.class) {
|
||||
throw newConfigurationException("Factory method %s has an @Assisted parameter, which "
|
||||
+ "is incompatible with the deprecated @AssistedInject annotation. Please replace "
|
||||
+ "@AssistedInject with @Inject on the %s constructor.",
|
||||
method, implementationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams);
|
||||
|
||||
result.put(method, matchingConstructor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ConfigurationException newConfigurationException(String format, Object... args) {
|
||||
return new ConfigurationException(ImmutableSet.of(new Message(Messages.format(format, args))));
|
||||
private FactoryProvider(
|
||||
TypeLiteral<F> factoryType,
|
||||
TypeLiteral<?> implementationType,
|
||||
Map<Method, AssistedConstructor<?>> factoryMethodToConstructor) {
|
||||
this.factoryType = factoryType;
|
||||
this.implementationType = implementationType;
|
||||
this.factoryMethodToConstructor = factoryMethodToConstructor;
|
||||
checkDeclaredExceptionsMatch();
|
||||
}
|
||||
|
||||
@Inject
|
||||
|
@ -269,8 +212,10 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
|
|||
// this is lame - we're not using the proper mechanism to add an
|
||||
// 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<F> implements Provider<F>, 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<F> implements Provider<F>, HasDependencies {
|
|||
return parameter.isBound(injector);
|
||||
}
|
||||
|
||||
private static Map<Method, AssistedConstructor<?>> createMethodMapping(
|
||||
TypeLiteral<?> factoryType, TypeLiteral<?> implementationType) {
|
||||
List<AssistedConstructor<?>> constructors = Lists.newArrayList();
|
||||
|
||||
for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) {
|
||||
if (constructor.isAnnotationPresent(AssistedInject.class)) {
|
||||
AssistedConstructor<?> assistedConstructor =
|
||||
AssistedConstructor.create(
|
||||
constructor, implementationType.getParameterTypes(constructor));
|
||||
constructors.add(assistedConstructor);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructors.isEmpty()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
|
||||
Method[] factoryMethods = factoryType.getRawType().getMethods();
|
||||
|
||||
if (constructors.size() != factoryMethods.length) {
|
||||
throw newConfigurationException(
|
||||
"Constructor mismatch: %s has %s @AssistedInject "
|
||||
+ "constructors, factory %s has %s creation methods",
|
||||
implementationType, constructors.size(), factoryType, factoryMethods.length);
|
||||
}
|
||||
|
||||
Map<ParameterListKey, AssistedConstructor<?>> paramsToConstructor = Maps.newHashMap();
|
||||
|
||||
for (AssistedConstructor<?> c : constructors) {
|
||||
if (paramsToConstructor.containsKey(c.getAssistedParameters())) {
|
||||
throw new RuntimeException("Duplicate constructor, " + c);
|
||||
}
|
||||
paramsToConstructor.put(c.getAssistedParameters(), c);
|
||||
}
|
||||
|
||||
Map<Method, AssistedConstructor<?>> result = Maps.newHashMap();
|
||||
for (Method method : factoryMethods) {
|
||||
if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) {
|
||||
throw newConfigurationException(
|
||||
"Return type of method %s is not assignable from %s", method, implementationType);
|
||||
}
|
||||
|
||||
List<Type> parameterTypes = Lists.newArrayList();
|
||||
for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) {
|
||||
parameterTypes.add(parameterType.getType());
|
||||
}
|
||||
ParameterListKey methodParams = new ParameterListKey(parameterTypes);
|
||||
|
||||
if (!paramsToConstructor.containsKey(methodParams)) {
|
||||
throw newConfigurationException(
|
||||
"%s has no @AssistInject constructor that takes the "
|
||||
+ "@Assisted parameters %s in that order. @AssistInject constructors are %s",
|
||||
implementationType, methodParams, paramsToConstructor.values());
|
||||
}
|
||||
|
||||
method.getParameterAnnotations();
|
||||
for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
|
||||
for (Annotation parameterAnnotation : parameterAnnotations) {
|
||||
if (parameterAnnotation.annotationType() == Assisted.class) {
|
||||
throw newConfigurationException(
|
||||
"Factory method %s has an @Assisted parameter, which is incompatible with the"
|
||||
+ " deprecated @AssistedInject annotation. Please replace @AssistedInject with"
|
||||
+ " @Inject on the %s constructor.",
|
||||
method, implementationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams);
|
||||
|
||||
result.put(method, matchingConstructor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Dependency<?>> getDependencies() {
|
||||
List<Dependency<?>> dependencies = Lists.newArrayList();
|
||||
for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) {
|
||||
|
@ -314,51 +337,55 @@ public class FactoryProvider<F> implements Provider<F>, 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<T>
|
||||
Class<F> factoryRawType = (Class<F>) factoryType.getRawType();
|
||||
return factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(),
|
||||
new Class<?>[]{factoryRawType}, invocationHandler));
|
||||
Class<F> factoryRawType = (Class<F>) (Class<?>) factoryType.getRawType();
|
||||
return factoryRawType.cast(
|
||||
Proxy.newProxyInstance(
|
||||
factoryRawType.getClassLoader(), new Class<?>[] {factoryRawType}, invocationHandler));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -375,4 +402,8 @@ public class FactoryProvider<F> implements Provider<F>, HasDependencies {
|
|||
return factoryType.equals(other.factoryType)
|
||||
&& implementationType.equals(other.implementationType);
|
||||
}
|
||||
|
||||
private static ConfigurationException newConfigurationException(String format, Object... args) {
|
||||
return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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<Class<?>> 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<Class<?>> FORBIDDEN_TYPES =
|
||||
ImmutableSet.<Class<?>>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 <T> UntargettedBindingImpl<T> 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<T, V> extends DefaultBindingTargetVisitor<T, V> {
|
||||
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) {
|
||||
|
|
|
@ -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<Object, String> DEEP_TO_STRING_FN = new Function<Object, String>() {
|
||||
@Override
|
||||
public String apply(Object arg) {
|
||||
String s = Arrays.deepToString(new Object[]{arg});
|
||||
return s.substring(1, s.length() - 1); // cut off brackets
|
||||
}
|
||||
};
|
||||
|
||||
private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
|
||||
CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
.build(new CacheLoader<>() {
|
||||
@Override
|
||||
public Annotation load(Class<? extends Annotation> input) {
|
||||
return generateAnnotationImpl(input);
|
||||
}
|
||||
});
|
||||
|
||||
private static final AnnotationChecker scopeChecker = new AnnotationChecker(
|
||||
Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));
|
||||
private static final AnnotationChecker bindingAnnotationChecker = new AnnotationChecker(
|
||||
Arrays.asList(BindingAnnotation.class, Qualifier.class));
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given annotation type has no attributes.
|
||||
*/
|
||||
/** Returns {@code true} if the given annotation type has no attributes. */
|
||||
public static boolean isMarker(Class<? extends Annotation> annotationType) {
|
||||
return annotationType.getDeclaredMethods().length == 0;
|
||||
}
|
||||
|
@ -79,11 +52,22 @@ public class Annotations {
|
|||
return hasMethods;
|
||||
}
|
||||
|
||||
private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
|
||||
CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
.build(
|
||||
new CacheLoader<Class<? extends Annotation>, Annotation>() {
|
||||
@Override
|
||||
public Annotation load(Class<? extends Annotation> input) {
|
||||
return generateAnnotationImpl(input);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates an Annotation for the annotation class. Requires that the annotation is all
|
||||
* optionals.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings("unchecked") // Safe because generateAnnotationImpl returns T for Class<T>
|
||||
public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) {
|
||||
Preconditions.checkState(
|
||||
isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType);
|
||||
|
@ -92,24 +76,27 @@ public class Annotations {
|
|||
|
||||
private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) {
|
||||
final Map<String, Object> 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<String, Object> resolveMembers(
|
||||
|
@ -121,68 +108,67 @@ public class Annotations {
|
|||
return result.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@link Annotation#equals}.
|
||||
*/
|
||||
private static boolean annotationEquals(Class<? extends Annotation> type,
|
||||
Map<String, Object> members, Object other) throws Exception {
|
||||
/** Implements {@link Annotation#equals}. */
|
||||
private static boolean annotationEquals(
|
||||
Class<? extends Annotation> type, Map<String, Object> 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<? extends Annotation> type,
|
||||
Map<String, Object> members) throws Exception {
|
||||
/** Implements {@link Annotation#hashCode}. */
|
||||
private static int annotationHashCode(
|
||||
Class<? extends Annotation> type, Map<String, Object> 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<? extends Annotation> type,
|
||||
Map<String, Object> members) throws Exception {
|
||||
private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("=");
|
||||
|
||||
/** Implements {@link Annotation#toString}. */
|
||||
private static String annotationToString(
|
||||
Class<? extends Annotation> type, Map<String, Object> 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<? extends Annotation> 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<? extends Annotation> findScopeAnnotation(
|
||||
Errors errors, Class<?> implementation) {
|
||||
return findScopeAnnotation(errors, implementation.getAnnotations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scoping annotation, or null if there isn't one.
|
||||
*/
|
||||
public static Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
|
||||
/** Returns the scoping annotation, or null if there isn't one. */
|
||||
public static Class<? extends Annotation> findScopeAnnotation(
|
||||
Errors errors, Annotation[] annotations) {
|
||||
Class<? extends Annotation> 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.
|
||||
*
|
||||
* <p>The value of the member is prefixed with `memberName=` unless the runtime omits the member
|
||||
* name. The value of the member is quoted if annotation implementations quote their member values
|
||||
* and the value type is String.
|
||||
*
|
||||
* <p>In Java 9, annotations quote their string members and in Java 15, the member name is
|
||||
* omitted.
|
||||
*/
|
||||
public static String memberValueString(String memberName, Object value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean quote = ANNOTATION_TO_STRING_CONFIG.quote;
|
||||
boolean includeMemberName = ANNOTATION_TO_STRING_CONFIG.includeMemberName;
|
||||
if (includeMemberName) {
|
||||
sb.append(memberName).append('=');
|
||||
}
|
||||
if (quote && (value instanceof String)) {
|
||||
sb.append('"').append(value).append('"');
|
||||
} else {
|
||||
sb.append(value);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Retention(RUNTIME)
|
||||
|
@ -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<Class<? extends Annotation>> annotationTypes;
|
||||
|
||||
/** Returns true if the given class has one of the desired annotations. */
|
||||
private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
|
||||
new CacheLoader<Class<? extends Annotation>, Boolean>() {
|
||||
@Override
|
||||
public Boolean load(Class<? extends Annotation> annotationType) {
|
||||
for (Annotation annotation : annotationType.getAnnotations()) {
|
||||
if (annotationTypes.contains(annotation.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
final LoadingCache<Class<? extends Annotation>, Boolean> cache =
|
||||
CacheBuilder.newBuilder().weakKeys().build(hasAnnotations);
|
||||
|
||||
/** Constructs a new checker that looks for annotations of the given types. */
|
||||
AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
|
||||
this.annotationTypes = annotationTypes;
|
||||
}
|
||||
|
||||
/** Returns true if the given type has one of the desired annotations. */
|
||||
boolean hasAnnotations(Class<? extends Annotation> annotated) {
|
||||
return cache.getUnchecked(annotated);
|
||||
}
|
||||
}
|
||||
|
||||
private static final AnnotationChecker scopeChecker =
|
||||
new AnnotationChecker(Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));
|
||||
|
||||
public static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) {
|
||||
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<? extends Annotation> 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<Class<? extends Annotation>> annotationTypes;
|
||||
|
||||
/**
|
||||
* Returns true if the given class has one of the desired annotations.
|
||||
*/
|
||||
private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
|
||||
new CacheLoader<>() {
|
||||
@Override
|
||||
public Boolean load(Class<? extends Annotation> annotationType) {
|
||||
for (Annotation annotation : annotationType.getAnnotations()) {
|
||||
if (annotationTypes.contains(annotation.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
final LoadingCache<Class<? extends Annotation>, Boolean> cache =
|
||||
CacheBuilder.newBuilder().weakKeys().build(hasAnnotations);
|
||||
|
||||
/**
|
||||
* Constructs a new checker that looks for annotations of the given types.
|
||||
*/
|
||||
AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
|
||||
this.annotationTypes = annotationTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given type has one of the desired annotations.
|
||||
*/
|
||||
boolean hasAnnotations(Class<? extends Annotation> annotated) {
|
||||
return cache.getUnchecked(annotated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Error reported by Guice when a key is bound at multiple places the injector. */
|
||||
final class BindingAlreadySetError extends InternalErrorDetail<BindingAlreadySetError> {
|
||||
private final Binding<?> binding;
|
||||
private final Binding<?> original;
|
||||
|
||||
BindingAlreadySetError(Binding<?> binding, Binding<?> original, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.BINDING_ALREADY_SET,
|
||||
String.format("%s was bound multiple times.", Messages.convert(binding.getKey())),
|
||||
sources,
|
||||
null);
|
||||
this.binding = binding;
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeable(ErrorDetail<?> otherError) {
|
||||
return otherError instanceof BindingAlreadySetError
|
||||
&& ((BindingAlreadySetError) otherError).binding.getKey().equals(binding.getKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
List<List<Object>> sourcesList = new ArrayList<>();
|
||||
sourcesList.add(ImmutableList.of(original.getSource()));
|
||||
sourcesList.add(ImmutableList.of(binding.getSource()));
|
||||
sourcesList.addAll(
|
||||
mergeableErrors.stream()
|
||||
.map(e -> ((BindingAlreadySetError) e).binding.getSource())
|
||||
.map(ImmutableList::of)
|
||||
.collect(Collectors.toList()));
|
||||
formatter.format("%n%s%n", Messages.bold("Bound at:"));
|
||||
for (int i = 0; i < sourcesList.size(); i++) {
|
||||
ErrorFormatter.formatSources(i + 1, sourcesList.get(i), formatter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingAlreadySetError withSources(List<Object> newSources) {
|
||||
return new BindingAlreadySetError(binding, original, newSources);
|
||||
}
|
||||
}
|
|
@ -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<T>) command) {
|
||||
@Override
|
||||
public Boolean visit(ConstructorBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
try {
|
||||
ConstructorBindingImpl<T> 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<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
|
||||
T instance = binding.getInstance();
|
||||
@SuppressWarnings("unchecked") // safe to cast to binding<T> because
|
||||
// the processor was constructed w/ it
|
||||
Initializable<T> ref = initializer.requestInjection(
|
||||
injector, instance, (Binding<T>) binding, source, injectionPoints);
|
||||
ConstantFactory<? extends T> factory = new ConstantFactory<>(ref);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factory, source, scoping);
|
||||
putBinding(new InstanceBindingImpl<>(injector, key, source,
|
||||
scopedFactory, injectionPoints, instance));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(ProviderInstanceBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
javax.inject.Provider<? extends T> provider = binding.getUserSuppliedProvider();
|
||||
if (provider instanceof InternalProviderInstanceBindingImpl.Factory) {
|
||||
@SuppressWarnings("unchecked")
|
||||
InternalProviderInstanceBindingImpl.Factory<T> asProviderMethod =
|
||||
(InternalProviderInstanceBindingImpl.Factory<T>) provider;
|
||||
return visitInternalProviderInstanceBindingFactory(asProviderMethod);
|
||||
return command.acceptTargetVisitor(
|
||||
new Processor<T, Boolean>((BindingImpl<T>) command) {
|
||||
@Override
|
||||
public Boolean visit(ConstructorBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
try {
|
||||
ConstructorBindingImpl<T> 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<InjectionPoint> injectionPoints = binding.getInjectionPoints();
|
||||
Initializable<? extends javax.inject.Provider<? extends T>> initializable =
|
||||
initializer.requestInjection(injector, provider, null, source, injectionPoints);
|
||||
// always visited with Binding<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
InternalFactory<T> factory = new InternalFactoryToInitializableAdapter<>(
|
||||
initializable, source,
|
||||
injector.provisionListenerStore.get((ProviderInstanceBinding<T>) binding));
|
||||
InternalFactory<? extends T> 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<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Key<? extends javax.inject.Provider<? extends T>> providerKey = binding.getProviderKey();
|
||||
// always visited with Binding<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
BoundProviderFactory<T> boundProviderFactory = new BoundProviderFactory<>(
|
||||
injector, providerKey, source,
|
||||
injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding));
|
||||
bindingData.addCreationListener(boundProviderFactory);
|
||||
InternalFactory<? extends T> scopedFactory = Scoping.scope(
|
||||
key, injector, boundProviderFactory, source, scoping);
|
||||
putBinding(new LinkedProviderBindingImpl<>(
|
||||
injector, key, source, scopedFactory, scoping, providerKey));
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public Boolean visit(InstanceBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
|
||||
T instance = binding.getInstance();
|
||||
@SuppressWarnings("unchecked") // safe to cast to binding<T> because
|
||||
// the processor was constructed w/ it
|
||||
Initializable<T> ref =
|
||||
initializer.requestInjection(
|
||||
injector, instance, (Binding<T>) binding, source, injectionPoints);
|
||||
ConstantFactory<? extends T> factory = new ConstantFactory<>(ref);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factory, source, scoping);
|
||||
putBinding(
|
||||
new InstanceBindingImpl<T>(
|
||||
injector, key, source, scopedFactory, injectionPoints, instance));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(LinkedKeyBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Key<? extends T> linkedKey = binding.getLinkedKey();
|
||||
if (key.equals(linkedKey)) {
|
||||
errors.recursiveBinding();
|
||||
}
|
||||
FactoryProxy<T> factory = new FactoryProxy<>(injector, key, linkedKey, source);
|
||||
bindingData.addCreationListener(factory);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factory, source, scoping);
|
||||
putBinding(new LinkedBindingImpl<>(injector, key, source, scopedFactory, scoping, linkedKey));
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public Boolean visit(ProviderInstanceBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
javax.inject.Provider<? extends T> provider = binding.getUserSuppliedProvider();
|
||||
if (provider instanceof InternalProviderInstanceBindingImpl.Factory) {
|
||||
@SuppressWarnings("unchecked")
|
||||
InternalProviderInstanceBindingImpl.Factory<T> asProviderMethod =
|
||||
(InternalProviderInstanceBindingImpl.Factory<T>) provider;
|
||||
return visitInternalProviderInstanceBindingFactory(asProviderMethod);
|
||||
}
|
||||
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
|
||||
Initializable<? extends javax.inject.Provider<? extends T>> initializable =
|
||||
initializer.<javax.inject.Provider<? extends T>>requestInjection(
|
||||
injector, provider, null, source, injectionPoints);
|
||||
// always visited with Binding<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
InternalFactory<T> factory =
|
||||
new InternalFactoryToInitializableAdapter<T>(
|
||||
initializable,
|
||||
source,
|
||||
injector.provisionListenerStore.get((ProviderInstanceBinding<T>) binding));
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factory, source, scoping);
|
||||
putBinding(
|
||||
new ProviderInstanceBindingImpl<T>(
|
||||
injector, key, source, scopedFactory, scoping, provider, injectionPoints));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Handle ProviderMethods specially. */
|
||||
private Boolean visitInternalProviderInstanceBindingFactory(
|
||||
InternalProviderInstanceBindingImpl.Factory<T> provider) {
|
||||
InternalProviderInstanceBindingImpl<T> 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<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Key<? extends javax.inject.Provider<? extends T>> providerKey =
|
||||
binding.getProviderKey();
|
||||
// always visited with Binding<T>
|
||||
@SuppressWarnings("unchecked")
|
||||
BoundProviderFactory<T> boundProviderFactory =
|
||||
new BoundProviderFactory<T>(
|
||||
injector,
|
||||
providerKey,
|
||||
source,
|
||||
injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding));
|
||||
processedBindingData.addCreationListener(boundProviderFactory);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(
|
||||
key,
|
||||
injector,
|
||||
(InternalFactory<? extends T>) boundProviderFactory,
|
||||
source,
|
||||
scoping);
|
||||
putBinding(
|
||||
new LinkedProviderBindingImpl<T>(
|
||||
injector, key, source, scopedFactory, scoping, providerKey));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(UntargettedBinding<? extends T> untargetted) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public Boolean visit(LinkedKeyBinding<? extends T> binding) {
|
||||
prepareBinding();
|
||||
Key<? extends T> linkedKey = binding.getLinkedKey();
|
||||
if (key.equals(linkedKey)) {
|
||||
// TODO: b/168656899 check for transitive recursive binding
|
||||
errors.recursiveBinding(key, linkedKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(ExposedBinding<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
FactoryProxy<T> factory = new FactoryProxy<>(injector, key, linkedKey, source);
|
||||
processedBindingData.addCreationListener(factory);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factory, source, scoping);
|
||||
putBinding(
|
||||
new LinkedBindingImpl<T>(injector, key, source, scopedFactory, scoping, linkedKey));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(ConvertedConstantBinding<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
/** Handle ProviderMethods specially. */
|
||||
private Boolean visitInternalProviderInstanceBindingFactory(
|
||||
InternalProviderInstanceBindingImpl.Factory<T> provider) {
|
||||
InternalProviderInstanceBindingImpl<T> binding =
|
||||
new InternalProviderInstanceBindingImpl<T>(
|
||||
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<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
@Override
|
||||
public Boolean visit(UntargettedBinding<? extends T> untargetted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean visitOther(Binding<? extends T> binding) {
|
||||
throw new IllegalStateException("BindingProcessor should override all visitations");
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public Boolean visit(ExposedBinding<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(ConvertedConstantBinding<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(ProviderBinding<? extends T> binding) {
|
||||
throw new IllegalArgumentException("Cannot apply a non-module element");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean visitOther(Binding<? extends T> binding) {
|
||||
throw new IllegalStateException("BindingProcessor should override all visitations");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -200,8 +229,13 @@ final class BindingProcessor extends AbstractBindingProcessor {
|
|||
|
||||
private <T> void bindExposed(PrivateElements privateElements, Key<T> key) {
|
||||
ExposedKeyFactory<T> exposedKeyFactory = new ExposedKeyFactory<>(key, privateElements);
|
||||
bindingData.addCreationListener(exposedKeyFactory);
|
||||
putBinding(new ExposedBindingImpl<>(
|
||||
injector, privateElements.getExposedSource(key), key, exposedKeyFactory, privateElements));
|
||||
processedBindingData.addCreationListener(exposedKeyFactory);
|
||||
putBinding(
|
||||
new ExposedBindingImpl<T>(
|
||||
injector,
|
||||
privateElements.getExposedSource(key),
|
||||
key,
|
||||
exposedKeyFactory,
|
||||
privateElements));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Error reported by Guice when a key is already bound in one or more child injectors or private
|
||||
* modules.
|
||||
*/
|
||||
final class ChildBindingAlreadySetError extends InternalErrorDetail<ChildBindingAlreadySetError> {
|
||||
private final Key<?> key;
|
||||
private final ImmutableList<Object> existingSources;
|
||||
|
||||
ChildBindingAlreadySetError(Key<?> key, Iterable<Object> existingSoruces, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.CHILD_BINDING_ALREADY_SET,
|
||||
String.format(
|
||||
"Unable to create binding for %s because it was already configured on one or more"
|
||||
+ " child injectors or private modules.",
|
||||
Messages.convert(key)),
|
||||
sources,
|
||||
null);
|
||||
this.key = key;
|
||||
// Can't use ImmutableList.toImmutableList here because of b/156759807.
|
||||
this.existingSources =
|
||||
ImmutableList.copyOf(
|
||||
Streams.stream(existingSoruces)
|
||||
.map(source -> source == null ? "" : source)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeable(ErrorDetail<?> otherError) {
|
||||
return otherError instanceof ChildBindingAlreadySetError
|
||||
&& ((ChildBindingAlreadySetError) otherError).key.equals(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
formatter.format("%n%s%n", Messages.bold("Bound at:"));
|
||||
int index = 1;
|
||||
for (Object source : existingSources) {
|
||||
formatter.format("%-2s: ", index++);
|
||||
if (source.equals("")) {
|
||||
formatter.format("as a just-in-time binding%n");
|
||||
} else {
|
||||
new SourceFormatter(source, formatter, /* omitPreposition= */ true).format();
|
||||
}
|
||||
}
|
||||
List<List<Object>> sourcesList = new ArrayList<>();
|
||||
sourcesList.add(getSources());
|
||||
mergeableErrors.forEach(error -> sourcesList.add(error.getSources()));
|
||||
|
||||
List<List<Object>> filteredSources =
|
||||
sourcesList.stream()
|
||||
.map(this::trimSource)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
if (!filteredSources.isEmpty()) {
|
||||
formatter.format("%n%s%n", Messages.bold("Requested by:"));
|
||||
for (int i = 0; i < sourcesList.size(); i++) {
|
||||
ErrorFormatter.formatSources(i + 1, sourcesList.get(i), formatter);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/151482394): Detect if the key was bound in any PrivateModule and suggest exposing the
|
||||
// key in those cases.
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChildBindingAlreadySetError withSources(List<Object> newSources) {
|
||||
return new ChildBindingAlreadySetError(key, existingSources, newSources);
|
||||
}
|
||||
|
||||
/** Omit the key itself in the source list since the information is redundant. */
|
||||
private List<Object> trimSource(List<Object> sources) {
|
||||
return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -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<T> extends BindingImpl<T>
|
||||
implements ConstructorBinding<T>, DelayedInitialize {
|
||||
|
||||
private final Factory<T> factory;
|
||||
|
||||
private final InjectionPoint constructorInjectionPoint;
|
||||
|
||||
private ConstructorBindingImpl(InjectorImpl injector,
|
||||
Key<T> key,
|
||||
Object source,
|
||||
InternalFactory<? extends T> scopedFactory,
|
||||
Scoping scoping,
|
||||
Factory<T> factory,
|
||||
InjectionPoint constructorInjectionPoint) {
|
||||
private ConstructorBindingImpl(
|
||||
InjectorImpl injector,
|
||||
Key<T> key,
|
||||
Object source,
|
||||
InternalFactory<? extends T> scopedFactory,
|
||||
Scoping scoping,
|
||||
Factory<T> factory,
|
||||
InjectionPoint constructorInjectionPoint) {
|
||||
super(injector, key, source, scopedFactory, scoping);
|
||||
this.factory = factory;
|
||||
this.constructorInjectionPoint = constructorInjectionPoint;
|
||||
}
|
||||
|
||||
public ConstructorBindingImpl(Key<T> key, Object source, Scoping scoping,
|
||||
InjectionPoint constructorInjectionPoint, Set<InjectionPoint> injectionPoints) {
|
||||
public ConstructorBindingImpl(
|
||||
Key<T> key,
|
||||
Object source,
|
||||
Scoping scoping,
|
||||
InjectionPoint constructorInjectionPoint,
|
||||
Set<InjectionPoint> injectionPoints) {
|
||||
super(source, key, scoping);
|
||||
this.factory = new Factory<T>(false, key);
|
||||
ConstructionProxy<T> constructionProxy
|
||||
= new DefaultConstructionProxyFactory<T>(constructorInjectionPoint).create();
|
||||
this.factory = new Factory<>(false, key);
|
||||
ConstructionProxy<T> constructionProxy =
|
||||
new DefaultConstructionProxyFactory<T>(constructorInjectionPoint).create();
|
||||
this.constructorInjectionPoint = constructorInjectionPoint;
|
||||
factory.constructorInjector = new ConstructorInjector<T>(
|
||||
injectionPoints, constructionProxy, null, null);
|
||||
factory.constructorInjector =
|
||||
new ConstructorInjector<T>(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 <T> ConstructorBindingImpl<T> create(InjectorImpl injector, Key<T> key,
|
||||
InjectionPoint constructorInjector,
|
||||
Object source,
|
||||
Scoping scoping,
|
||||
Errors errors,
|
||||
boolean failIfNotLinked, boolean failIfNotExplicit)
|
||||
static <T> ConstructorBindingImpl<T> create(
|
||||
InjectorImpl injector,
|
||||
Key<T> 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<? super T> 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<T> extends BindingImpl<T>
|
|||
// 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<T> extends BindingImpl<T>
|
|||
Class<?> annotatedType = constructorInjector.getMember().getDeclaringClass();
|
||||
Class<? extends Annotation> 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<T> factoryFactory = new Factory<T>(failIfNotLinked, key);
|
||||
InternalFactory<? extends T> scopedFactory
|
||||
= Scoping.scope(key, injector, factoryFactory, source, scoping);
|
||||
Factory<T> factoryFactory = new Factory<>(failIfNotLinked, key);
|
||||
InternalFactory<? extends T> scopedFactory =
|
||||
Scoping.scope(key, injector, factoryFactory, source, scoping);
|
||||
|
||||
return new ConstructorBindingImpl<T>(
|
||||
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<T>) 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<T> extends BindingImpl<T>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of dependencies that can be iterated over to clean up stray JIT bindings.
|
||||
*/
|
||||
/** Returns a set of dependencies that can be iterated over to clean up stray JIT bindings. */
|
||||
Set<Dependency<?>> getInternalDependencies() {
|
||||
ImmutableSet.Builder<InjectionPoint> 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<T> extends BindingImpl<T>
|
|||
|
||||
@Override
|
||||
public Set<Dependency<?>> getDependencies() {
|
||||
return Dependency.forInjectionPoints(new ImmutableSet.Builder<InjectionPoint>()
|
||||
.add(getConstructor())
|
||||
.addAll(getInjectableMembers())
|
||||
.build());
|
||||
return Dependency.forInjectionPoints(
|
||||
new ImmutableSet.Builder<InjectionPoint>()
|
||||
.add(getConstructor())
|
||||
.addAll(getInjectableMembers())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -208,11 +202,17 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
|
|||
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<T> extends BindingImpl<T>
|
|||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get(InternalContext context, Dependency<?> dependency, boolean linked)
|
||||
throws InternalProvisionException {
|
||||
|
@ -259,6 +260,7 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T>
|
|||
if (localInjector == null) {
|
||||
throw new IllegalStateException("Constructor not ready");
|
||||
}
|
||||
|
||||
if (!linked && failIfNotLinked) {
|
||||
throw InternalProvisionException.jitDisabled(key);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.spi.Dependency;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.Collection;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Error reported by Guice when duplicate elements are found in a {@link Multibinder} that does not
|
||||
* permit duplicates.
|
||||
*/
|
||||
final class DuplicateElementError<T> extends InternalErrorDetail<DuplicateElementError<T>> {
|
||||
private final Key<Set<T>> setKey;
|
||||
private final ImmutableMultimap<T, Element<T>> elements;
|
||||
|
||||
DuplicateElementError(
|
||||
Key<Set<T>> setKey, List<Binding<T>> bindings, T[] values, List<Object> sources) {
|
||||
this(setKey, indexElements(bindings, values), sources);
|
||||
}
|
||||
|
||||
private DuplicateElementError(
|
||||
Key<Set<T>> setKey, ImmutableMultimap<T, Element<T>> elements, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.DUPLICATE_ELEMENT,
|
||||
String.format("Duplicate elements found in Multibinder %s.", Messages.convert(setKey)),
|
||||
sources,
|
||||
null);
|
||||
this.setKey = setKey;
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void formatDetail(List<ErrorDetail<?>> others, Formatter formatter) {
|
||||
formatter.format("%n%s%n", Messages.bold("Duplicates:"));
|
||||
int duplicateIndex = 1;
|
||||
for (Map.Entry<T, Collection<Element<T>>> entry : elements.asMap().entrySet()) {
|
||||
formatter.format("%-2s: ", duplicateIndex++);
|
||||
if (entry.getValue().size() > 1) {
|
||||
Set<String> valuesAsString =
|
||||
entry.getValue().stream()
|
||||
.map(element -> element.value.toString())
|
||||
.collect(Collectors.toSet());
|
||||
if (valuesAsString.size() == 1) {
|
||||
// String representation of the duplicates elements are the same, so only print out one.
|
||||
formatter.format("Element: %s%n", Messages.redBold(valuesAsString.iterator().next()));
|
||||
formatter.format(" Bound at:%n");
|
||||
int index = 1;
|
||||
for (Element<T> element : entry.getValue()) {
|
||||
formatter.format(" %-2s: ", index++);
|
||||
formatElement(element, formatter);
|
||||
}
|
||||
} else {
|
||||
// Print out all elements as string when there are different string representations of the
|
||||
// elements. To keep the logic simple, same strings are not grouped together unless all
|
||||
// elements have the same string represnetation. This means some strings may be printed
|
||||
// out multiple times.
|
||||
// There is no indentation for the first duplicate element.
|
||||
boolean indent = false;
|
||||
for (Element<T> element : entry.getValue()) {
|
||||
if (indent) {
|
||||
formatter.format(" ");
|
||||
} else {
|
||||
indent = true;
|
||||
}
|
||||
formatter.format("Element: %s%n", Messages.redBold(element.value.toString()));
|
||||
formatter.format(" Bound at: ");
|
||||
formatElement(element, formatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
formatter.format("%n%s%n", Messages.bold("Multibinder declared at:"));
|
||||
// Multibinder source includes the key of the set. Filter it out since it's not useful in the
|
||||
// printed error stack.
|
||||
List<Object> filteredSource =
|
||||
getSources().stream()
|
||||
.filter(
|
||||
source -> {
|
||||
if (source instanceof Dependency) {
|
||||
return !((Dependency<?>) source).getKey().equals(setKey);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
ErrorFormatter.formatSources(filteredSource, formatter);
|
||||
}
|
||||
|
||||
private void formatElement(Element<T> element, Formatter formatter) {
|
||||
Object source = element.binding.getSource();
|
||||
new SourceFormatter(
|
||||
source,
|
||||
formatter,
|
||||
/** omitPreposition= */
|
||||
true)
|
||||
.format();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplicateElementError<T> withSources(List<Object> newSources) {
|
||||
return new DuplicateElementError<>(setKey, elements, newSources);
|
||||
}
|
||||
|
||||
static <T> ImmutableMultimap<T, Element<T>> indexElements(List<Binding<T>> bindings, T[] values) {
|
||||
ImmutableMultimap.Builder<T, Element<T>> map = ImmutableMultimap.builder();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
map.put(values[i], new Element<T>(values[i], bindings.get(i)));
|
||||
}
|
||||
return map.build();
|
||||
}
|
||||
|
||||
static class Element<T> {
|
||||
T value;
|
||||
Binding<T> binding;
|
||||
|
||||
Element(T value, Binding<T> binding) {
|
||||
this.value = value;
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.Collection;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Error reported by Guice when a duplicate key is found in a {@link MapBinder} that does not permit
|
||||
* duplicates.
|
||||
*/
|
||||
final class DuplicateMapKeyError<K, V> extends InternalErrorDetail<DuplicateMapKeyError<K, V>> {
|
||||
private final Key<Map<K, V>> mapKey;
|
||||
private final Multimap<K, Binding<V>> duplicates;
|
||||
|
||||
DuplicateMapKeyError(
|
||||
Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates, List<Object> sources) {
|
||||
super(ErrorId.DUPLICATE_MAP_KEY, getDuplicateKeysMessage(mapKey, duplicates), sources, null);
|
||||
this.mapKey = mapKey;
|
||||
this.duplicates = duplicates;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void formatDetail(List<ErrorDetail<?>> others, Formatter formatter) {
|
||||
formatter.format("%n%s%n", Messages.bold("Duplicates:"));
|
||||
|
||||
for (Map.Entry<K, Collection<Binding<V>>> entry : duplicates.asMap().entrySet()) {
|
||||
formatter.format(" Key: \"%s\"%n", Messages.redBold(entry.getKey().toString()));
|
||||
formatter.format(" Bound at:%n");
|
||||
int index = 1;
|
||||
for (Binding<V> binding : entry.getValue()) {
|
||||
formatter.format(" %-2s: ", index++);
|
||||
new SourceFormatter(
|
||||
binding.getSource(),
|
||||
formatter,
|
||||
/** omitPreposition= */
|
||||
true)
|
||||
.format();
|
||||
}
|
||||
formatter.format("%n");
|
||||
}
|
||||
|
||||
formatter.format("%s%n", Messages.bold("MapBinder declared at:"));
|
||||
ErrorFormatter.formatSources(getSources(), formatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplicateMapKeyError<K, V> withSources(List<Object> newSources) {
|
||||
return new DuplicateMapKeyError<>(mapKey, duplicates, newSources);
|
||||
}
|
||||
|
||||
private static <K, V> String getDuplicateKeysMessage(
|
||||
Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates) {
|
||||
Set<K> duplicateKeys = duplicates.keySet();
|
||||
String mapBinderKey = Messages.convert(mapKey).toString();
|
||||
String firstDuplicateKey = duplicateKeys.iterator().next().toString();
|
||||
if (duplicateKeys.size() == 1) {
|
||||
return String.format("Duplicate key \"%s\" found in %s.", firstDuplicateKey, mapBinderKey);
|
||||
} else {
|
||||
return String.format(
|
||||
"\"%s\" and %s other duplicate keys found in %s.",
|
||||
firstDuplicateKey, duplicateKeys.size() - 1, mapBinderKey);
|
||||
}
|
||||
}
|
||||
}
|
33
src/main/java/com/google/inject/internal/ErrorFormatter.java
Normal file
33
src/main/java/com/google/inject/internal/ErrorFormatter.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
|
||||
/** Helper for formatting Guice errors. */
|
||||
final class ErrorFormatter {
|
||||
private ErrorFormatter() {}
|
||||
|
||||
/**
|
||||
* Format a list of sources to the given {@code formatter}, prefixed by the give {@code index}.
|
||||
*/
|
||||
static void formatSources(int index, List<Object> sources, Formatter formatter) {
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
Object source = sources.get(i);
|
||||
if (i == 0) {
|
||||
formatter.format("%-3s: ", index);
|
||||
} else {
|
||||
formatter.format(SourceFormatter.INDENT);
|
||||
}
|
||||
new SourceFormatter(source, formatter, i == 0).format();
|
||||
}
|
||||
}
|
||||
|
||||
/** Format a list of sources to the given {@code formatter}. */
|
||||
static void formatSources(List<Object> sources, Formatter formatter) {
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
Object source = sources.get(i);
|
||||
formatter.format(" ");
|
||||
new SourceFormatter(source, formatter, i == 0).format();
|
||||
}
|
||||
}
|
||||
}
|
65
src/main/java/com/google/inject/internal/ErrorId.java
Normal file
65
src/main/java/com/google/inject/internal/ErrorId.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
/** Enum used to identify a specific Guice error. */
|
||||
public enum ErrorId {
|
||||
AMBIGUOUS_TYPE_CONVERSION,
|
||||
AOP_DISABLED,
|
||||
AT_INJECT_REQUIRED,
|
||||
AT_TARGET_IS_MISSING_PARAMETER,
|
||||
BINDING_ALREADY_SET,
|
||||
BINDING_TO_GUICE_TYPE,
|
||||
BINDING_TO_PROVIDER,
|
||||
CAN_NOT_PROXY_CLASS,
|
||||
CHILD_BINDING_ALREADY_SET,
|
||||
CIRCULAR_PROXY_DISABLED,
|
||||
CONSTRUCTOR_NOT_DEFINED_BY_TYPE,
|
||||
CONVERSION_TYPE_ERROR,
|
||||
CONVERTER_RETURNED_NULL,
|
||||
DUPLICATE_BINDING_ANNOTATIONS,
|
||||
DUPLICATE_ELEMENT,
|
||||
DUPLICATE_MAP_KEY,
|
||||
DUPLICATE_SCOPES,
|
||||
DUPLICATE_SCOPE_ANNOTATIONS,
|
||||
ERROR_ENHANCING_CLASS,
|
||||
ERROR_INJECTING_CONSTRUCTOR,
|
||||
ERROR_INJECTING_METHOD,
|
||||
ERROR_IN_CUSTOM_PROVIDER,
|
||||
ERROR_IN_USER_CODE,
|
||||
ERROR_IN_USER_INJECTOR,
|
||||
ERROR_NOTIFYING_TYPE_LISTENER,
|
||||
EXPOSED_BUT_NOT_BOUND,
|
||||
INJECT_ABSTRACT_METHOD,
|
||||
INJECT_FINAL_FIELD,
|
||||
INJECT_INNER_CLASS,
|
||||
INJECT_METHOD_WITH_TYPE_PARAMETER,
|
||||
INJECT_RAW_MEMBERS_INJECTOR,
|
||||
INJECT_RAW_PROVIDER,
|
||||
INJECT_RAW_TYPE_LITERAL,
|
||||
JIT_BINDING_ALREADY_SET,
|
||||
JIT_DISABLED,
|
||||
JIT_DISABLED_IN_PARENT,
|
||||
KEY_NOT_FULLY_SPECIFIED,
|
||||
MISPLACED_BINDING_ANNOTATION,
|
||||
MISSING_CONSTANT_VALUES,
|
||||
MISSING_CONSTRUCTOR,
|
||||
MISSING_IMPLEMENTATION,
|
||||
MISSING_RUNTIME_RETENTION,
|
||||
MISSING_SCOPE_ANNOTATION,
|
||||
NOT_A_SUBTYPE,
|
||||
NULL_ELEMENT_IN_SET,
|
||||
NULL_INJECTED_INTO_NON_NULLABLE,
|
||||
NULL_VALUE_IN_MAP,
|
||||
OPTIONAL_CONSTRUCTOR,
|
||||
RECURSIVE_BINDING,
|
||||
RECURSIVE_IMPLEMENTATION_TYPE,
|
||||
RECURSIVE_PROVIDER_TYPE,
|
||||
SCOPE_ANNOTATION_ON_ABSTRACT_TYPE,
|
||||
SCOPE_NOT_FOUND,
|
||||
STATIC_INJECTION_ON_INTERFACE,
|
||||
SUBTYPE_NOT_PROVIDED,
|
||||
TOO_MANY_CONSTRUCTORS,
|
||||
VOID_PROVIDER_METHOD,
|
||||
|
||||
// All other uncommon type of errors
|
||||
OTHER;
|
||||
}
|
|
@ -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> 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<Class<?>> COMMON_AMBIGUOUS_TYPES =
|
||||
ImmutableSet.<Class<?>>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<Message> 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:
|
||||
*
|
||||
* <pre><code>Guice.createInjector(new AbstractModule() {
|
||||
* public void configure() {
|
||||
* bind(Runnable.class);
|
||||
* }
|
||||
* }</code></pre>
|
||||
*
|
||||
* ...and at provide-time errors:
|
||||
*
|
||||
* <pre><code>Guice.createInjector().getInstance(Runnable.class);</code></pre>
|
||||
*
|
||||
* Otherwise we need to know who's calling when resolving a just-in-time binding, which makes
|
||||
* things unnecessarily complex.
|
||||
*/
|
||||
public Errors missingImplementation(Key<?> key) {
|
||||
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 */
|
||||
<T> Errors missingImplementationWithHint(Key<T> 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<String> possibleMatches = new ArrayList<>();
|
||||
|
||||
// Check for other keys that may have the same type,
|
||||
// but not the same annotation
|
||||
TypeLiteral<T> type = key.getTypeLiteral();
|
||||
List<Binding<T>> sameTypes = injector.findBindingsByType(type);
|
||||
if (!sameTypes.isEmpty()) {
|
||||
sb.append(Messages.format("%n Did you mean?"));
|
||||
int howMany = Math.min(sameTypes.size(), MAX_MATCHING_TYPES_REPORTED);
|
||||
for (int i = 0; i < howMany; ++i) {
|
||||
// TODO: Look into a better way to prioritize suggestions. For example, possbily
|
||||
// use levenshtein distance of the given annotation vs actual annotation.
|
||||
sb.append(Messages.format("%n * %s", sameTypes.get(i).getKey()));
|
||||
}
|
||||
int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED;
|
||||
if (remaining > 0) {
|
||||
String plural = (remaining == 1) ? "" : "s";
|
||||
sb.append(Messages.format("%n %d more binding%s with other annotations.", remaining, plural));
|
||||
}
|
||||
} else {
|
||||
// For now, do a simple substring search for possibilities. This can help spot
|
||||
// issues when there are generics being used (such as a wrapper class) and the
|
||||
// user has forgotten they need to bind based on the wrapper, not the underlying
|
||||
// class. In the future, consider doing a strict in-depth type search.
|
||||
// TODO: Look into a better way to prioritize suggestions. For example, possbily
|
||||
// use levenshtein distance of the type literal strings.
|
||||
String want = type.toString();
|
||||
Map<Key<?>, Binding<?>> bindingMap = injector.getAllBindings();
|
||||
for (Key<?> bindingKey : bindingMap.keySet()) {
|
||||
String have = bindingKey.getTypeLiteral().toString();
|
||||
if (have.contains(want) || want.contains(have)) {
|
||||
Formatter fmt = new Formatter();
|
||||
Messages.formatSource(fmt, bindingMap.get(bindingKey).getSource());
|
||||
String match = String.format("%s bound%s", Messages.convert(bindingKey), fmt.toString());
|
||||
possibleMatches.add(match);
|
||||
// TODO: Consider a check that if there are more than some number of results,
|
||||
// don't suggest any.
|
||||
if (possibleMatches.size() > MAX_RELATED_TYPES_REPORTED) {
|
||||
// Early exit if we have found more than we need.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((possibleMatches.size() > 0) && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) {
|
||||
sb.append(Messages.format("%n Did you mean?"));
|
||||
for (String possibleMatch : possibleMatches) {
|
||||
sb.append(Messages.format("%n %s", possibleMatch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If where are no possibilities to suggest, then handle the case of missing
|
||||
// annotations on simple types. This is usually a bad idea.
|
||||
if (sameTypes.isEmpty()
|
||||
&& possibleMatches.isEmpty()
|
||||
&& key.getAnnotationType() == null
|
||||
&& COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) {
|
||||
// We don't recommend using such simple types without annotations.
|
||||
sb.append(Messages.format("%nThe key seems very generic, did you forget an annotation?"));
|
||||
}
|
||||
|
||||
return addMessage(sb.toString());
|
||||
MissingImplementationError<T> error =
|
||||
new MissingImplementationError<T>(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<? extends Annotation> annotation) {
|
||||
return addMessage(Messages.format("Please annotate %s with @Retention(RUNTIME).", annotation));
|
||||
return addMessage(
|
||||
ErrorId.MISSING_RUNTIME_RETENTION,
|
||||
format("Please annotate %s with @Retention(RUNTIME).", annotation));
|
||||
}
|
||||
|
||||
public Errors missingScopeAnnotation(Class<? extends Annotation> annotation) {
|
||||
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<? extends Annotation> scopeAnnotation) {
|
||||
return addMessage("No scope is bound to %s.", scopeAnnotation);
|
||||
return addMessage(
|
||||
new Message(
|
||||
GuiceInternal.GUICE_INTERNAL,
|
||||
ErrorId.SCOPE_NOT_FOUND,
|
||||
new ScopeNotFoundError(scopeAnnotation, getSources())));
|
||||
}
|
||||
|
||||
public Errors scopeAnnotationOnAbstractType(
|
||||
Class<? extends Annotation> 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<? extends Annotation> 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 <K, V> Errors duplicateMapKey(Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates) {
|
||||
return addMessage(
|
||||
new Message(
|
||||
GuiceInternal.GUICE_INTERNAL,
|
||||
ErrorId.DUPLICATE_MAP_KEY,
|
||||
new DuplicateMapKeyError<K, V>(mapKey, duplicates, getSources())));
|
||||
}
|
||||
|
||||
public Errors duplicateScopes(
|
||||
ScopeBinding existing, Class<? extends Annotation> annotationType, Scope scope) {
|
||||
return addMessage(
|
||||
ErrorId.DUPLICATE_SCOPES,
|
||||
"Scope %s is already bound to %s at %s.%n Cannot bind %s.",
|
||||
existing.getScope(),
|
||||
annotationType,
|
||||
existing.getSource(),
|
||||
scope);
|
||||
}
|
||||
|
||||
public Errors voidProviderMethod() {
|
||||
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<? extends Annotation> a, Class<? extends Annotation> b) {
|
||||
return addMessage("%s has more than one annotation annotated with @BindingAnnotation: "
|
||||
+ "%s and %s", member, a, b);
|
||||
public Errors duplicateBindingAnnotations(
|
||||
Member member, Class<? extends Annotation> a, Class<? extends Annotation> 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<? extends Annotation> a, Class<? extends Annotation> b) {
|
||||
return addMessage("More than one scope annotation was found: %s and %s.", a, b);
|
||||
return addMessage(
|
||||
ErrorId.DUPLICATE_SCOPE_ANNOTATIONS,
|
||||
"More than one scope annotation was found: %s and %s.",
|
||||
a,
|
||||
b);
|
||||
}
|
||||
|
||||
public Errors recursiveBinding() {
|
||||
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<Object> 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<Message> 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<Object> getSources() {
|
||||
private List<Object> getSources() {
|
||||
List<Object> 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<Message> getMessages() {
|
||||
if (root.errors == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
return new Ordering<Message>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ final class ExposedKeyFactory<T> implements InternalFactory<T>, CreationListener
|
|||
@Override
|
||||
public void notify(Errors errors) {
|
||||
InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector();
|
||||
BindingImpl<T> explicitBinding = privateInjector.state.getExplicitBinding(key);
|
||||
BindingImpl<T> explicitBinding = privateInjector.getBindingData().getExplicitBinding(key);
|
||||
|
||||
// validate that the child injector has its own factory. If the getInternalFactory() returns
|
||||
// this, then that child injector doesn't have a factory (and getExplicitBinding has returned
|
||||
|
@ -34,8 +34,10 @@ final class ExposedKeyFactory<T> implements InternalFactory<T>, CreationListener
|
|||
this.delegate = explicitBinding;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.io.Serializable;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
|
||||
/** Generic error message representing a Guice internal error. */
|
||||
public final class GenericErrorDetail extends InternalErrorDetail<GenericErrorDetail>
|
||||
implements Serializable {
|
||||
public GenericErrorDetail(
|
||||
ErrorId errorId, String message, List<Object> sources, Throwable cause) {
|
||||
super(errorId, checkNotNull(message, "message"), sources, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
Preconditions.checkArgument(mergeableErrors.isEmpty(), "Unexpected mergeable errors");
|
||||
List<Object> dependencies = getSources();
|
||||
for (Object source : Lists.reverse(dependencies)) {
|
||||
formatter.format(" ");
|
||||
new SourceFormatter(source, formatter, /* omitPreposition= */ false).format();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericErrorDetail withSources(List<Object> newSources) {
|
||||
return new GenericErrorDetail(errorId, getMessage(), newSources, getCause());
|
||||
}
|
||||
}
|
14
src/main/java/com/google/inject/internal/GuiceInternal.java
Normal file
14
src/main/java/com/google/inject/internal/GuiceInternal.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
/**
|
||||
* Class used for restricting APIs in other packages to only be used by this package.
|
||||
*
|
||||
* <p>Other packages can reference this class but only this package can reference an instance of it,
|
||||
* so adding this class as a method param ensures that only this package can call it (provided null
|
||||
* is disallowed).
|
||||
*/
|
||||
public final class GuiceInternal {
|
||||
static final GuiceInternal GUICE_INTERNAL = new GuiceInternal();
|
||||
|
||||
private GuiceInternal() {}
|
||||
}
|
|
@ -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<InjectorBindingData> parent;
|
||||
|
||||
// Must be a linked hashmap in order to preserve order of bindings in Modules.
|
||||
private final Map<Key<?>, Binding<?>> explicitBindingsMutable = Maps.newLinkedHashMap();
|
||||
|
@ -44,109 +50,97 @@ final class InheritingState implements State {
|
|||
private final List<TypeListenerBinding> typeListenerBindings = Lists.newArrayList();
|
||||
private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList();
|
||||
private final List<ModuleAnnotatedMethodScannerBinding> 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<TypeLiteral<?>, 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<InjectorBindingData> parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State parent() {
|
||||
public Optional<InjectorBindingData> parent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types
|
||||
@Override
|
||||
public <T> BindingImpl<T> getExplicitBinding(Key<T> key) {
|
||||
Binding<?> binding = explicitBindings.get(key);
|
||||
return binding != null ? (BindingImpl<T>) binding : parent.getExplicitBinding(key);
|
||||
if (binding == null && parent.isPresent()) {
|
||||
return parent.get().getExplicitBinding(key);
|
||||
}
|
||||
return (BindingImpl<T>) binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() {
|
||||
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<ProviderLookup<?>> getProviderLookupsThisLevel() {
|
||||
return providerLookups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putStaticInjectionRequest(StaticInjectionRequest staticInjectionRequest) {
|
||||
staticInjectionRequests.add(staticInjectionRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<StaticInjectionRequest> getStaticInjectionRequestsThisLevel() {
|
||||
return staticInjectionRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInjectionRequest(InjectionRequest<?> injectionRequest) {
|
||||
injectionRequests.add(injectionRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<InjectionRequest<?>> getInjectionRequestsThisLevel() {
|
||||
return injectionRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putMembersInjectorLookup(MembersInjectorLookup<?> membersInjectorLookup) {
|
||||
membersInjectorLookups.add(membersInjectorLookup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<MembersInjectorLookup<?>> getMembersInjectorLookupsThisLevel() {
|
||||
return membersInjectorLookups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScopeBinding getScopeBinding(Class<? extends Annotation> annotationType) {
|
||||
ScopeBinding scopeBinding = scopes.get(annotationType);
|
||||
return scopeBinding != null ? scopeBinding : parent.getScopeBinding(annotationType);
|
||||
if (scopeBinding == null && parent.isPresent()) {
|
||||
return parent.get().getScopeBinding(annotationType);
|
||||
}
|
||||
return scopeBinding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putScopeBinding(Class<? extends Annotation> annotationType, ScopeBinding scope) {
|
||||
scopes.put(annotationType, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ScopeBinding> getScopeBindingsThisLevel() {
|
||||
return scopes.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<TypeConverterBinding> 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<TypeListenerBinding> getTypeListenerBindings() {
|
||||
List<TypeListenerBinding> parentBindings = parent.getTypeListenerBindings();
|
||||
List<TypeListenerBinding> result =
|
||||
Lists.newArrayListWithCapacity(parentBindings.size() + typeListenerBindings.size());
|
||||
result.addAll(parentBindings);
|
||||
result.addAll(typeListenerBindings);
|
||||
return result;
|
||||
public ImmutableList<TypeListenerBinding> getTypeListenerBindings() {
|
||||
if (parent.isPresent()) {
|
||||
return new ImmutableList.Builder<TypeListenerBinding>()
|
||||
.addAll(parent.get().getTypeListenerBindings())
|
||||
.addAll(typeListenerBindings)
|
||||
.build();
|
||||
}
|
||||
return ImmutableList.copyOf(typeListenerBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeListenerBinding> getTypeListenerBindingsThisLevel() {
|
||||
return typeListenerBindings;
|
||||
public ImmutableList<TypeListenerBinding> getTypeListenerBindingsThisLevel() {
|
||||
return ImmutableList.copyOf(typeListenerBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProvisionListener(ProvisionListenerBinding listenerBinding) {
|
||||
provisionListenerBindings.add(listenerBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProvisionListenerBinding> getProvisionListenerBindings() {
|
||||
List<ProvisionListenerBinding> parentBindings = parent.getProvisionListenerBindings();
|
||||
List<ProvisionListenerBinding> result =
|
||||
Lists.newArrayListWithCapacity(parentBindings.size() + provisionListenerBindings.size());
|
||||
result.addAll(parentBindings);
|
||||
result.addAll(provisionListenerBindings);
|
||||
return result;
|
||||
public ImmutableList<ProvisionListenerBinding> getProvisionListenerBindings() {
|
||||
if (parent.isPresent()) {
|
||||
return new ImmutableList.Builder<ProvisionListenerBinding>()
|
||||
.addAll(parent.get().getProvisionListenerBindings())
|
||||
.addAll(provisionListenerBindings)
|
||||
.build();
|
||||
}
|
||||
return ImmutableList.copyOf(provisionListenerBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProvisionListenerBinding> getProvisionListenerBindingsThisLevel() {
|
||||
return provisionListenerBindings;
|
||||
public ImmutableList<ProvisionListenerBinding> getProvisionListenerBindingsThisLevel() {
|
||||
return ImmutableList.copyOf(provisionListenerBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScanner(ModuleAnnotatedMethodScannerBinding scanner) {
|
||||
scannerBindings.add(scanner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModuleAnnotatedMethodScannerBinding> getScannerBindings() {
|
||||
List<ModuleAnnotatedMethodScannerBinding> parentBindings = parent.getScannerBindings();
|
||||
List<ModuleAnnotatedMethodScannerBinding> result =
|
||||
Lists.newArrayListWithCapacity(parentBindings.size() + scannerBindings.size());
|
||||
result.addAll(parentBindings);
|
||||
result.addAll(scannerBindings);
|
||||
return result;
|
||||
public ImmutableList<ModuleAnnotatedMethodScannerBinding> getScannerBindings() {
|
||||
if (parent.isPresent()) {
|
||||
return new ImmutableList.Builder<ModuleAnnotatedMethodScannerBinding>()
|
||||
.addAll(parent.get().getScannerBindings())
|
||||
.addAll(scannerBindings)
|
||||
.build();
|
||||
}
|
||||
return ImmutableList.copyOf(scannerBindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ModuleAnnotatedMethodScannerBinding> getScannerBindingsThisLevel() {
|
||||
return scannerBindings;
|
||||
public ImmutableList<ModuleAnnotatedMethodScannerBinding> 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<Object> getSourcesForBlacklistedKey(Key<?> key) {
|
||||
return blacklistedKeys.getSources(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object lock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Class<? extends Annotation>, Scope> getScopes() {
|
||||
ImmutableMap.Builder<Class<? extends Annotation>, Scope> builder = ImmutableMap.builder();
|
||||
for (Map.Entry<Class<? extends Annotation>, 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<TypeLiteral<?>, Binding<?>> getIndexedExplicitBindings() {
|
||||
return indexedExplicitBindings;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,106 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Key;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A container for most just-in-time (JIT) binding data corresponding to an Injector. It
|
||||
* includes @Inject constructor bindings. It does not include {@link MembersInjectorStore} or {@link
|
||||
* ProvisionListenerCallbackStore}, which are still stored in {@link InjectorImpl}.
|
||||
*/
|
||||
final class InjectorJitBindingData {
|
||||
/** Just-in-time binding cache. Guarded by {@link #lock}. */
|
||||
private final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap();
|
||||
/**
|
||||
* Cache of Keys that we were unable to create JIT bindings for, so we don't keep trying. Guarded
|
||||
* by {@link #lock}.
|
||||
*/
|
||||
private final Set<Key<?>> failedJitBindings = Sets.newHashSet();
|
||||
|
||||
// The set of JIT binding keys that are banned for this particular injector, because a binding
|
||||
// already exists in a child injector. Guarded by {@link #lock}.
|
||||
private final WeakKeySet bannedKeys;
|
||||
|
||||
// The InjectorJitBindingData corresponding to the Injector's parent, if it exists.
|
||||
private final Optional<InjectorJitBindingData> parent;
|
||||
|
||||
/**
|
||||
* This lock is needed for threadsafe InjectorJitBindingData accesses. It corresponds to this
|
||||
* InjectorJitBindingData's highest ancestor.
|
||||
*/
|
||||
private final Object lock;
|
||||
|
||||
InjectorJitBindingData(Optional<InjectorJitBindingData> parent) {
|
||||
this.parent = parent;
|
||||
this.lock = parent.isPresent() ? parent.get().lock() : this;
|
||||
this.bannedKeys = new WeakKeySet(lock);
|
||||
}
|
||||
|
||||
Map<Key<?>, BindingImpl<?>> getJitBindings() {
|
||||
return Collections.unmodifiableMap(jitBindings);
|
||||
}
|
||||
|
||||
BindingImpl<?> getJitBinding(Key<?> key) {
|
||||
return jitBindings.get(key);
|
||||
}
|
||||
|
||||
void putJitBinding(Key<?> key, BindingImpl<?> binding) {
|
||||
jitBindings.put(key, binding);
|
||||
}
|
||||
|
||||
void removeJitBinding(Key<?> key) {
|
||||
jitBindings.remove(key);
|
||||
}
|
||||
|
||||
boolean isFailedJitBinding(Key<?> key) {
|
||||
return failedJitBindings.contains(key);
|
||||
}
|
||||
|
||||
void addFailedJitBinding(Key<?> key) {
|
||||
failedJitBindings.add(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbids the corresponding injector and its ancestors from creating a binding to {@code key}.
|
||||
* Child injectors ban their bound keys on their parent injectors to prevent just-in-time bindings
|
||||
* on the parent injector that would conflict, and pass along their InjectorBindingData to control
|
||||
* the banned key's lifetime.
|
||||
*/
|
||||
void banKey(Key<?> key, InjectorBindingData injectorBindingData, Object source) {
|
||||
banKeyInParent(key, injectorBindingData, source);
|
||||
bannedKeys.add(key, injectorBindingData, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #banKey(Key, InjectorBindingData, Object)} but we only begin banning the
|
||||
* binding at the parent level. This is used to prevent JIT bindings in the parent injector from
|
||||
* overriding explicit bindings declared in a child injector.
|
||||
*/
|
||||
void banKeyInParent(Key<?> key, InjectorBindingData injectorBindingData, Object source) {
|
||||
if (parent.isPresent()) {
|
||||
parent.get().banKey(key, injectorBindingData, source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@code key} is forbidden from being bound in the injector corresponding to this
|
||||
* data object. This indicates that one of the injector's children has bound the key.
|
||||
*/
|
||||
boolean isBannedKey(Key<?> key) {
|
||||
return bannedKeys.contains(key);
|
||||
}
|
||||
|
||||
/** Returns the source of a banned key. */
|
||||
Set<Object> getSourcesForBannedKey(Key<?> key) {
|
||||
return bannedKeys.getSources(key);
|
||||
}
|
||||
|
||||
Object lock() {
|
||||
return lock;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>The root InjectorShell extracts elements from its list of modules and processes these elements
|
||||
* to aggregate data that is used to populate its injector's fields. Child injectors are constructed
|
||||
* similarly, but using {@link PrivateElements} instead of modules.
|
||||
*
|
||||
* <p>It is necessary to create the root and child injectors in a single batch because there can be
|
||||
* bidirectional parent <-> child injector dependencies that require the entire tree of injectors to
|
||||
* be initialized together in the {@link InternalInjectorCreator}.
|
||||
*
|
||||
*/
|
||||
final class InjectorShell {
|
||||
|
||||
|
@ -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<Injector> key = Key.get(Injector.class);
|
||||
InjectorFactory injectorFactory = new InjectorFactory(injector);
|
||||
injector.state.putBinding(key,
|
||||
new ProviderInstanceBindingImpl<>(injector, key, SourceProvider.UNKNOWN_SOURCE,
|
||||
injectorFactory, Scoping.UNSCOPED, injectorFactory,
|
||||
ImmutableSet.of()));
|
||||
}
|
||||
|
||||
private static void bindStage(InjectorImpl injector, Stage stage) {
|
||||
Key<Stage> key = Key.get(Stage.class);
|
||||
InstanceBindingImpl<Stage> stageBinding = new InstanceBindingImpl<>(
|
||||
injector,
|
||||
key,
|
||||
SourceProvider.UNKNOWN_SOURCE,
|
||||
new ConstantFactory<>(Initializables.of(stage)),
|
||||
ImmutableSet.of(),
|
||||
stage);
|
||||
injector.state.putBinding(key, stageBinding);
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
private final List<Element> elements = Lists.newArrayList();
|
||||
private final List<Module> 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<? extends Module> 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<InjectorShell> 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<TypeListenerBinding> typeListenerBindings = injector.state.getTypeListenerBindings();
|
||||
List<TypeListenerBinding> typeListenerBindings =
|
||||
injector.getBindingData().getTypeListenerBindings();
|
||||
injector.membersInjectorStore = new MembersInjectorStore(injector, typeListenerBindings);
|
||||
List<ProvisionListenerBinding> 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<Injector> key = Key.get(Injector.class);
|
||||
InjectorFactory injectorFactory = new InjectorFactory(injector);
|
||||
injector
|
||||
.getBindingData()
|
||||
.putBinding(
|
||||
key,
|
||||
new ProviderInstanceBindingImpl<Injector>(
|
||||
injector,
|
||||
key,
|
||||
SourceProvider.UNKNOWN_SOURCE,
|
||||
injectorFactory,
|
||||
Scoping.UNSCOPED,
|
||||
injectorFactory,
|
||||
ImmutableSet.<InjectionPoint>of()));
|
||||
}
|
||||
|
||||
private static class InjectorFactory implements InternalFactory<Injector>, Provider<Injector> {
|
||||
|
@ -238,11 +253,66 @@ final class InjectorShell {
|
|||
return injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Provider<Injector>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Logger is a special case because it knows the injection point of the injected member. It's
|
||||
* the only binding that does this.
|
||||
*/
|
||||
private static void bindLogger(InjectorImpl injector) {
|
||||
Key<Logger> key = Key.get(Logger.class);
|
||||
LoggerFactory loggerFactory = new LoggerFactory();
|
||||
injector
|
||||
.getBindingData()
|
||||
.putBinding(
|
||||
key,
|
||||
new ProviderInstanceBindingImpl<Logger>(
|
||||
injector,
|
||||
key,
|
||||
SourceProvider.UNKNOWN_SOURCE,
|
||||
loggerFactory,
|
||||
Scoping.UNSCOPED,
|
||||
loggerFactory,
|
||||
ImmutableSet.<InjectionPoint>of()));
|
||||
}
|
||||
|
||||
private static class LoggerFactory implements InternalFactory<Logger>, Provider<Logger> {
|
||||
@Override
|
||||
public Logger get(InternalContext context, Dependency<?> dependency, boolean linked) {
|
||||
InjectionPoint injectionPoint = dependency.getInjectionPoint();
|
||||
return injectionPoint == null
|
||||
? Logger.getAnonymousLogger()
|
||||
: Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger get() {
|
||||
return Logger.getAnonymousLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Provider<Logger>";
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindStage(InjectorImpl injector, Stage stage) {
|
||||
Key<Stage> key = Key.get(Stage.class);
|
||||
InstanceBindingImpl<Stage> stageBinding =
|
||||
new InstanceBindingImpl<Stage>(
|
||||
injector,
|
||||
key,
|
||||
SourceProvider.UNKNOWN_SOURCE,
|
||||
new ConstantFactory<Stage>(Initializables.of(stage)),
|
||||
ImmutableSet.<InjectionPoint>of(),
|
||||
stage);
|
||||
injector.getBindingData().putBinding(key, stageBinding);
|
||||
}
|
||||
|
||||
private static class RootModule implements Module {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents an error created by Guice as opposed to custom error added to the binder from
|
||||
* application code.
|
||||
*/
|
||||
abstract class InternalErrorDetail<T extends ErrorDetail<T>> extends ErrorDetail<T> {
|
||||
// A list of errors that have help documentation.
|
||||
private static final ImmutableSet<ErrorId> DOCUMENTED_ERRORS =
|
||||
ImmutableSet.<ErrorId>builder()
|
||||
.add(ErrorId.BINDING_ALREADY_SET)
|
||||
.add(ErrorId.CAN_NOT_PROXY_CLASS)
|
||||
.add(ErrorId.CIRCULAR_PROXY_DISABLED)
|
||||
.add(ErrorId.DUPLICATE_BINDING_ANNOTATIONS)
|
||||
.add(ErrorId.DUPLICATE_ELEMENT)
|
||||
.add(ErrorId.DUPLICATE_SCOPES)
|
||||
.add(ErrorId.ERROR_INJECTING_CONSTRUCTOR)
|
||||
.add(ErrorId.ERROR_INJECTING_METHOD)
|
||||
.add(ErrorId.ERROR_IN_CUSTOM_PROVIDER)
|
||||
.add(ErrorId.INJECT_INNER_CLASS)
|
||||
.add(ErrorId.MISSING_CONSTRUCTOR)
|
||||
.add(ErrorId.MISSING_IMPLEMENTATION)
|
||||
.add(ErrorId.NULL_INJECTED_INTO_NON_NULLABLE)
|
||||
.add(ErrorId.NULL_VALUE_IN_MAP)
|
||||
.add(ErrorId.SCOPE_NOT_FOUND)
|
||||
.add(ErrorId.TOO_MANY_CONSTRUCTORS)
|
||||
.build();
|
||||
|
||||
private static final String DOC_BASE_URL = "https://github.com/google/guice/wiki/";
|
||||
|
||||
protected final ErrorId errorId;
|
||||
|
||||
protected InternalErrorDetail(
|
||||
ErrorId errorId, String message, List<Object> sources, Throwable cause) {
|
||||
super(message, sources, cause);
|
||||
this.errorId = errorId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Optional<String> getLearnMoreLink() {
|
||||
if (DOCUMENTED_ERRORS.contains(errorId)) {
|
||||
return Optional.of(DOC_BASE_URL + errorId.name());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Optional<String> getErrorIdentifier() {
|
||||
if (errorId == ErrorId.OTHER) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String id = "Guice/" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, errorId.name());
|
||||
return Optional.of(id);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Runtime bytecode generation (instead of reflection) will be used when Guice need to
|
||||
* invoke application code.
|
||||
* <li>Method interception.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Bytecode generation is generally faster than using reflection when invoking application
|
||||
* code, however, it can use more memory and slower in certain cases due to the time spent in
|
||||
* generating the classes. If you prefer to use reflection over bytecode generation then set
|
||||
* {@link BytecodeGenOption} to {@code DISABLED}.
|
||||
*/
|
||||
public enum BytecodeGenOption {
|
||||
/**
|
||||
* Bytecode generation is disabled and using features that require it such as method
|
||||
* interception will throw errors at run time.
|
||||
*/
|
||||
DISABLED,
|
||||
/** Bytecode generation is enabled. */
|
||||
ENABLED,
|
||||
}
|
||||
|
||||
/** Options for enable or disable using ansi color in error messages. */
|
||||
public enum ColorizeOption {
|
||||
AUTO {
|
||||
@Override
|
||||
boolean enabled() {
|
||||
return System.console() != null && System.getenv("TERM") != null;
|
||||
}
|
||||
},
|
||||
ON {
|
||||
@Override
|
||||
boolean enabled() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
OFF {
|
||||
@Override
|
||||
boolean enabled() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
abstract boolean enabled();
|
||||
}
|
||||
|
||||
public static IncludeStackTraceOption getIncludeStackTraceOption() {
|
||||
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 extends Enum<T>> T getSystemOption(final String name, T defaultValue,
|
||||
T secureValue) {
|
||||
Class<T> enumType = defaultValue.getDeclaringClass();
|
||||
String value = null;
|
||||
try {
|
||||
String value = AccessController.doPrivileged((PrivilegedAction<String>) ()
|
||||
-> System.getProperty(name));
|
||||
value =
|
||||
AccessController.doPrivileged(
|
||||
new PrivilegedAction<String>() {
|
||||
@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() {}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>Injector construction happens in two phases.
|
||||
*
|
||||
* <ol>
|
||||
* <li>Static building. In this phase, we interpret commands, create bindings, and inspect
|
||||
* dependencies. During this phase, we hold a lock to ensure consistency with parent injectors.
|
||||
* No user code is executed in this phase.</li>
|
||||
* <li>Dynamic injection. In this phase, we call user code. We inject members that requested
|
||||
* injection. This may require user's objects be created and their providers be called. And we
|
||||
* 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}</li>
|
||||
* <li>Static building. In this phase, we interpret commands, create bindings, and inspect
|
||||
* dependencies. During this phase, we hold a lock to ensure consistency with parent
|
||||
* injectors. No user code is executed in this phase.
|
||||
* <li>Dynamic injection. In this phase, we call user code. We inject members that requested
|
||||
* injection. This may require user's objects be created and their providers be called. And we
|
||||
* 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}
|
||||
* </ol>
|
||||
*
|
||||
* @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<BindingImpl<?>> candidateBindings = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe
|
||||
Collection<BindingImpl<?>> bindingsAtThisLevel =
|
||||
(Collection) injector.state.getExplicitBindingsThisLevel().values();
|
||||
List<BindingImpl<?>> 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;
|
||||
|
||||
|
|
|
@ -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<Dependency<?>> warnedDependencies =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private static final Logger logger = Logger.getLogger(Guice.class.getName());
|
||||
private static final Set<Dependency<?>> warnedDependencies =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<Dependency<?>, 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<Message> 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<? extends javax.inject.Provider<?>> 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<Message> 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<Object> sourcesToPrepend = new ArrayList<>();
|
||||
private final ImmutableList<Message> errors;
|
||||
|
||||
private InternalProvisionException(Message error) {
|
||||
this(ImmutableList.of(error));
|
||||
}
|
||||
|
||||
private InternalProvisionException(Iterable<Message> 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.
|
||||
*
|
||||
* <p>See {@link Errors#withSource(Object)}
|
||||
*
|
||||
* <p>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<? extends javax.inject.Provider<?>> 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<Message> getErrors() {
|
||||
ImmutableList.Builder<Message> builder = ImmutableList.builder();
|
||||
// reverse them since sources are added as the exception propagates (so the first source is the
|
||||
// last one added)
|
||||
List<Object> 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<Object> sourcesToPrepend = new ArrayList<>();
|
||||
private final ImmutableList<Message> errors;
|
||||
|
||||
InternalProvisionException(Message error) {
|
||||
this(ImmutableList.of(error));
|
||||
}
|
||||
|
||||
private InternalProvisionException(Iterable<Message> 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.
|
||||
*
|
||||
* <p>See {@link Errors#withSource(Object)}
|
||||
*
|
||||
* <p>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<Message> getErrors() {
|
||||
ImmutableList.Builder<Message> builder = ImmutableList.builder();
|
||||
// reverse them since sources are added as the exception propagates (so the first source is the
|
||||
// last one added)
|
||||
List<Object> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Object> sources, Message message) {
|
||||
List<Object> 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<Message> errorMessages) {
|
||||
Formatter fmt = new Formatter().format(heading).format(":%n%n");
|
||||
int index = 1;
|
||||
boolean displayCauses = getOnlyCause(errorMessages) == null;
|
||||
|
||||
List<ErrorDetail<?>> remainingErrors =
|
||||
errorMessages.stream().map(Message::getErrorDetail).collect(Collectors.toList());
|
||||
|
||||
Map<Equivalence.Wrapper<Throwable>, 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<Boolean, List<ErrorDetail<?>>> partitionedByMergeable =
|
||||
remainingErrors.subList(1, remainingErrors.size()).stream()
|
||||
.collect(Collectors.partitioningBy(currentError::isMergeable));
|
||||
|
||||
List<Object> 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<Throwable> 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<Object> sources, String messageFormat, Object... arguments) {
|
||||
public static Message create(
|
||||
ErrorId errorId,
|
||||
Throwable cause,
|
||||
List<Object> sources,
|
||||
String messageFormat,
|
||||
Object... arguments) {
|
||||
String message = format(messageFormat, arguments);
|
||||
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<String> modules = Lists.newArrayList(elementSource.getModuleClassNames());
|
||||
// Insert any original element sources w/ module info into the path.
|
||||
while (elementSource.getOriginalElementSource() != null) {
|
||||
elementSource = elementSource.getOriginalElementSource();
|
||||
modules.addAll(0, elementSource.getModuleClassNames());
|
||||
}
|
||||
if (modules.size() <= 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Ideally we'd do:
|
||||
// return Joiner.on(" -> ")
|
||||
// .appendTo(new StringBuilder(" (via modules: "), Lists.reverse(modules))
|
||||
// .append(")").toString();
|
||||
// ... but for some reason we can't find Lists.reverse, so do it the boring way.
|
||||
StringBuilder builder = new StringBuilder(" (via modules: ");
|
||||
for (int i = modules.size() - 1; i >= 0; i--) {
|
||||
builder.append(modules.get(i));
|
||||
if (i != 0) {
|
||||
builder.append(" -> ");
|
||||
}
|
||||
}
|
||||
builder.append(")");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static void formatSource(Formatter formatter, Object source) {
|
||||
ElementSource elementSource = null;
|
||||
if (source instanceof ElementSource) {
|
||||
elementSource = (ElementSource) source;
|
||||
source = elementSource.getDeclaringSource();
|
||||
}
|
||||
formatSource(formatter, source, elementSource);
|
||||
}
|
||||
|
||||
static void formatSource(Formatter formatter, Object source, ElementSource elementSource) {
|
||||
String modules = moduleSourceString(elementSource);
|
||||
if (source instanceof Dependency) {
|
||||
Dependency<?> dependency = (Dependency<?>) source;
|
||||
InjectionPoint injectionPoint = dependency.getInjectionPoint();
|
||||
if (injectionPoint != null) {
|
||||
formatInjectionPoint(formatter, dependency, injectionPoint, elementSource);
|
||||
} else {
|
||||
formatSource(formatter, dependency.getKey(), elementSource);
|
||||
}
|
||||
|
||||
} else if (source instanceof InjectionPoint) {
|
||||
formatInjectionPoint(formatter, null, (InjectionPoint) source, elementSource);
|
||||
|
||||
} else if (source instanceof Class) {
|
||||
formatter.format(" at %s%s%n", StackTraceElements.forType((Class<?>) source), modules);
|
||||
|
||||
} else if (source instanceof Member) {
|
||||
formatter.format(" at %s%s%n", StackTraceElements.forMember((Member) source), modules);
|
||||
|
||||
} else if (source instanceof TypeLiteral) {
|
||||
formatter.format(" while locating %s%s%n", source, modules);
|
||||
|
||||
} else if (source instanceof Key) {
|
||||
Key<?> key = (Key<?>) source;
|
||||
formatter.format(" while locating %s%n", convert(key, elementSource));
|
||||
|
||||
} else if (source instanceof Thread) {
|
||||
formatter.format(" in thread %s%n", source);
|
||||
|
||||
} else {
|
||||
formatter.format(" at %s%s%n", source, modules);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatInjectionPoint(
|
||||
Formatter formatter,
|
||||
Dependency<?> dependency,
|
||||
InjectionPoint injectionPoint,
|
||||
ElementSource elementSource) {
|
||||
Member member = injectionPoint.getMember();
|
||||
Class<? extends Member> memberType = Classes.memberType(member);
|
||||
|
||||
if (memberType == Field.class) {
|
||||
dependency = injectionPoint.getDependencies().get(0);
|
||||
formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource));
|
||||
formatter.format(" for field at %s%n", StackTraceElements.forMember(member));
|
||||
|
||||
} else if (dependency != null) {
|
||||
formatter.format(" while locating %s%n", convert(dependency.getKey(), elementSource));
|
||||
formatter.format(" for %s%n", formatParameter(dependency));
|
||||
|
||||
} else {
|
||||
formatSource(formatter, injectionPoint.getMember());
|
||||
}
|
||||
}
|
||||
|
||||
static String formatParameter(Dependency<?> dependency) {
|
||||
int ordinal = dependency.getParameterIndex() + 1;
|
||||
return String.format(
|
||||
"the %s%s parameter of %s",
|
||||
ordinal,
|
||||
getOrdinalSuffix(ordinal),
|
||||
StackTraceElements.forMember(dependency.getInjectionPoint().getMember()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps {@code 1} to the string {@code "1st"} ditto for all non-negative numbers
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers">
|
||||
* https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers</a>
|
||||
*/
|
||||
private static String getOrdinalSuffix(int ordinal) {
|
||||
// negative ordinals don't make sense, we allow zero though because we are programmers
|
||||
checkArgument(ordinal >= 0);
|
||||
if ((ordinal / 10) % 10 == 1) {
|
||||
// all the 'teens' are weird
|
||||
return "th";
|
||||
} else {
|
||||
// could use a lookup table? any better?
|
||||
switch (ordinal % 10) {
|
||||
case 1:
|
||||
return "st";
|
||||
case 2:
|
||||
return "nd";
|
||||
case 3:
|
||||
return "rd";
|
||||
default:
|
||||
return "th";
|
||||
}
|
||||
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<Converter<?>> converters =
|
||||
ImmutableList.of(
|
||||
new Converter<>(Class.class) {
|
||||
new Converter<Class>(Class.class) {
|
||||
@Override
|
||||
public String toString(Class c) {
|
||||
return c.getName();
|
||||
}
|
||||
},
|
||||
new Converter<>(Member.class) {
|
||||
new Converter<Member>(Member.class) {
|
||||
@Override
|
||||
public String toString(Member member) {
|
||||
return Classes.toString(member);
|
||||
}
|
||||
},
|
||||
new Converter<>(Key.class) {
|
||||
new Converter<Key>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
|
||||
/** Error reported when Guice can't find an useable constructor to create objects. */
|
||||
final class MissingConstructorError extends InternalErrorDetail<MissingConstructorError> {
|
||||
private final TypeLiteral<?> type;
|
||||
private final boolean atInjectRequired;
|
||||
|
||||
MissingConstructorError(TypeLiteral<?> type, boolean atInjectRequired, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.MISSING_CONSTRUCTOR,
|
||||
"No injectable constructor for type " + type + ".",
|
||||
sources,
|
||||
null);
|
||||
this.type = type;
|
||||
this.atInjectRequired = atInjectRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeable(ErrorDetail<?> other) {
|
||||
if (other instanceof MissingConstructorError) {
|
||||
MissingConstructorError otherMissing = (MissingConstructorError) other;
|
||||
return Objects.equal(type, otherMissing.type)
|
||||
&& Objects.equal(atInjectRequired, otherMissing.atInjectRequired);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
formatter.format("%n");
|
||||
Class<?> rawType = type.getRawType();
|
||||
if (atInjectRequired) {
|
||||
formatter.format(
|
||||
"Injector is configured to require @Inject constructors but %s does not have a @Inject"
|
||||
+ " annotated constructor.%n",
|
||||
rawType);
|
||||
} else {
|
||||
Constructor<?> noArgConstructor = null;
|
||||
try {
|
||||
noArgConstructor = type.getRawType().getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (noArgConstructor == null) {
|
||||
formatter.format(
|
||||
"%s does not have a @Inject annotated constructor or a no-arg constructor.%n", rawType);
|
||||
} else if (Modifier.isPrivate(noArgConstructor.getModifiers())
|
||||
&& !Modifier.isPrivate(rawType.getModifiers())) {
|
||||
formatter.format(
|
||||
"%s has a private no-arg constructor but the class is not private. Guice can only use"
|
||||
+ " a private no-arg constructor if it is defined in a private class.%n",
|
||||
rawType);
|
||||
}
|
||||
}
|
||||
formatter.format("%n");
|
||||
|
||||
List<List<Object>> sourcesList = new ArrayList<>();
|
||||
sourcesList.add(getSources());
|
||||
mergeableErrors.forEach(error -> sourcesList.add(error.getSources()));
|
||||
|
||||
formatter.format("%s%n", Messages.bold("Requested by:"));
|
||||
int sourceListIndex = 1;
|
||||
for (List<Object> sources : sourcesList) {
|
||||
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MissingConstructorError withSources(List<Object> newSources) {
|
||||
return new MissingConstructorError(type, atInjectRequired, newSources);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Error reported by Guice when a key is not bound in the injector. */
|
||||
final class MissingImplementationError<T>
|
||||
extends InternalErrorDetail<MissingImplementationError<T>> {
|
||||
|
||||
private final Key<T> key;
|
||||
private final ImmutableList<String> suggestions;
|
||||
|
||||
public MissingImplementationError(Key<T> key, Injector injector, List<Object> sources) {
|
||||
this(key, MissingImplementationErrorHints.getSuggestions(key, injector), sources);
|
||||
}
|
||||
|
||||
private MissingImplementationError(
|
||||
Key<T> key, ImmutableList<String> suggestions, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.MISSING_IMPLEMENTATION,
|
||||
String.format("No implementation for %s was bound.", Messages.convert(key)),
|
||||
sources,
|
||||
null);
|
||||
this.key = key;
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeable(ErrorDetail<?> otherError) {
|
||||
return otherError instanceof MissingImplementationError
|
||||
&& ((MissingImplementationError) otherError).key.equals(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
if (!suggestions.isEmpty()) {
|
||||
suggestions.forEach(formatter::format);
|
||||
}
|
||||
List<List<Object>> sourcesList = new ArrayList<>();
|
||||
sourcesList.add(getSources());
|
||||
sourcesList.addAll(
|
||||
mergeableErrors.stream().map(ErrorDetail::getSources).collect(Collectors.toList()));
|
||||
|
||||
List<List<Object>> filteredSourcesList =
|
||||
sourcesList.stream()
|
||||
.map(this::trimSource)
|
||||
.filter(sources -> !sources.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!filteredSourcesList.isEmpty()) {
|
||||
formatter.format("%n%s%n", Messages.bold("Requested by:"));
|
||||
int sourceListIndex = 1;
|
||||
for (List<Object> sources : filteredSourcesList) {
|
||||
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MissingImplementationError<T> withSources(List<Object> newSources) {
|
||||
return new MissingImplementationError<T>(key, suggestions, newSources);
|
||||
}
|
||||
|
||||
/** Omit the key itself in the source list since the information is redundant. */
|
||||
private List<Object> trimSource(List<Object> sources) {
|
||||
return sources.stream().filter(source -> !source.equals(this.key)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.spi.BindingSourceRestriction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO(b/165344346): Migrate to use suggest hints API.
|
||||
/** Helper class to find hints for {@link MissingImplementationError}. */
|
||||
final class MissingImplementationErrorHints {
|
||||
|
||||
private MissingImplementationErrorHints() {}
|
||||
|
||||
/** When a binding is not found, show at most this many bindings with the same type */
|
||||
private static final int MAX_MATCHING_TYPES_REPORTED = 3;
|
||||
|
||||
/** When a binding is not found, show at most this many bindings that have some similarities */
|
||||
private static final int MAX_RELATED_TYPES_REPORTED = 3;
|
||||
|
||||
/**
|
||||
* If the key is unknown and it is one of these types, it generally means there is a missing
|
||||
* annotation.
|
||||
*/
|
||||
private static final ImmutableSet<Class<?>> COMMON_AMBIGUOUS_TYPES =
|
||||
ImmutableSet.<Class<?>>builder()
|
||||
.add(Object.class)
|
||||
.add(String.class)
|
||||
.addAll(Primitives.allWrapperTypes())
|
||||
.build();
|
||||
|
||||
static <T> ImmutableList<String> getSuggestions(Key<T> key, Injector injector) {
|
||||
ImmutableList.Builder<String> suggestions = ImmutableList.builder();
|
||||
TypeLiteral<T> type = key.getTypeLiteral();
|
||||
|
||||
BindingSourceRestriction.getMissingImplementationSuggestion(GuiceInternal.GUICE_INTERNAL, key)
|
||||
.ifPresent(suggestions::add);
|
||||
|
||||
// Keys which have similar strings as the desired key
|
||||
List<String> possibleMatches = new ArrayList<>();
|
||||
List<Binding<T>> sameTypes = injector.findBindingsByType(type);
|
||||
if (!sameTypes.isEmpty()) {
|
||||
suggestions.add("%nDid you mean?");
|
||||
int howMany = min(sameTypes.size(), MAX_MATCHING_TYPES_REPORTED);
|
||||
for (int i = 0; i < howMany; ++i) {
|
||||
// TODO: Look into a better way to prioritize suggestions. For example, possbily
|
||||
// use levenshtein distance of the given annotation vs actual annotation.
|
||||
suggestions.add(Messages.format("%n * %s", sameTypes.get(i).getKey()));
|
||||
}
|
||||
int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED;
|
||||
if (remaining > 0) {
|
||||
String plural = (remaining == 1) ? "" : "s";
|
||||
suggestions.add(
|
||||
Messages.format("%n %d more binding%s with other annotations.", remaining, plural));
|
||||
}
|
||||
suggestions.add("%n");
|
||||
} else {
|
||||
// For now, do a simple substring search for possibilities. This can help spot
|
||||
// issues when there are generics being used (such as a wrapper class) and the
|
||||
// user has forgotten they need to bind based on the wrapper, not the underlying
|
||||
// class. In the future, consider doing a strict in-depth type search.
|
||||
// TODO: Look into a better way to prioritize suggestions. For example, possbily
|
||||
// use levenshtein distance of the type literal strings.
|
||||
String want = type.toString();
|
||||
Map<Key<?>, Binding<?>> bindingMap = injector.getAllBindings();
|
||||
for (Key<?> bindingKey : bindingMap.keySet()) {
|
||||
String have = bindingKey.getTypeLiteral().toString();
|
||||
if (have.contains(want) || want.contains(have)) {
|
||||
Formatter fmt = new Formatter();
|
||||
fmt.format("%s bound ", Messages.convert(bindingKey));
|
||||
new SourceFormatter(
|
||||
bindingMap.get(bindingKey).getSource(), fmt, /* omitPreposition= */ false)
|
||||
.format();
|
||||
possibleMatches.add(fmt.toString());
|
||||
// TODO: Consider a check that if there are more than some number of results,
|
||||
// don't suggest any.
|
||||
if (possibleMatches.size() > MAX_RELATED_TYPES_REPORTED) {
|
||||
// Early exit if we have found more than we need.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!possibleMatches.isEmpty() && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) {
|
||||
suggestions.add("%nDid you mean?");
|
||||
for (String possibleMatch : possibleMatches) {
|
||||
suggestions.add(Messages.format("%n %s", possibleMatch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If where are no possibilities to suggest, then handle the case of missing
|
||||
// annotations on simple types. This is usually a bad idea.
|
||||
if (sameTypes.isEmpty()
|
||||
&& possibleMatches.isEmpty()
|
||||
&& key.getAnnotationType() == null
|
||||
&& COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) {
|
||||
// We don't recommend using such simple types without annotations.
|
||||
suggestions.add("%nThe key seems very generic, did you forget an annotation?");
|
||||
}
|
||||
|
||||
return suggestions.build();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Munges an error message to remove/shorten package names and adds a legend at the end.
|
||||
*/
|
||||
final class PackageNameCompressor {
|
||||
|
||||
static final String LEGEND_HEADER =
|
||||
"\n\n======================\nFull classname legend:\n======================\n";
|
||||
static final String LEGEND_FOOTER =
|
||||
"========================\nEnd of classname legend:\n========================\n";
|
||||
|
||||
private static final ImmutableSet<String> PACKAGES_SKIPPED_IN_LEGEND = ImmutableSet.of(
|
||||
"java.lang.",
|
||||
"java.util.");
|
||||
|
||||
private static final Splitter PACKAGE_SPLITTER = Splitter.on('.');
|
||||
|
||||
private static final Joiner PACKAGE_JOINER = Joiner.on('.');
|
||||
|
||||
// TODO(erichang): Consider validating this regex by also passing in all of the known types from
|
||||
// keys, module names, component names, etc and checking against that list. This may have some
|
||||
// extra complications with taking apart types like List<Foo> to get the inner class names.
|
||||
private static final Pattern CLASSNAME_PATTERN =
|
||||
// Match lowercase package names with trailing dots. Start with a non-word character so we
|
||||
// don't match substrings in like Bar.Foo and match the com.foo.Foo. Require at least 2
|
||||
// package names to avoid matching non package names like a sentence ending with a period and
|
||||
// starting with an upper case letter without space, for example:
|
||||
// foo.Must in message "Invalid value for foo.Must not be empty." should not be compressed.
|
||||
// Start a group to not include the non-word character.
|
||||
Pattern.compile(
|
||||
"[\\W](([a-z_0-9]++[.]){2,}+"
|
||||
// Then match a name starting with an uppercase letter. This is the outer class name.
|
||||
+ "[A-Z][\\w$]*)");
|
||||
|
||||
// Pattern used to filter out quoted strings that should not have their package name compressed.
|
||||
// Picked '"' here because Guice uses it when including a string literal in an error message. This
|
||||
// will allow user to include class names in the error message and disable the compressor by
|
||||
// putting the name in a pair of '"'.
|
||||
// The pattern without the escapes: ([^"]+)((")?[^"\r\n]*")?
|
||||
// First group captures non quoted strings
|
||||
// Second group captures either a single quote or a string with a pair of quotes within a line
|
||||
// Class names in second group will not be compressed.
|
||||
private static final Pattern QUOTED_PATTERN =
|
||||
Pattern.compile("([^\\\"]+)((\\\")?[^\\\"\\r\\n]*\\\")?");
|
||||
|
||||
/**
|
||||
* Compresses an error message by stripping the packages out of class names and adding them
|
||||
* to a legend at the bottom of the error.
|
||||
*/
|
||||
static String compressPackagesInMessage(String input) {
|
||||
Matcher matcher = CLASSNAME_PATTERN.matcher(input);
|
||||
|
||||
Set<String> names = new HashSet<>();
|
||||
// Find all classnames in the error. Note that if our regex isn't complete, it just means the
|
||||
// classname is left in the full form, which is a fine fallback.
|
||||
while (matcher.find()) {
|
||||
String name = matcher.group(1);
|
||||
names.add(name);
|
||||
}
|
||||
// Now dedupe any conflicts. Use a TreeMap since we're going to need the legend sorted anyway.
|
||||
// This map is from short name to full name.
|
||||
Map<String, String> replacementMap = shortenNames(names);
|
||||
|
||||
// If we have nothing to replace, just return the original.
|
||||
if (replacementMap.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
Set<String> replacedShortNames = replaceFullNames(input, replacementMap, output);
|
||||
if (replacedShortNames.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
String classNameLegend =
|
||||
buildClassNameLegend(Maps.filterKeys(replacementMap, replacedShortNames::contains));
|
||||
return output.append(classNameLegend).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces full class names in {@code input} and append the replaced content to {@code output}
|
||||
* and then returns a set of short names that were used as replacement.
|
||||
*
|
||||
* <p>String literals that are quoted in the {@code input} will be added to the {@code output}
|
||||
* unchanged. So any full class name that only appear in the string literal will not be included
|
||||
* in the returned short names set.
|
||||
*/
|
||||
private static ImmutableSet<String> replaceFullNames(
|
||||
String input, Map<String, String> replacementMap, StringBuilder output) {
|
||||
ImmutableSet.Builder<String> replacedShortNames = ImmutableSet.builder();
|
||||
// Sort short names in reverse alphabetical order. This is necessary so that a short name that
|
||||
// has a prefix that is another short name will be replaced first, otherwise the longer name
|
||||
// will not be collected as one of the replacedShortNames.
|
||||
List<String> shortNames =
|
||||
replacementMap.keySet().stream()
|
||||
.sorted(Ordering.natural().reverse())
|
||||
.collect(Collectors.toList());
|
||||
Matcher matcher = QUOTED_PATTERN.matcher(input);
|
||||
while (matcher.find()) {
|
||||
String replaced = matcher.group(1);
|
||||
for (String shortName : shortNames) {
|
||||
String fullName = replacementMap.get(shortName);
|
||||
int beforeLen = replaced.length();
|
||||
replaced = replaced.replace(fullName, shortName);
|
||||
// If the replacement happened then put the short name in replacedShortNames.
|
||||
// Only values in replacedShortNames are included in the full class name legend.
|
||||
if (replaced.length() < beforeLen) {
|
||||
replacedShortNames.add(shortName);
|
||||
}
|
||||
}
|
||||
output.append(replaced);
|
||||
String quoted = matcher.group(2);
|
||||
if (quoted != null) {
|
||||
output.append(quoted);
|
||||
}
|
||||
}
|
||||
return replacedShortNames.build();
|
||||
}
|
||||
|
||||
private static String buildClassNameLegend(Map<String, String> replacementMap) {
|
||||
StringBuilder legendBuilder = new StringBuilder();
|
||||
// Find the longest key for building the legend
|
||||
int longestKey = replacementMap.keySet().stream().max(comparing(String::length)).get().length();
|
||||
for (Map.Entry<String, String> entry : replacementMap.entrySet()) {
|
||||
String shortName = entry.getKey();
|
||||
String fullName = entry.getValue();
|
||||
// Skip certain prefixes. We need to check the shortName for a . though in case
|
||||
// there was some type of conflict like java.util.concurrent.Future and
|
||||
// java.util.foo.Future that got shortened to concurrent.Future and foo.Future.
|
||||
// In those cases we do not want to skip the legend. We only skip if the class
|
||||
// is directly in that package.
|
||||
String prefix = fullName.substring(0, fullName.length() - shortName.length());
|
||||
if (PACKAGES_SKIPPED_IN_LEGEND.contains(prefix) && !shortName.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to the legend
|
||||
legendBuilder
|
||||
.append(shortName)
|
||||
.append(": ")
|
||||
// Add enough spaces to adjust the columns
|
||||
.append(Strings.repeat(" ", longestKey - shortName.length()))
|
||||
// Surround the full class name with quotes to avoid them getting compressed again if
|
||||
// the error is wrapped inside another Guice error.
|
||||
.append('"')
|
||||
.append(fullName)
|
||||
.append('"')
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
return legendBuilder.length() == 0
|
||||
? ""
|
||||
: Messages.bold(LEGEND_HEADER)
|
||||
+ Messages.faint(legendBuilder.toString())
|
||||
+ Messages.bold(LEGEND_FOOTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map from short name to full name after resolving conflicts. This resolves conflicts
|
||||
* by adding on segments of the package name until they are unique. For example, com.foo.Baz and
|
||||
* com.bar.Baz will conflict on Baz and then resolve with foo.Baz and bar.Baz as replacements.
|
||||
*/
|
||||
private static Map<String, String> shortenNames(Collection<String> names) {
|
||||
HashMultimap<String, List<String>> shortNameToPartsMap = HashMultimap.create();
|
||||
for (String name : names) {
|
||||
List<String> parts = new ArrayList<>(PACKAGE_SPLITTER.splitToList(name));
|
||||
// Start with the just the class name as the simple name
|
||||
String className = parts.remove(parts.size() - 1);
|
||||
shortNameToPartsMap.put(className, parts);
|
||||
}
|
||||
|
||||
// Iterate through looking for conflicts adding the next part of the package until there are no
|
||||
// more conflicts
|
||||
while (true) {
|
||||
// Save the keys with conflicts to avoid concurrent modification issues
|
||||
List<String> conflictingShortNames = new ArrayList<>();
|
||||
for (Map.Entry<String, Collection<List<String>>> entry
|
||||
: shortNameToPartsMap.asMap().entrySet()) {
|
||||
if (entry.getValue().size() > 1) {
|
||||
conflictingShortNames.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (conflictingShortNames.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// For all conflicts, add in the next part of the package
|
||||
for (String conflictingShortName : conflictingShortNames) {
|
||||
Set<List<String>> partsCollection = shortNameToPartsMap.removeAll(conflictingShortName);
|
||||
for (List<String> parts : partsCollection) {
|
||||
String newShortName = parts.remove(parts.size() - 1) + "." + conflictingShortName;
|
||||
// If we've removed the last part of the package, then just skip it entirely because
|
||||
// now we're not shortening it at all.
|
||||
if (!parts.isEmpty()) {
|
||||
shortNameToPartsMap.put(newShortName, parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn the multimap into a regular map now that conflicts have been resolved. Use a TreeMap
|
||||
// since we're going to need the legend sorted anyway. This map is from short name to full name.
|
||||
Map<String, String> replacementMap = new TreeMap<>();
|
||||
for (Map.Entry<String, Collection<List<String>>> entry
|
||||
: shortNameToPartsMap.asMap().entrySet()) {
|
||||
replacementMap.put(
|
||||
entry.getKey(),
|
||||
PACKAGE_JOINER.join(Iterables.getOnlyElement(entry.getValue())) + "." + entry.getKey());
|
||||
}
|
||||
return replacementMap;
|
||||
}
|
||||
|
||||
private PackageNameCompressor() {}
|
||||
}
|
|
@ -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 <a href="http://code.google.com/p/google-gin/">GIN</a>
|
||||
* Returns a module which creates bindings for provider methods from the given object. This is
|
||||
* useful notably for <a href="http://code.google.com/p/google-gin/">GIN</a>
|
||||
*
|
||||
* <p>This will skip bytecode generation for provider methods, since it is assumed that callers
|
||||
* 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<ProviderMethod<?>> getProviderMethods(Binder binder) {
|
||||
List<ProviderMethod<?>> result = null;
|
||||
List<MethodAndAnnotation> 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<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
|
||||
TypeLiteral<T> returnType = (TypeLiteral<T>) typeLiteral.getReturnType(method);
|
||||
Key<T> 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<? extends Annotation> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> {
|
|||
private final ProvisionListener[] listeners;
|
||||
private final Binding<T> binding;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ProvisionListenerStackCallback<T> emptyListener() {
|
||||
return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK;
|
||||
}
|
||||
|
||||
public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) {
|
||||
this.binding = binding;
|
||||
if (listeners.isEmpty()) {
|
||||
|
@ -33,18 +37,13 @@ final class ProvisionListenerStackCallback<T> {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ProvisionListenerStackCallback<T> emptyListener() {
|
||||
return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK;
|
||||
}
|
||||
|
||||
public boolean hasListeners() {
|
||||
return listeners.length > 0;
|
||||
}
|
||||
|
||||
public T provision(InternalContext context, ProvisionCallback<T> 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<T> {
|
|||
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<T> {
|
|||
|
||||
// TODO(sameb): Can this be more InternalFactory-like?
|
||||
public interface ProvisionCallback<T> {
|
||||
T call() throws InternalProvisionException;
|
||||
public T call() throws InternalProvisionException;
|
||||
}
|
||||
|
||||
private class Provision extends ProvisionListener.ProvisionInvocation<T> {
|
||||
|
||||
final InternalContext context;
|
||||
final ProvisionCallback<T> callable;
|
||||
int index = -1;
|
||||
T result;
|
||||
InternalProvisionException exceptionDuringProvision;
|
||||
ProvisionListener erredListener;
|
||||
|
||||
public Provision(InternalContext context, ProvisionCallback<T> callable) {
|
||||
public Provision(ProvisionCallback<T> callable) {
|
||||
this.callable = callable;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<K, V> implements Module {
|
|||
|
||||
// Binds a Map<K, Provider<V>>
|
||||
RealProviderMapProvider<K, V> providerMapProvider =
|
||||
new RealProviderMapProvider<K, V>(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<K, V> implements Module {
|
|||
binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider);
|
||||
|
||||
RealMapProvider<K, V> mapProvider = new RealMapProvider<>(bindingSelection);
|
||||
binder.bind(bindingSelection.getMapKey()).toProvider(mapProvider);
|
||||
// Bind Map<K, V> to the provider w/ extension support.
|
||||
binder
|
||||
.bind(bindingSelection.getMapKey())
|
||||
.toProvider(new ExtensionRealMapProvider<>(mapProvider));
|
||||
// Bind Map<K, ? extends V> to the provider w/o the extension support.
|
||||
binder.bind(bindingSelection.getMapOfKeyExtendsValueKey()).toProvider(mapProvider);
|
||||
|
||||
// The Map.Entries are all ProviderMapEntry instances which do not allow setValue, so it is
|
||||
// safe to massage the return type like this
|
||||
|
@ -364,6 +370,7 @@ public final class RealMapBinder<K, V> implements Module {
|
|||
private Key<Map<K, Collection<Provider<V>>>> providerCollectionMultimapKey;
|
||||
private Key<Map<K, Collection<javax.inject.Provider<V>>>> javaxProviderCollectionMultimapKey;
|
||||
private Key<Set<Map.Entry<K, javax.inject.Provider<V>>>> entrySetJavaxProviderKey;
|
||||
private Key<Map<K, ? extends V>> mapOfKeyExtendsValueKey;
|
||||
|
||||
private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;
|
||||
|
||||
|
@ -485,7 +492,7 @@ public final class RealMapBinder<K, V> 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<K, V> implements Module {
|
|||
}
|
||||
|
||||
private static <K, V> void reportDuplicateKeysError(
|
||||
Multimap<K, Binding<V>> duplicates, Errors errors) {
|
||||
StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key ");
|
||||
boolean first = true;
|
||||
for (Map.Entry<K, Collection<Binding<V>>> entry : duplicates.asMap().entrySet()) {
|
||||
K dupKey = entry.getKey();
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
sb.append("\"").append(dupKey).append("\", from bindings:\n");
|
||||
} else {
|
||||
sb.append("\n and key: \"").append(dupKey).append("\", from bindings:\n");
|
||||
}
|
||||
|
||||
for (Binding<V> dup : entry.getValue()) {
|
||||
sb.append("\t at ").append(Messages.convert(dup.getSource())).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): Add a different error for every duplicated key
|
||||
errors.addMessage(sb.toString());
|
||||
Key<Map<K, V>> mapKey, Multimap<K, Binding<V>> duplicates, Errors errors) {
|
||||
errors.duplicateMapKey(mapKey, duplicates);
|
||||
return;
|
||||
}
|
||||
|
||||
private boolean containsElement(Element element) {
|
||||
|
@ -553,6 +543,7 @@ public final class RealMapBinder<K, V> 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<K, V> implements Module {
|
|||
return local;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Key<Map<K, ? extends V>> getMapOfKeyExtendsValueKey() {
|
||||
Key<Map<K, ? extends V>> local = mapOfKeyExtendsValueKey;
|
||||
if (local == null) {
|
||||
Type extendsValue = Types.subtypeOf(valueType.getType());
|
||||
Type mapOfKeyAndExtendsValue = Types.mapOf(keyType.getType(), extendsValue);
|
||||
local =
|
||||
mapOfKeyExtendsValueKey =
|
||||
(Key<Map<K, ? extends V>>) mapKey.ofType(mapOfKeyAndExtendsValue);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
private ImmutableMap<K, Binding<V>> getMapBindings() {
|
||||
checkConfiguration(isInitialized(), "MapBinder has not yet been initialized");
|
||||
return mapBindings;
|
||||
|
@ -727,24 +731,23 @@ public final class RealMapBinder<K, V> implements Module {
|
|||
}
|
||||
|
||||
private static final class RealMapProvider<K, V>
|
||||
extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>>
|
||||
implements ProviderWithExtensionVisitor<Map<K, V>>, MapBinderBinding<Map<K, V>> {
|
||||
private Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;
|
||||
extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>> {
|
||||
Set<Dependency<?>> dependencies = RealMapBinder.MODULE_DEPENDENCIES;
|
||||
|
||||
/**
|
||||
* An array of all the injectors.
|
||||
*
|
||||
* <p>This is parallel to array of keys below
|
||||
*/
|
||||
private SingleParameterInjector<V>[] injectors;
|
||||
SingleParameterInjector<V>[] injectors;
|
||||
|
||||
private K[] keys;
|
||||
K[] keys;
|
||||
|
||||
private RealMapProvider(BindingSelection<K, V> bindingSelection) {
|
||||
RealMapProvider(BindingSelection<K, V> bindingSelection) {
|
||||
super(bindingSelection);
|
||||
}
|
||||
|
||||
private BindingSelection<K, V> getBindingSelection() {
|
||||
BindingSelection<K, V> getBindingSelection() {
|
||||
return bindingSelection;
|
||||
}
|
||||
|
||||
|
@ -805,6 +808,42 @@ public final class RealMapBinder<K, V> implements Module {
|
|||
public Set<Dependency<?>> 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<K, V>
|
||||
extends RealMapBinderProviderWithDependencies<K, V, Map<K, V>>
|
||||
implements ProviderWithExtensionVisitor<Map<K, V>>, MapBinderBinding<Map<K, V>> {
|
||||
final RealMapProvider<K, V> delegate;
|
||||
|
||||
ExtensionRealMapProvider(RealMapProvider<K, V> delegate) {
|
||||
super(delegate.bindingSelection);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
BindingSelection<K, V> getBindingSelection() {
|
||||
return bindingSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException {
|
||||
delegate.doInitialize(injector, errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<K, V> doProvision(InternalContext context, Dependency<?> dependency)
|
||||
throws InternalProvisionException {
|
||||
return delegate.doProvision(context, dependency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Dependency<?>> getDependencies() {
|
||||
return delegate.getDependencies();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -831,7 +870,8 @@ public final class RealMapBinder<K, V> 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<K, V> 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<V> 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<V> 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<K, V> implements Module {
|
|||
ProviderInstanceBinding<Map<K, V>> providerInstanceBinding =
|
||||
(ProviderInstanceBinding<Map<K, V>>) mapBinding;
|
||||
@SuppressWarnings("unchecked")
|
||||
RealMapProvider<K, V> mapProvider =
|
||||
(RealMapProvider<K, V>) providerInstanceBinding.getUserSuppliedProvider();
|
||||
ExtensionRealMapProvider<K, V> mapProvider =
|
||||
(ExtensionRealMapProvider<K, V>) providerInstanceBinding.getUserSuppliedProvider();
|
||||
|
||||
this.bindingSelection = mapProvider.getBindingSelection();
|
||||
|
||||
|
@ -1346,7 +1386,9 @@ public final class RealMapBinder<K, V> implements Module {
|
|||
private static <K, V> InternalProvisionException createNullValueException(
|
||||
K key, Binding<V> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> implements Module {
|
|||
return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> TypeLiteral<Set<? extends T>> setOfExtendsOf(TypeLiteral<T> elementType) {
|
||||
Type extendsType = Types.subtypeOf(elementType.getType());
|
||||
Type setOfExtendsType = Types.setOf(extendsType);
|
||||
return (TypeLiteral<Set<? extends T>>) TypeLiteral.get(setOfExtendsType);
|
||||
}
|
||||
|
||||
private final BindingSelection<T> bindingSelection;
|
||||
private final Binder binder;
|
||||
|
||||
|
@ -92,9 +100,17 @@ public final class RealMultibinder<T> implements Module {
|
|||
@Override
|
||||
public void configure(Binder binder) {
|
||||
checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized");
|
||||
|
||||
RealMultibinderProvider<T> setProvider = new RealMultibinderProvider<T>(bindingSelection);
|
||||
// Bind the setKey to the provider wrapped w/ extension support.
|
||||
binder
|
||||
.bind(bindingSelection.getSetKey())
|
||||
.toProvider(new RealMultibinderProvider<T>(bindingSelection));
|
||||
.toProvider(new ExtensionRealMultibinderProvider<>(setProvider));
|
||||
// Bind the <? extends T> to the provider w/o extension support.
|
||||
// It's important the exactly one binding implement the extension support and show
|
||||
// the other keys as aliases, to adhere to the extension contract.
|
||||
binder.bind(bindingSelection.getSetOfExtendsKey()).toProvider(setProvider);
|
||||
|
||||
Provider<Collection<Provider<T>>> collectionOfProvidersProvider =
|
||||
new RealMultibinderCollectionOfProvidersProvider<T>(bindingSelection);
|
||||
binder
|
||||
|
@ -148,15 +164,19 @@ public final class RealMultibinder<T> implements Module {
|
|||
return bindingSelection.containsElement(element);
|
||||
}
|
||||
|
||||
private static final class RealMultibinderProvider<T>
|
||||
extends InternalProviderInstanceBindingImpl.Factory<Set<T>>
|
||||
implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> {
|
||||
private final BindingSelection<T> bindingSelection;
|
||||
private List<Binding<T>> bindings;
|
||||
private SingleParameterInjector<T>[] 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<ValueT, ProvidedT>
|
||||
extends InternalProviderInstanceBindingImpl.Factory<ProvidedT> {
|
||||
final Function<BindingSelection<ValueT>, ImmutableSet<Dependency<?>>> dependenciesFn;
|
||||
final BindingSelection<ValueT> bindingSelection;
|
||||
|
||||
RealMultibinderProvider(BindingSelection<T> bindingSelection) {
|
||||
BaseFactory(
|
||||
BindingSelection<ValueT> bindingSelection,
|
||||
Function<BindingSelection<ValueT>, ImmutableSet<Dependency<?>>> dependenciesFn) {
|
||||
// While Multibinders only depend on bindings created in modules so we could theoretically
|
||||
// initialize eagerly, they also depend on
|
||||
// 1. findBindingsByType returning results
|
||||
|
@ -164,23 +184,58 @@ public final class RealMultibinder<T> implements Module {
|
|||
// neither of those is available during eager initialization, so we use DELAYED
|
||||
super(InitializationTiming.DELAYED);
|
||||
this.bindingSelection = bindingSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Dependency<?>> 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<Dependency<?>> getDependencies() {
|
||||
return dependenciesFn.apply(bindingSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<T> doProvision(InternalContext context, Dependency<?> dependency)
|
||||
public boolean equals(Object obj) {
|
||||
return getClass().isInstance(obj)
|
||||
&& bindingSelection.equals(((BaseFactory<?, ?>) obj).bindingSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return bindingSelection.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider instance implementation that provides the actual set of values. This is parameterized
|
||||
* so it can be used to supply a Set<T> and Set<? extends T>, the latter being useful for Kotlin
|
||||
* support.
|
||||
*/
|
||||
private static final class RealMultibinderProvider<T> extends BaseFactory<T, Set<T>> {
|
||||
List<Binding<T>> bindings;
|
||||
SingleParameterInjector<T>[] injectors;
|
||||
boolean permitDuplicates;
|
||||
|
||||
RealMultibinderProvider(BindingSelection<T> bindingSelection) {
|
||||
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
|
||||
super(bindingSelection, bs -> bs.getDependencies());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialize() {
|
||||
bindings = bindingSelection.getBindings();
|
||||
injectors = bindingSelection.getParameterInjectors();
|
||||
permitDuplicates = bindingSelection.permitsDuplicates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableSet<T> doProvision(InternalContext context, Dependency<?> dependency)
|
||||
throws InternalProvisionException {
|
||||
SingleParameterInjector<T>[] localInjectors = injectors;
|
||||
if (localInjectors == null) {
|
||||
|
@ -204,14 +259,52 @@ public final class RealMultibinder<T> implements Module {
|
|||
ImmutableSet<T> 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<T>(
|
||||
bindingSelection.getSetKey(), bindings, values, ImmutableList.of(getSource())));
|
||||
return new InternalProvisionException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of BaseFactory that exposes details about the multibinder through the extension
|
||||
* SPI.
|
||||
*/
|
||||
private static final class ExtensionRealMultibinderProvider<T> extends BaseFactory<T, Set<T>>
|
||||
implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> {
|
||||
final RealMultibinderProvider<T> delegate;
|
||||
|
||||
ExtensionRealMultibinderProvider(RealMultibinderProvider<T> delegate) {
|
||||
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
|
||||
super(delegate.bindingSelection, bs -> bs.getDependencies());
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialize() {
|
||||
delegate.doInitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableSet<T> doProvision(InternalContext context, Dependency<?> dependency)
|
||||
throws InternalProvisionException {
|
||||
return delegate.doProvision(context, dependency);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -225,69 +318,17 @@ public final class RealMultibinder<T> implements Module {
|
|||
}
|
||||
}
|
||||
|
||||
private InternalProvisionException newDuplicateValuesException(
|
||||
ImmutableSet<T> set, T[] values) {
|
||||
// TODO(lukes): consider reporting all duplicate values, the easiest way would be to rebuild
|
||||
// a new set and detect dupes as we go
|
||||
// Find the duplicate binding
|
||||
// To do this we take advantage of the fact that set, values and bindings all have the same
|
||||
// ordering for a non-empty prefix of the set.
|
||||
// First we scan for the first item dropped from the set.
|
||||
int newBindingIndex = 0;
|
||||
for (T item : set) {
|
||||
if (item != values[newBindingIndex]) {
|
||||
break;
|
||||
}
|
||||
newBindingIndex++;
|
||||
}
|
||||
// once we exit the loop newBindingIndex will point at the first item in values that was
|
||||
// dropped.
|
||||
|
||||
Binding<T> newBinding = bindings.get(newBindingIndex);
|
||||
T newValue = values[newBindingIndex];
|
||||
// Now we scan again to find the index of the value, we are guaranteed to find it.
|
||||
int oldBindingIndex = set.asList().indexOf(newValue);
|
||||
T oldValue = values[oldBindingIndex];
|
||||
Binding<T> duplicateBinding = bindings.get(oldBindingIndex);
|
||||
String oldString = oldValue.toString();
|
||||
String newString = newValue.toString();
|
||||
if (Objects.equal(oldString, newString)) {
|
||||
// When the value strings match, just show the source of the bindings
|
||||
return InternalProvisionException.create(
|
||||
"Set injection failed due to duplicated element \"%s\""
|
||||
+ "\n Bound at %s\n Bound at %s",
|
||||
newValue, duplicateBinding.getSource(), newBinding.getSource());
|
||||
} else {
|
||||
// When the value strings don't match, include them both as they may be useful for debugging
|
||||
return InternalProvisionException.create(
|
||||
"Set injection failed due to multiple elements comparing equal:"
|
||||
+ "\n \"%s\"\n bound at %s"
|
||||
+ "\n \"%s\"\n bound at %s",
|
||||
oldValue, duplicateBinding.getSource(), newValue, newBinding.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof RealMultibinderProvider
|
||||
&& bindingSelection.equals(((RealMultibinderProvider<?>) obj).bindingSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return bindingSelection.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key<Set<T>> getSetKey() {
|
||||
return bindingSelection.getSetKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Key<?>> getAlternateSetKeys() {
|
||||
public ImmutableSet<Key<?>> 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<T> implements Module {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of BaseFactory that exposes a collection of providers of the values in the set.
|
||||
*/
|
||||
private static final class RealMultibinderCollectionOfProvidersProvider<T>
|
||||
extends BaseFactory<T, Collection<Provider<T>>> {
|
||||
ImmutableList<Provider<T>> providers;
|
||||
|
||||
RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) {
|
||||
// Note: method reference doesn't work for the 2nd arg for some reason when compiling on java8
|
||||
super(bindingSelection, bs -> bs.getProviderDependencies());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialize() {
|
||||
ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder();
|
||||
for (Binding<T> binding : bindingSelection.getBindings()) {
|
||||
providers.add(binding.getProvider());
|
||||
}
|
||||
this.providers = providers.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableList<Provider<T>> doProvision(
|
||||
InternalContext context, Dependency<?> dependency) {
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BindingSelection<T> {
|
||||
// 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<T> implements Module {
|
|||
private String setName;
|
||||
private Key<Collection<Provider<T>>> collectionOfProvidersKey;
|
||||
private Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey;
|
||||
private Key<Set<? extends T>> setOfExtendsKey;
|
||||
private Key<Boolean> permitDuplicatesKey;
|
||||
|
||||
private boolean isInitialized;
|
||||
|
@ -358,7 +428,7 @@ public final class RealMultibinder<T> implements Module {
|
|||
for (Binding<?> entry : injector.findBindingsByType(elementType)) {
|
||||
if (keyMatches(entry.getKey())) {
|
||||
@SuppressWarnings("unchecked") // protected by findBindingsByType()
|
||||
Binding<T> binding = (Binding<T>) entry;
|
||||
Binding<T> binding = (Binding<T>) 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<T> implements Module {
|
|||
return local;
|
||||
}
|
||||
|
||||
Key<Set<? extends T>> getSetOfExtendsKey() {
|
||||
Key<Set<? extends T>> local = setOfExtendsKey;
|
||||
if (local == null) {
|
||||
local = setOfExtendsKey = setKey.ofType(setOfExtendsOf(elementType));
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
@ -487,7 +565,8 @@ public final class RealMultibinder<T> 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<T> implements Module {
|
|||
return bindingSelection.hashCode();
|
||||
}
|
||||
|
||||
private static final class RealMultibinderCollectionOfProvidersProvider<T>
|
||||
extends InternalProviderInstanceBindingImpl.Factory<Collection<Provider<T>>> {
|
||||
|
||||
private final BindingSelection<T> bindingSelection;
|
||||
private ImmutableList<Provider<T>> collectionOfProviders;
|
||||
|
||||
RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) {
|
||||
super(InitializationTiming.DELAYED); // See comment in RealMultibinderProvider
|
||||
this.bindingSelection = bindingSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
|
||||
bindingSelection.initialize(injector, errors);
|
||||
ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder();
|
||||
for (Binding<T> binding : bindingSelection.getBindings()) {
|
||||
providers.add(binding.getProvider());
|
||||
}
|
||||
this.collectionOfProviders = providers.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Provider<T>> doProvision(
|
||||
InternalContext context, Dependency<?> dependency) {
|
||||
return collectionOfProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Dependency<?>> getDependencies() {
|
||||
return bindingSelection.getProviderDependencies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof RealMultibinderCollectionOfProvidersProvider
|
||||
&& bindingSelection.equals(
|
||||
((RealMultibinderCollectionOfProvidersProvider<?>) obj).bindingSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return bindingSelection.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We install the permit duplicates configuration as its own binding, all by itself. This way, if
|
||||
* only one of a multibinder's users remember to call permitDuplicates(), they're still permitted.
|
||||
|
|
|
@ -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<? extends Annotation> annotationType = checkNotNull(command.getAnnotationType(), "annotation type");
|
||||
Class<? extends Annotation> annotationType =
|
||||
checkNotNull(command.getAnnotationType(), "annotation type");
|
||||
|
||||
if (!Annotations.isScopeAnnotation(annotationType)) {
|
||||
errors.missingScopeAnnotation(annotationType);
|
||||
|
@ -32,13 +32,13 @@ final class ScopeBindingProcessor extends AbstractProcessor {
|
|||
// Go ahead and bind anyway so we don't get collateral errors.
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.spi.ErrorDetail;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
|
||||
/** Error reported by Guice when a scope annotation is not bound to any scope implementation. */
|
||||
final class ScopeNotFoundError extends InternalErrorDetail<ScopeNotFoundError> {
|
||||
|
||||
private final Class<? extends Annotation> scopeAnnotation;
|
||||
|
||||
ScopeNotFoundError(Class<? extends Annotation> scopeAnnotation, List<Object> sources) {
|
||||
super(
|
||||
ErrorId.SCOPE_NOT_FOUND,
|
||||
String.format("No scope is bound to %s.", Messages.convert(scopeAnnotation)),
|
||||
sources,
|
||||
null);
|
||||
this.scopeAnnotation = scopeAnnotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMergeable(ErrorDetail<?> other) {
|
||||
return other instanceof ScopeNotFoundError
|
||||
&& ((ScopeNotFoundError) other).scopeAnnotation.equals(scopeAnnotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
List<List<Object>> sourcesSet = new ArrayList<>();
|
||||
sourcesSet.add(getSources());
|
||||
mergeableErrors.stream().map(ErrorDetail::getSources).forEach(sourcesSet::add);
|
||||
|
||||
formatter.format("%n%s%n", "Used at:");
|
||||
int sourceListIndex = 1;
|
||||
for (List<Object> sources : sourcesSet) {
|
||||
ErrorFormatter.formatSources(sourceListIndex++, Lists.reverse(sources), formatter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScopeNotFoundError withSources(List<Object> newSources) {
|
||||
return new ScopeNotFoundError(scopeAnnotation, newSources);
|
||||
}
|
||||
}
|
|
@ -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> V acceptVisitor(BindingScopingVisitor<V> visitor) {
|
||||
return visitor.visitNoScoping();
|
||||
}
|
||||
public static final Scoping UNSCOPED =
|
||||
new Scoping() {
|
||||
@Override
|
||||
public <V> V acceptVisitor(BindingScopingVisitor<V> 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> V acceptVisitor(BindingScopingVisitor<V> 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> V acceptVisitor(BindingScopingVisitor<V> visitor) {
|
||||
return visitor.visitNoScoping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> 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> V acceptVisitor(BindingScopingVisitor<V> visitor) {
|
||||
return visitor.visitScope(Scopes.SINGLETON);
|
||||
}
|
||||
public static final Scoping SINGLETON_ANNOTATION =
|
||||
new Scoping() {
|
||||
@Override
|
||||
public <V> V acceptVisitor(BindingScopingVisitor<V> visitor) {
|
||||
return visitor.visitScopeAnnotation(Singleton.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScopeInstance() {
|
||||
return Scopes.SINGLETON;
|
||||
}
|
||||
@Override
|
||||
public Class<? extends Annotation> 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> V acceptVisitor(BindingScopingVisitor<V> visitor) {
|
||||
return visitor.visitEagerSingleton();
|
||||
}
|
||||
public static final Scoping SINGLETON_INSTANCE =
|
||||
new Scoping() {
|
||||
@Override
|
||||
public <V> V acceptVisitor(BindingScopingVisitor<V> 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> V acceptVisitor(BindingScopingVisitor<V> 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<? extends Annotation> 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 <T> InternalFactory<? extends T> scope(Key<T> key, InjectorImpl injector,
|
||||
InternalFactory<? extends T> creator, Object source, Scoping scoping) {
|
||||
|
||||
if (scoping.isNoScope()) {
|
||||
return creator;
|
||||
}
|
||||
|
||||
Scope scope = scoping.getScopeInstance();
|
||||
|
||||
Provider<T> scoped = scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
|
||||
return new InternalFactoryToProviderAdapter<T>(scoped, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance
|
||||
* map. If the scope annotation has no corresponding instance, an error will be added and unscoped
|
||||
* will be retuned.
|
||||
*/
|
||||
static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) {
|
||||
Class<? extends Annotation> scopeAnnotation = scoping.getScopeAnnotation();
|
||||
if (scopeAnnotation == null) {
|
||||
return scoping;
|
||||
}
|
||||
|
||||
ScopeBinding scope = injector.state.getScopeBinding(scopeAnnotation);
|
||||
if (scope != null) {
|
||||
return forInstance(scope.getScope());
|
||||
}
|
||||
|
||||
errors.scopeNotFound(scopeAnnotation);
|
||||
return UNSCOPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this scope was explicitly applied. If no scope was explicitly applied then the
|
||||
* 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<? extends Annotation> getScopeAnnotation() {
|
||||
return null;
|
||||
}
|
||||
|
@ -269,4 +258,48 @@ public abstract class Scoping {
|
|||
public abstract <V> V acceptVisitor(BindingScopingVisitor<V> visitor);
|
||||
|
||||
public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder);
|
||||
|
||||
private Scoping() {}
|
||||
|
||||
/** Scopes an internal factory. */
|
||||
static <T> InternalFactory<? extends T> scope(
|
||||
Key<T> key,
|
||||
InjectorImpl injector,
|
||||
InternalFactory<? extends T> creator,
|
||||
Object source,
|
||||
Scoping scoping) {
|
||||
|
||||
if (scoping.isNoScope()) {
|
||||
return creator;
|
||||
}
|
||||
|
||||
Scope scope = scoping.getScopeInstance();
|
||||
|
||||
// NOTE: SingletonScope relies on the fact that we are passing a
|
||||
// ProviderToInternalFactoryAdapter here. If you change the type make sure to update
|
||||
// SingletonScope as well.
|
||||
Provider<T> scoped =
|
||||
scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
|
||||
return new InternalFactoryToProviderAdapter<T>(scoped, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance
|
||||
* map. If the scope annotation has no corresponding instance, an error will be added and unscoped
|
||||
* will be retuned.
|
||||
*/
|
||||
static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) {
|
||||
Class<? extends Annotation> scopeAnnotation = scoping.getScopeAnnotation();
|
||||
if (scopeAnnotation == null) {
|
||||
return scoping;
|
||||
}
|
||||
|
||||
ScopeBinding scope = injector.getBindingData().getScopeBinding(scopeAnnotation);
|
||||
if (scope != null) {
|
||||
return forInstance(scope.getScope());
|
||||
}
|
||||
|
||||
errors.scopeNotFound(scopeAnnotation);
|
||||
return UNSCOPED;
|
||||
}
|
||||
}
|
||||
|
|
169
src/main/java/com/google/inject/internal/SourceFormatter.java
Normal file
169
src/main/java/com/google/inject/internal/SourceFormatter.java
Normal file
|
@ -0,0 +1,169 @@
|
|||
package com.google.inject.internal;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.internal.util.Classes;
|
||||
import com.google.inject.internal.util.StackTraceElements;
|
||||
import com.google.inject.spi.Dependency;
|
||||
import com.google.inject.spi.ElementSource;
|
||||
import com.google.inject.spi.InjectionPoint;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
|
||||
/** Formatting a single source in Guice error message. */
|
||||
final class SourceFormatter {
|
||||
static final String INDENT = Strings.repeat(" ", 5);
|
||||
|
||||
private final Object source;
|
||||
private final Formatter formatter;
|
||||
private final boolean omitPreposition;
|
||||
private final String moduleStack;
|
||||
|
||||
SourceFormatter(Object source, Formatter formatter, boolean omitPreposition) {
|
||||
if (source instanceof ElementSource) {
|
||||
ElementSource elementSource = (ElementSource) source;
|
||||
this.source = elementSource.getDeclaringSource();
|
||||
this.moduleStack = getModuleStack(elementSource);
|
||||
} else {
|
||||
this.source = source;
|
||||
this.moduleStack = "";
|
||||
}
|
||||
this.formatter = formatter;
|
||||
this.omitPreposition = omitPreposition;
|
||||
}
|
||||
|
||||
void format() {
|
||||
boolean appendModuleSource = !moduleStack.isEmpty();
|
||||
if (source instanceof Dependency) {
|
||||
formatDependency((Dependency<?>) source);
|
||||
} else if (source instanceof InjectionPoint) {
|
||||
formatInjectionPoint(null, (InjectionPoint) source);
|
||||
} else if (source instanceof Class) {
|
||||
formatter.format("%s%s%n", preposition("at "), StackTraceElements.forType((Class<?>) source));
|
||||
} else if (source instanceof Member) {
|
||||
formatMember((Member) source);
|
||||
} else if (source instanceof TypeLiteral) {
|
||||
formatter.format("%s%s%n", preposition("while locating "), source);
|
||||
} else if (source instanceof Key) {
|
||||
formatKey((Key<?>) source);
|
||||
} else if (source instanceof Thread) {
|
||||
appendModuleSource = false;
|
||||
formatter.format("%s%s%n", preposition("in thread "), source);
|
||||
} else {
|
||||
formatter.format("%s%s%n", preposition("at "), source);
|
||||
}
|
||||
|
||||
if (appendModuleSource) {
|
||||
formatter.format("%s \\_ installed by: %s%n", INDENT, moduleStack);
|
||||
}
|
||||
}
|
||||
|
||||
private String preposition(String prepostition) {
|
||||
if (omitPreposition) {
|
||||
return "";
|
||||
}
|
||||
return prepostition;
|
||||
}
|
||||
|
||||
private void formatDependency(Dependency<?> dependency) {
|
||||
InjectionPoint injectionPoint = dependency.getInjectionPoint();
|
||||
if (injectionPoint != null) {
|
||||
formatInjectionPoint(dependency, injectionPoint);
|
||||
} else {
|
||||
formatKey(dependency.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
private void formatKey(Key<?> key) {
|
||||
formatter.format("%s%s%n", preposition("while locating "), Messages.convert(key));
|
||||
}
|
||||
|
||||
private void formatMember(Member member) {
|
||||
formatter.format("%s%s%n", preposition("at "), StackTraceElements.forMember(member));
|
||||
}
|
||||
|
||||
private void formatInjectionPoint(Dependency<?> dependency, InjectionPoint injectionPoint) {
|
||||
Member member = injectionPoint.getMember();
|
||||
Class<? extends Member> memberType = Classes.memberType(member);
|
||||
formatMember(injectionPoint.getMember());
|
||||
if (memberType == Field.class) {
|
||||
formatter.format("%s \\_ for field %s%n", INDENT, Messages.redBold(member.getName()));
|
||||
} else if (dependency != null) {
|
||||
|
||||
formatter.format("%s \\_ for %s%n", INDENT, getParameterName(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
static String getModuleStack(ElementSource elementSource) {
|
||||
if (elementSource == null) {
|
||||
return "";
|
||||
}
|
||||
List<String> modules = Lists.newArrayList(elementSource.getModuleClassNames());
|
||||
// Insert any original element sources w/ module info into the path.
|
||||
while (elementSource.getOriginalElementSource() != null) {
|
||||
elementSource = elementSource.getOriginalElementSource();
|
||||
modules.addAll(0, elementSource.getModuleClassNames());
|
||||
}
|
||||
if (modules.size() <= 1) {
|
||||
return "";
|
||||
}
|
||||
return String.join(" -> ", Lists.reverse(modules));
|
||||
}
|
||||
|
||||
static String getParameterName(Dependency<?> dependency) {
|
||||
int parameterIndex = dependency.getParameterIndex();
|
||||
int ordinal = parameterIndex + 1;
|
||||
Member member = dependency.getInjectionPoint().getMember();
|
||||
Parameter parameter = null;
|
||||
if (member instanceof Constructor) {
|
||||
parameter = ((Constructor<?>) member).getParameters()[parameterIndex];
|
||||
} else if (member instanceof Method) {
|
||||
parameter = ((Method) member).getParameters()[parameterIndex];
|
||||
}
|
||||
String parameterName = "";
|
||||
if (parameter != null && parameter.isNamePresent()) {
|
||||
parameterName = parameter.getName();
|
||||
}
|
||||
return String.format(
|
||||
"%s%s parameter%s",
|
||||
ordinal,
|
||||
getOrdinalSuffix(ordinal),
|
||||
parameterName.isEmpty() ? "" : " " + Messages.redBold(parameterName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps {@code 1} to the string {@code "1st"} ditto for all non-negative numbers
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers">
|
||||
* https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers</a>
|
||||
*/
|
||||
private static String getOrdinalSuffix(int ordinal) {
|
||||
// negative ordinals don't make sense, we allow zero though because we are programmers
|
||||
checkArgument(ordinal >= 0);
|
||||
if ((ordinal / 10) % 10 == 1) {
|
||||
// all the 'teens' are weird
|
||||
return "th";
|
||||
} else {
|
||||
// could use a lookup table? any better?
|
||||
switch (ordinal % 10) {
|
||||
case 1:
|
||||
return "st";
|
||||
case 2:
|
||||
return "nd";
|
||||
case 3:
|
||||
return "rd";
|
||||
default:
|
||||
return "th";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Character>";
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeConverter<Character>";
|
||||
}
|
||||
});
|
||||
|
||||
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<E extends Enum<E>>";
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeConverter<E extends Enum<E>>";
|
||||
}
|
||||
});
|
||||
|
||||
internalConvertToTypes(injector, new AbstractMatcher<>() {
|
||||
internalConvertToTypes(
|
||||
injector,
|
||||
new AbstractMatcher<TypeLiteral<?>>() {
|
||||
@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<Class<?>>";
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private static <T> void convertToPrimitiveType(InjectorImpl injector, Class<T> primitiveType,
|
||||
final Class<T> wrapperType) {
|
||||
private static <T> void convertToPrimitiveType(
|
||||
InjectorImpl injector, Class<T> primitiveType, final Class<T> 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 <T> void convertToClass(InjectorImpl injector, Class<T> type,
|
||||
TypeConverter converter) {
|
||||
private static <T> void convertToClass(
|
||||
InjectorImpl injector, Class<T> type, TypeConverter converter) {
|
||||
convertToClasses(injector, Matchers.identicalTo(type), converter);
|
||||
}
|
||||
|
||||
private static void convertToClasses(InjectorImpl injector,
|
||||
final Matcher<? super Class<?>> 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<? super Class<?>> typeMatcher, TypeConverter converter) {
|
||||
internalConvertToTypes(
|
||||
injector,
|
||||
new AbstractMatcher<TypeLiteral<?>>() {
|
||||
@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<? super TypeLiteral<?>> 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<? super TypeLiteral<?>> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Key<?>, Multiset<Object>> 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<Key<?>, Multiset<Object>> backingMap;
|
||||
/**
|
||||
* Tracks child injector lifetimes and evicts blacklisted keys/sources after the child injector is
|
||||
* Tracks child injector lifetimes and evicts banned keys/sources after the child injector is
|
||||
* garbage collected.
|
||||
*/
|
||||
private final Cache<State, Set<KeyAndSource>> evictionCache = CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
.removalListener(
|
||||
(RemovalListener<State, Set<KeyAndSource>>) notification -> {
|
||||
Preconditions.checkState(RemovalCause.COLLECTED.equals(notification.getCause()));
|
||||
private final Cache<InjectorBindingData, Set<KeyAndSource>> evictionCache =
|
||||
CacheBuilder.newBuilder().weakKeys().removalListener(this::cleanupOnRemoval).build();
|
||||
|
||||
cleanUpForCollectedState(notification.getValue());
|
||||
})
|
||||
.build();
|
||||
private void cleanupOnRemoval(
|
||||
RemovalNotification<InjectorBindingData, Set<KeyAndSource>> 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<KeyAndSource> 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<Object> 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<KeyAndSource> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
package com.google.inject.spi;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.RestrictedBindingSource;
|
||||
import com.google.inject.RestrictedBindingSource.RestrictionLevel;
|
||||
import com.google.inject.internal.Errors;
|
||||
import com.google.inject.internal.GuiceInternal;
|
||||
import java.util.regex.Pattern;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.Formatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Contains abstractions for enforcing {@link RestrictedBindingSource}.
|
||||
*
|
||||
* <p>Enforcement happens in two phases:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Data structures for enforcement are built during Binder configuration. {@link
|
||||
* PermitMapConstruction} encapsulates this process, and the {@link PermitMap} is the end
|
||||
* result.
|
||||
* <li>Restrictions are enforced by checking each binding for violations with {@link #check},
|
||||
* which uses the {@link PermitMap}(s) built during Binder configuration.
|
||||
* </ol>
|
||||
*
|
||||
* <p>Note: None of this is thread-safe because it's only used while the Injector is being built,
|
||||
* which happens on a single thread.
|
||||
*
|
||||
*/
|
||||
public final class BindingSourceRestriction {
|
||||
private BindingSourceRestriction() {}
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RestrictedBindingSource.class.getName());
|
||||
|
||||
/** Mapping between an element source and its permit annotations. */
|
||||
interface PermitMap {
|
||||
ImmutableSet<Class<? extends Annotation>> getPermits(ElementSource elementSource);
|
||||
|
||||
void clear();
|
||||
}
|
||||
|
||||
/** Returns a suggestion for how a restricted binding should be created in case it's missing. */
|
||||
public static Optional<String> getMissingImplementationSuggestion(
|
||||
GuiceInternal guiceInternal, Key<?> key) {
|
||||
checkNotNull(guiceInternal);
|
||||
RestrictedBindingSource restriction = getRestriction(key);
|
||||
if (restriction == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
String.format(
|
||||
"%nHint: This key is restricted and cannot be bound directly. Restriction explanation:"
|
||||
+ " %s",
|
||||
restriction.explanation()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the restriction violations found on the given Module Elements, as error messages.
|
||||
*
|
||||
* <p>Note: Intended to be used on Module Elements, not Injector Elements, ie. the result of
|
||||
* {@link Elements#getElements} not {@code Injector.getElements}. The Module Elements this check
|
||||
* cares about are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Module Bindings, which are always explicit and always have an {@link ElementSource} (with
|
||||
* a Module Stack), unlike Injector Bindings, which may be implicit and bereft of an
|
||||
* ElementSource.
|
||||
* <li>{@link PrivateElements}, which represent the recursive case of this check. They contain a
|
||||
* list of elements that this check is recursively called on.
|
||||
* </ul>
|
||||
*/
|
||||
public static ImmutableList<Message> check(GuiceInternal guiceInternal, List<Element> elements) {
|
||||
checkNotNull(guiceInternal);
|
||||
ImmutableList<Message> errorMessages = check(elements);
|
||||
// Clear all the permit maps after the checks are done.
|
||||
elements.forEach(BindingSourceRestriction::clear);
|
||||
return errorMessages;
|
||||
}
|
||||
|
||||
private static ImmutableList<Message> check(List<Element> elements) {
|
||||
ImmutableList.Builder<Message> errorMessagesBuilder = ImmutableList.builder();
|
||||
for (Element element : elements) {
|
||||
errorMessagesBuilder.addAll(check(element));
|
||||
}
|
||||
return errorMessagesBuilder.build();
|
||||
}
|
||||
|
||||
private static ImmutableList<Message> check(Element element) {
|
||||
return element.acceptVisitor(
|
||||
new DefaultElementVisitor<ImmutableList<Message>>() {
|
||||
// Base case.
|
||||
@Override
|
||||
protected ImmutableList<Message> visitOther(Element element) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
// Base case.
|
||||
@Override
|
||||
public <T> ImmutableList<Message> visit(Binding<T> binding) {
|
||||
Optional<Message> errorMessage = check(binding);
|
||||
if (errorMessage.isPresent()) {
|
||||
return ImmutableList.of(errorMessage.get());
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
// Recursive case.
|
||||
@Override
|
||||
public ImmutableList<Message> visit(PrivateElements privateElements) {
|
||||
return check(privateElements.getElements());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Optional<Message> check(Binding<?> binding) {
|
||||
Key<?> key = binding.getKey();
|
||||
// Module Bindings are all explicit and have an ElementSource.
|
||||
ElementSource elementSource = (ElementSource) binding.getSource();
|
||||
RestrictedBindingSource restriction = getRestriction(key);
|
||||
if (restriction == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ImmutableSet<Class<? extends Annotation>> permits = getAllPermits(elementSource);
|
||||
ImmutableSet<Class<? extends Annotation>> acceptablePermits =
|
||||
ImmutableSet.copyOf(restriction.permits());
|
||||
boolean bindingPermitted = permits.stream().anyMatch(acceptablePermits::contains);
|
||||
if (bindingPermitted || isExempt(elementSource, restriction.exemptModules())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String violationMessage =
|
||||
getViolationMessage(
|
||||
key, restriction.explanation(), acceptablePermits, key.getAnnotationType() != null);
|
||||
if (restriction.restrictionLevel() == RestrictionLevel.WARNING) {
|
||||
Formatter sourceFormatter = new Formatter();
|
||||
Errors.formatSource(sourceFormatter, elementSource);
|
||||
logger.log(Level.WARNING, violationMessage + "\n" + sourceFormatter);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new Message(elementSource, violationMessage));
|
||||
}
|
||||
|
||||
private static String getViolationMessage(
|
||||
Key<?> key,
|
||||
String explanation,
|
||||
ImmutableSet<Class<? extends Annotation>> acceptablePermits,
|
||||
boolean annotationRestricted) {
|
||||
return String.format(
|
||||
"Unable to bind key: %s. One of the modules that created this binding has to be annotated"
|
||||
+ " with one of %s, because the key's %s is annotated with @RestrictedBindingSource."
|
||||
+ " %s",
|
||||
key,
|
||||
acceptablePermits.stream().map(a -> "@" + a.getName()).collect(toList()),
|
||||
annotationRestricted ? "annotation" : "type",
|
||||
explanation);
|
||||
}
|
||||
|
||||
/** Get all permits on the element source chain. */
|
||||
private static ImmutableSet<Class<? extends Annotation>> getAllPermits(
|
||||
ElementSource elementSource) {
|
||||
ImmutableSet.Builder<Class<? extends Annotation>> permitsBuilder = ImmutableSet.builder();
|
||||
permitsBuilder.addAll(elementSource.moduleSource.getPermitMap().getPermits(elementSource));
|
||||
if (elementSource.scanner != null) {
|
||||
getPermits(elementSource.scanner.getClass()).forEach(permitsBuilder::add);
|
||||
}
|
||||
if (elementSource.getOriginalElementSource() != null
|
||||
&& elementSource.trustedOriginalElementSource) {
|
||||
permitsBuilder.addAll(getAllPermits(elementSource.getOriginalElementSource()));
|
||||
}
|
||||
return permitsBuilder.build();
|
||||
}
|
||||
|
||||
private static boolean isExempt(ElementSource elementSource, String exemptModulesRegex) {
|
||||
if (exemptModulesRegex.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Pattern exemptModulePattern = Pattern.compile(exemptModulesRegex);
|
||||
// TODO(b/156759807): Switch to Streams.stream (instead of inlining it).
|
||||
return StreamSupport.stream(getAllModules(elementSource).spliterator(), false)
|
||||
.anyMatch(moduleName -> exemptModulePattern.matcher(moduleName).matches());
|
||||
}
|
||||
|
||||
private static Iterable<String> getAllModules(ElementSource elementSource) {
|
||||
List<String> modules = elementSource.getModuleClassNames();
|
||||
if (elementSource.getOriginalElementSource() == null
|
||||
|| !elementSource.trustedOriginalElementSource) {
|
||||
return modules;
|
||||
}
|
||||
return Iterables.concat(modules, getAllModules(elementSource.getOriginalElementSource()));
|
||||
}
|
||||
|
||||
private static void clear(Element element) {
|
||||
element.acceptVisitor(
|
||||
new DefaultElementVisitor<Void>() {
|
||||
// Base case.
|
||||
@Override
|
||||
protected Void visitOther(Element element) {
|
||||
Object source = element.getSource();
|
||||
// Some Module Elements, like Message, don't always have an ElementSource.
|
||||
if (source instanceof ElementSource) {
|
||||
clear((ElementSource) source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Recursive case.
|
||||
@Override
|
||||
public Void visit(PrivateElements privateElements) {
|
||||
privateElements.getElements().forEach(BindingSourceRestriction::clear);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void clear(ElementSource elementSource) {
|
||||
while (elementSource != null) {
|
||||
elementSource.moduleSource.getPermitMap().clear();
|
||||
elementSource = elementSource.getOriginalElementSource();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the restriction on the given key (null if there is none).
|
||||
*
|
||||
* If the key is annotated then only the annotation restriction matters, the type restriction is
|
||||
* ignored (an annotated type is essentially a new type).
|
||||
**/
|
||||
private static RestrictedBindingSource getRestriction(Key<?> key) {
|
||||
return key.getAnnotationType() == null
|
||||
? key.getTypeLiteral().getRawType().getAnnotation(RestrictedBindingSource.class)
|
||||
: key.getAnnotationType().getAnnotation(RestrictedBindingSource.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the map from each module to all the permit annotations on its module stack.
|
||||
*
|
||||
* <p>Bindings refer to the module that created them via a {@link ModuleSource}. The map built
|
||||
* here maps a module's {@link ModuleSource} to all the {@link RestrictedBindingSource.Permit}
|
||||
* annotations found on the path from the root of the module hierarchy to it. This path contains
|
||||
* all the modules that transitively install the module (including the module itself). This path
|
||||
* is also known as the module stack.
|
||||
*
|
||||
* <p>The map is built by piggybacking on the depth-first traversal of the module hierarchy during
|
||||
* Binder configuration.
|
||||
*/
|
||||
static final class PermitMapConstruction {
|
||||
private static final class PermitMapImpl implements PermitMap {
|
||||
Map<ModuleSource, ImmutableSet<Class<? extends Annotation>>> modulePermits;
|
||||
|
||||
@Override
|
||||
public ImmutableSet<Class<? extends Annotation>> getPermits(ElementSource elementSource) {
|
||||
return modulePermits.get(elementSource.moduleSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
modulePermits = null;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<ModuleSource, ImmutableSet<Class<? extends Annotation>>> modulePermits =
|
||||
new HashMap<>();
|
||||
// Maintains the permits on the current module installation path.
|
||||
ImmutableSet<Class<? extends Annotation>> currentModulePermits = ImmutableSet.of();
|
||||
// Stack tracking the currentModulePermits during module traversal.
|
||||
final Deque<ImmutableSet<Class<? extends Annotation>>> modulePermitsStack = new ArrayDeque<>();
|
||||
|
||||
final PermitMapImpl permitMap = new PermitMapImpl();
|
||||
|
||||
/**
|
||||
* Returns a possibly unfinished map. The map should only be used after the construction is
|
||||
* finished.
|
||||
*/
|
||||
PermitMap getPermitMap() {
|
||||
return permitMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permits on the current module installation path to the permits on the given module
|
||||
* source so that subsequently installed modules may inherit them. Used only for method
|
||||
* scanning, so that modules installed by scanners inherit permits from the method's module.
|
||||
*/
|
||||
void restoreCurrentModulePermits(ModuleSource moduleSource) {
|
||||
currentModulePermits = modulePermits.get(moduleSource);
|
||||
}
|
||||
|
||||
/** Called by the Binder prior to entering a module's configure method. */
|
||||
void pushModule(Class<?> module, ModuleSource moduleSource) {
|
||||
List<Class<? extends Annotation>> newModulePermits =
|
||||
getPermits(module)
|
||||
.filter(permit -> !currentModulePermits.contains(permit))
|
||||
.collect(toList());
|
||||
// Save the parent module's permits so that they can be restored when the Binder exits this
|
||||
// new (child) module's configure method.
|
||||
modulePermitsStack.push(currentModulePermits);
|
||||
if (!newModulePermits.isEmpty()) {
|
||||
currentModulePermits =
|
||||
ImmutableSet.<Class<? extends Annotation>>builder()
|
||||
.addAll(currentModulePermits)
|
||||
.addAll(newModulePermits)
|
||||
.build();
|
||||
}
|
||||
modulePermits.put(moduleSource, currentModulePermits);
|
||||
}
|
||||
|
||||
/** Called by the Binder when it exits a module's configure method. */
|
||||
void popModule() {
|
||||
// Restore the parent module's permits.
|
||||
currentModulePermits = modulePermitsStack.pop();
|
||||
}
|
||||
|
||||
/** Finishes the {@link PermitMap}. Called by the Binder when all modules are installed. */
|
||||
void finish() {
|
||||
permitMap.modulePermits = modulePermits;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean isElementSourceCleared(ElementSource elementSource) {
|
||||
PermitMapImpl permitMap = (PermitMapImpl) elementSource.moduleSource.getPermitMap();
|
||||
return permitMap.modulePermits == null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<? extends Class<? extends Annotation>> getPermits(Class<?> clazz) {
|
||||
Stream<Annotation> annotations = Arrays.stream(clazz.getAnnotations());
|
||||
// Pick up annotations on anonymous classes (e.g. new @Bar Foo() { ... }):
|
||||
if (clazz.getAnnotatedSuperclass() != null) {
|
||||
annotations =
|
||||
Stream.concat(
|
||||
annotations, Arrays.stream(clazz.getAnnotatedSuperclass().getAnnotations()));
|
||||
}
|
||||
return annotations
|
||||
.map(Annotation::annotationType)
|
||||
.filter(a -> a.isAnnotationPresent(RestrictedBindingSource.Permit.class));
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <p>
|
||||
* The {@link #getDeclaringSource() declaring source} refers to a location in
|
||||
* source code that defines the Guice {@link Element element}. For example, if
|
||||
* the element is created from a method annotated by {@literal @Provides}, the
|
||||
* declaring source of element would be the method itself.
|
||||
* <p>
|
||||
* The {@link #getStackTrace()} refers to the sequence of calls ends at one of
|
||||
* {@link com.google.inject.Binder} {@code bindXXX()} methods and eventually
|
||||
* defines the element. Note that {@link #getStackTrace()} lists
|
||||
* {@link StackTraceElement StackTraceElements} in reverse chronological order.
|
||||
* The first element (index zero) is the last method call and the last element
|
||||
* is the first method invocation. By default, the stack trace is not collected.
|
||||
* The default behavior can be changed by setting the
|
||||
* {@code guice_include_stack_traces} flag value. The value can be either
|
||||
* {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that
|
||||
* collecting stack traces for every binding can cause a performance hit when
|
||||
* the injector is created.
|
||||
* <p>
|
||||
* The sequence of class names of {@link com.google.inject.Module modules}
|
||||
* involved in the element creation can be retrieved by
|
||||
* {@link #getModuleClassNames()}. Similar to {@link #getStackTrace()}, the
|
||||
* order is reverse chronological. The first module (index 0) is the module that
|
||||
* installs the {@link Element element}. The last module is the root module.
|
||||
* <p>
|
||||
* In order to support the cases where a Guice {@link Element element} is
|
||||
* created from another Guice {@link Element element} (original) (e.g., by
|
||||
* {@link Element#applyTo}), it also provides a reference to the original
|
||||
* element source ({@link #getOriginalElementSource()}).
|
||||
* Contains information about where and how an {@link Element element} was bound.
|
||||
*
|
||||
* <p>The {@link #getDeclaringSource() declaring source} refers to a location in source code that
|
||||
* defines the Guice {@link Element element}. For example, if the element is created from a method
|
||||
* annotated by {@literal @Provides}, the declaring source of element would be the method itself.
|
||||
*
|
||||
* <p>The {@link #getStackTrace()} refers to the sequence of calls ends at one of {@link
|
||||
* com.google.inject.Binder} {@code bindXXX()} methods and eventually defines the element. Note that
|
||||
* {@link #getStackTrace()} lists {@link StackTraceElement StackTraceElements} in reverse
|
||||
* chronological order. The first element (index zero) is the last method call and the last element
|
||||
* is the first method invocation. By default, the stack trace is not collected. The default
|
||||
* behavior can be changed by setting the {@code guice_include_stack_traces} flag value. The value
|
||||
* can be either {@code OFF}, {@code ONLY_FOR_DECLARING_SOURCE} or {@code COMPLETE}. Note that
|
||||
* collecting stack traces for every binding can cause a performance hit when the injector is
|
||||
* created.
|
||||
*
|
||||
* <p>The sequence of class names of {@link com.google.inject.Module modules} involved in the
|
||||
* element creation can be retrieved by {@link #getModuleClassNames()}. Similar to {@link
|
||||
* #getStackTrace()}, the order is reverse chronological. The first module (index 0) is the module
|
||||
* that installs the {@link Element element}. The last module is the root module.
|
||||
*
|
||||
* <p>In order to support the cases where a Guice {@link Element element} is created from another
|
||||
* Guice {@link Element element} (original) (e.g., by {@link Element#applyTo}), it also provides a
|
||||
* reference to the original element source ({@link #getOriginalElementSource()}).
|
||||
*
|
||||
* @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
|
||||
* <p>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<String> 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:
|
||||
* <p>
|
||||
* {@code
|
||||
* 0 - Binder.bind(),
|
||||
* 1 - ModuleTwo.configure(),
|
||||
* 2 - Binder.install(),
|
||||
* 3 - ModuleOne.configure(),
|
||||
* 4 - theRest().
|
||||
* }
|
||||
* <p>
|
||||
* 1 and 3 are returned.
|
||||
* <p>
|
||||
* In the cases where stack trace is not available (i.e., the stack trace was not collected),
|
||||
*
|
||||
* <ol>
|
||||
* <li>{@code Binder.bind()}
|
||||
* <li>{@code ModuleTwo.configure()}
|
||||
* <li>{@code Binder.install()}
|
||||
* <li>{@code ModuleOne.configure()}
|
||||
* <li>{@code theRest().
|
||||
* </ol>
|
||||
*
|
||||
* <p>1 and 3 are returned.
|
||||
*
|
||||
* <p>In the cases where stack trace is not available (i.e., the stack trace was not collected),
|
||||
* it returns -1 for all module positions.
|
||||
*/
|
||||
public List<Integer> getModuleConfigurePositionsInStackTrace() {
|
||||
|
@ -140,11 +161,11 @@ public final class ElementSource {
|
|||
|
||||
/**
|
||||
* Returns the sequence of method calls that ends at one of {@link com.google.inject.Binder}
|
||||
* {@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();
|
||||
|
|
|
@ -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<Object, Object> GET_INSTANCE_VISITOR =
|
||||
new DefaultBindingTargetVisitor<>() {
|
||||
@Override
|
||||
public Object visit(InstanceBinding<?> binding) {
|
||||
return binding.getInstance();
|
||||
}
|
||||
new DefaultBindingTargetVisitor<Object, Object>() {
|
||||
@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<Element> 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<Element> 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<Element> getElements(Iterable<? extends Module> modules) {
|
||||
return getElements(Stage.DEVELOPMENT, modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the elements executed by {@code modules}.
|
||||
*/
|
||||
/** Records the elements executed by {@code modules}. */
|
||||
public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) {
|
||||
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}.
|
||||
*
|
||||
* <p>Using Binder.withSource is not trustworthy because it's a public API that external users can
|
||||
* use to spoof the original ElementSource of a binding by calling withSource(bogusElementSource).
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public static Module getModule(final Iterable<? extends Element> elements) {
|
||||
return new ElementsAsModule(elements);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
|
||||
return (BindingTargetVisitor<T, T>) 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<? extends Element> elements) {
|
||||
return new ElementsAsModule(elements);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
|
||||
return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR;
|
||||
}
|
||||
|
||||
private static class ModuleInfo {
|
||||
private 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<ModuleAnnotatedMethodScanner> 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<RecordingBinder> privateBinders;
|
||||
/**
|
||||
* The current modules stack
|
||||
*/
|
||||
/** All children private binders, so we can scan through them. */
|
||||
private final List<RecordingBinder> privateBindersForScanning;
|
||||
|
||||
private final BindingSourceRestriction.PermitMapConstruction permitMapConstruction;
|
||||
|
||||
/** The current modules stack */
|
||||
private ModuleSource moduleSource = null;
|
||||
/**
|
||||
* The current scanner.
|
||||
*
|
||||
* <p>Note that scanners cannot nest, ie. a scanner cannot install a module that requires
|
||||
* scanning - except the built-in @Provides* methods. The built-in scanner isn't tracked by this
|
||||
* variable, only custom scanners are.
|
||||
*/
|
||||
private ModuleAnnotatedMethodScanner scannerSource = null;
|
||||
|
||||
private ModuleAnnotatedMethodScanner currentScanner = null;
|
||||
private boolean trustedSource = false;
|
||||
|
||||
private RecordingBinder(Stage stage) {
|
||||
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<Object>) TypeLiteral.get(instance.getClass()), instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void requestInjection(TypeLiteral<T> type, T instance) {
|
||||
elements.add(new InjectionRequest<>(getElementSource(), MoreTypes.canonicalizeForKey(type),
|
||||
instance));
|
||||
checkNotNull(instance, "instance");
|
||||
elements.add(
|
||||
new InjectionRequest<T>(
|
||||
getElementSource(), MoreTypes.canonicalizeForKey(type), instance));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> typeLiteral) {
|
||||
final MembersInjectorLookup<T> element = new MembersInjectorLookup<>(getElementSource(),
|
||||
MoreTypes.canonicalizeForKey(typeLiteral));
|
||||
final MembersInjectorLookup<T> element =
|
||||
new MembersInjectorLookup<T>(
|
||||
getElementSource(), MoreTypes.canonicalizeForKey(typeLiteral));
|
||||
elements.add(element);
|
||||
return element.getMembersInjector();
|
||||
}
|
||||
|
@ -247,8 +284,8 @@ public final class Elements {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void bindListener(Matcher<? super Binding<?>> bindingMatcher,
|
||||
ProvisionListener... listeners) {
|
||||
public void bindListener(
|
||||
Matcher<? super Binding<?>> 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<Module, ModuleInfo> entry : Maps.newLinkedHashMap(modules).entrySet()) {
|
||||
Module module = entry.getKey();
|
||||
ModuleInfo info = entry.getValue();
|
||||
if (info.skipScanning) {
|
||||
continue;
|
||||
}
|
||||
Iterable<ModuleAnnotatedMethodScanner> 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<Module, ModuleInfo> 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<Message> 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<Message> 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<Message> 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.
|
||||
*
|
||||
* <p>Should only be called during module scanning, because at that point registering new
|
||||
* scanners is forbidden.
|
||||
*/
|
||||
private Iterable<ModuleAnnotatedMethodScanner> getAllScanners() {
|
||||
if (privateElements == null) {
|
||||
return scanners;
|
||||
}
|
||||
// Private binders have their own set of scanners and they inherit from their parent.
|
||||
return Iterables.concat(scanners, parent.getAllScanners());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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 <T> AnnotatedBindingBuilder<T> bind(Key<T> key) {
|
||||
return new BindingBuilder<>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key));
|
||||
BindingBuilder<T> builder =
|
||||
new BindingBuilder<T>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key));
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -399,14 +489,24 @@ public final class Elements {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void convertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
|
||||
TypeConverter converter) {
|
||||
public void convertToTypes(
|
||||
Matcher<? super TypeLiteral<?>> 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 <T> AnnotatedElementBuilder exposeInternal(Key<T> 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<? extends Annotation> annotationType) {
|
||||
}
|
||||
public void annotatedWith(Class<? extends Annotation> annotationType) {}
|
||||
|
||||
@Override
|
||||
public void annotatedWith(Annotation annotation) {
|
||||
}
|
||||
public void annotatedWith(Annotation annotation) {}
|
||||
};
|
||||
}
|
||||
|
||||
ExposureBuilder<T> builder =
|
||||
new ExposureBuilder<>(this, getElementSource(), MoreTypes.canonicalizeKey(key));
|
||||
new ExposureBuilder<T>(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";
|
||||
|
|
127
src/main/java/com/google/inject/spi/ErrorDetail.java
Normal file
127
src/main/java/com/google/inject/spi/ErrorDetail.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package com.google.inject.spi;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.internal.Messages;
|
||||
import java.io.Serializable;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Details about a single Guice error and supports formatting itself in the context of other Guice
|
||||
* errors.
|
||||
*
|
||||
* <p>WARNING: The class and its APIs are still experimental and subject to change.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class ErrorDetail<SelfT extends ErrorDetail<SelfT>> implements Serializable {
|
||||
private final String message;
|
||||
private final ImmutableList<Object> sources;
|
||||
private final Throwable cause;
|
||||
|
||||
protected ErrorDetail(String message, List<Object> sources, Throwable cause) {
|
||||
this.message = message;
|
||||
this.sources = ImmutableList.copyOf(sources);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this error can be merged with the {@code otherError} and formatted together.
|
||||
*
|
||||
* <p>By default this return false and implementations that support merging with other errors
|
||||
* should override this method.
|
||||
*/
|
||||
public boolean isMergeable(ErrorDetail<?> otherError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this error along with other errors that are mergeable with this error.
|
||||
*
|
||||
* <p>{@code mergeableErrors} is a list that contains all other errors that are reported in the
|
||||
* same exception that are considered to be mergable with this error base on result of calling
|
||||
* {@link #isMergeable}. The list will be empty if non of the other errors are mergable with this
|
||||
* error.
|
||||
*
|
||||
* <p>Formatted error has the following structure:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Summary of the error
|
||||
* <li>Details about the error such as the source of the error
|
||||
* <li>Hints for fixing the error if available
|
||||
* <li>Link to the documentation on this error in greater detail
|
||||
*
|
||||
* @param index index for this error
|
||||
* @param mergeableErrors list of errors that are mergeable with this error
|
||||
* @param formatter for printing the error message
|
||||
*/
|
||||
public final void format(int index, List<ErrorDetail<?>> mergeableErrors, Formatter formatter) {
|
||||
String id = getErrorIdentifier().map(s -> "[" + Messages.redBold(s) + "]: ").orElse("");
|
||||
formatter.format("%s) %s%s%n", index, id, getMessage());
|
||||
formatDetail(mergeableErrors, formatter);
|
||||
// TODO(b/151482394): Output potiential fixes for the error
|
||||
Optional<String> learnMoreLink = getLearnMoreLink();
|
||||
if (learnMoreLink.isPresent()) {
|
||||
formatter.format("%n%s%n", Messages.bold("Learn more:"));
|
||||
formatter.format(" %s%n", Messages.underline(learnMoreLink.get()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the detail of this error message along with other errors that are mergeable with this
|
||||
* error. This is called from {@link #format}.
|
||||
*
|
||||
* <p>{@code mergeableErrors} is a list that contains all other errors that are reported in the
|
||||
* same exception that are considered to be mergable with this error base on result of calling
|
||||
* {@link #isMergeable}. The list will be empty if non of the other errors are mergable with this
|
||||
* error.
|
||||
*
|
||||
* @param mergeableErrors list of errors that are mergeable with this error
|
||||
* @param formatter for printing the error message
|
||||
*/
|
||||
protected abstract void formatDetail(List<ErrorDetail<?>> mergeableErrors, Formatter formatter);
|
||||
|
||||
/**
|
||||
* Returns an optional link to additional documentation about this error to be included in the
|
||||
* formatted error message.
|
||||
*/
|
||||
protected Optional<String> getLearnMoreLink() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Returns an optional string identifier for this error. */
|
||||
protected Optional<String> getErrorIdentifier() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<Object> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(message, cause, sources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ErrorDetail)) {
|
||||
return false;
|
||||
}
|
||||
ErrorDetail<?> e = (ErrorDetail<?>) o;
|
||||
return message.equals(e.message) && Objects.equal(cause, e.cause) && sources.equals(e.sources);
|
||||
}
|
||||
|
||||
/** Returns a new instance of the same {@link ErrorDetail} with updated sources. */
|
||||
public abstract SelfT withSources(List<Object> newSources);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* try {
|
||||
* bindPropertiesFromFile();
|
||||
|
@ -21,21 +42,36 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
* addError(e);
|
||||
* }</pre>
|
||||
*
|
||||
* @author crazybob@google.com (Bob Lee)
|
||||
*/
|
||||
public final class Message implements Element {
|
||||
public final class Message implements Serializable, Element {
|
||||
private final ErrorId errorId;
|
||||
private final ErrorDetail<?> errorDetail;
|
||||
|
||||
private final String message;
|
||||
|
||||
private final Throwable cause;
|
||||
|
||||
private final List<Object> sources;
|
||||
|
||||
public Message(List<Object> 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<Object> sources, String message, Throwable cause) {
|
||||
this.errorId = errorId;
|
||||
this.errorDetail = new GenericErrorDetail(errorId, message, sources, cause);
|
||||
}
|
||||
|
||||
/** @since 2.0 */
|
||||
public Message(List<Object> sources, String message, Throwable cause) {
|
||||
this(ErrorId.OTHER, sources, message, cause);
|
||||
}
|
||||
|
||||
/** @since 4.0 */
|
||||
public Message(String message, Throwable cause) {
|
||||
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<Object> 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<Object> 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> T acceptVisitor(ElementVisitor<T> 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<Object> newSources) {
|
||||
return new Message(errorId, errorDetail.withSources(newSources));
|
||||
}
|
||||
|
||||
/**
|
||||
* When serialized, we convert the error detail to a {@link GenericErrorDetail} with string
|
||||
* sources. This hurts our formatting, but it guarantees that the receiving end will be able to
|
||||
* read the message.
|
||||
*/
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
Object[] sourcesAsStrings = getSources().toArray();
|
||||
for (int i = 0; i < sourcesAsStrings.length; i++) {
|
||||
sourcesAsStrings[i] = Errors.convert(sourcesAsStrings[i]).toString();
|
||||
}
|
||||
return new Message(
|
||||
errorId,
|
||||
new GenericErrorDetail(
|
||||
errorId, getMessage(), ImmutableList.copyOf(sourcesAsStrings), getCause()));
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0;
|
||||
}
|
||||
|
|
|
@ -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> T acceptVisitor(ElementVisitor<T> 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)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>The permit map is a binder-scoped object, but it's saved here because these maps have to
|
||||
* outlive the binders that created them in order to be used at injector creation, and there isn't
|
||||
* a 'BinderSource' object.
|
||||
*/
|
||||
private final BindingSourceRestriction.PermitMap permitMap;
|
||||
|
||||
/**
|
||||
* The chunk of call stack that starts from the parent module {@link Module#configure(Binder)
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());</pre>
|
||||
* convertToTypes(Matchers.only(TypeLiteral.get(DateTime.class)), new DateTimeConverter());
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public final class TypeConverterBinding implements Element {
|
||||
|
@ -20,13 +22,15 @@ public final class TypeConverterBinding implements Element {
|
|||
private final Matcher<? super TypeLiteral<?>> typeMatcher;
|
||||
private final TypeConverter typeConverter;
|
||||
|
||||
public TypeConverterBinding(Object source, Matcher<? super TypeLiteral<?>> typeMatcher,
|
||||
TypeConverter typeConverter) {
|
||||
/** @since 3.0 */
|
||||
public TypeConverterBinding(
|
||||
Object source, Matcher<? super TypeLiteral<?>> 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> T acceptVisitor(ElementVisitor<T> 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)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
*
|
||||
* <pre>
|
||||
* 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:
|
||||
*
|
||||
* <pre>
|
||||
* 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}.
|
||||
*
|
||||
* <p>Although sometimes helpful, this method is rarely necessary. Most Guice APIs accept multiple
|
||||
* arguments or (like {@code install()}) can be called repeatedly. Where possible, external APIs
|
||||
* that require a single module should similarly be adapted to permit multiple modules.
|
||||
*/
|
||||
public static Module combine(Module... modules) {
|
||||
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}.
|
||||
*
|
||||
* <p>Although sometimes helpful, this method is rarely necessary. Most Guice APIs accept multiple
|
||||
* arguments or (like {@code install()}) can be called repeatedly. Where possible, external APIs
|
||||
* that require a single module should similarly be adapted to permit multiple modules.
|
||||
*/
|
||||
public static Module combine(Iterable<? extends Module> modules) {
|
||||
return new CombinedModule(modules);
|
||||
}
|
||||
|
||||
private static Module extractScanners(Iterable<Element> elements) {
|
||||
final List<ModuleAnnotatedMethodScannerBinding> scanners = Lists.newArrayList();
|
||||
ElementVisitor<Void> visitor = new DefaultElementVisitor<>() {
|
||||
@Override
|
||||
public Void visit(ModuleAnnotatedMethodScannerBinding binding) {
|
||||
scanners.add(binding);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
for (Element element : elements) {
|
||||
element.acceptVisitor(visitor);
|
||||
}
|
||||
return new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
for (ModuleAnnotatedMethodScannerBinding scanner : scanners) {
|
||||
scanner.applyTo(binder());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* See the EDSL example at {@link Modules#override(Module[]) override()}.
|
||||
*/
|
||||
public interface OverriddenModuleBuilder {
|
||||
|
||||
/**
|
||||
* See the EDSL example at {@link Modules#override(Module[]) override()}.
|
||||
*/
|
||||
Module with(Module... overrides);
|
||||
|
||||
/**
|
||||
* See the EDSL example at {@link Modules#override(Module[]) override()}.
|
||||
*/
|
||||
Module with(Iterable<? extends Module> overrides);
|
||||
}
|
||||
|
||||
private static class EmptyModule implements Module {
|
||||
public void configure(Binder binder) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class CombinedModule implements Module {
|
||||
final Set<Module> 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<? extends Module> overrides);
|
||||
}
|
||||
|
||||
private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder {
|
||||
private final ImmutableSet<Module> baseModules;
|
||||
|
||||
// TODO(diamondm) checkArgument(!baseModules.isEmpty())?
|
||||
private RealOverriddenModuleBuilder(Iterable<? extends Module> 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<? extends Module> overrides) {
|
||||
return new OverrideModule(overrides, baseModules);
|
||||
}
|
||||
|
@ -174,6 +184,7 @@ public final class Modules {
|
|||
private final ImmutableSet<Module> overrides;
|
||||
private final ImmutableSet<Module> baseModules;
|
||||
|
||||
// TODO(diamondm) checkArgument(!overrides.isEmpty())?
|
||||
OverrideModule(Iterable<? extends Module> overrides, ImmutableSet<Module> 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<Element> elements = new LinkedHashSet<>(baseElements);
|
||||
final Module scannersModule = extractScanners(elements);
|
||||
final List<Element> overrideElements = Elements.getElements(currentStage(),
|
||||
ImmutableList.<Module>builder().addAll(overrides).add(scannersModule).build());
|
||||
final List<Element> overrideElements =
|
||||
Elements.getElements(
|
||||
currentStage(),
|
||||
ImmutableList.<Module>builder().addAll(overrides).add(scannersModule).build());
|
||||
|
||||
final Set<Key<?>> overriddenKeys = Sets.newHashSet();
|
||||
final Map<Class<? extends Annotation>, 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<Key<?>> keysToSkip) {
|
||||
PrivateBinder privateBinder = binder.withSource(privateElements.getSource())
|
||||
.newPrivateBinder();
|
||||
PrivateBinder privateBinder =
|
||||
binder.withSource(privateElements.getSource()).newPrivateBinder();
|
||||
|
||||
Set<Key<?>> 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<Object> 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<Scope>() {
|
||||
@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<Element> elements) {
|
||||
final List<ModuleAnnotatedMethodScannerBinding> scanners = Lists.newArrayList();
|
||||
ElementVisitor<Void> visitor =
|
||||
new DefaultElementVisitor<Void>() {
|
||||
@Override
|
||||
public Void visit(ModuleAnnotatedMethodScannerBinding binding) {
|
||||
scanners.add(binding);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
for (Element element : elements) {
|
||||
element.acceptVisitor(visitor);
|
||||
}
|
||||
return new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
for (ModuleAnnotatedMethodScannerBinding scanner : scanners) {
|
||||
scanner.applyTo(binder());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a module that will configure the injector to require explicit bindings.
|
||||
*
|
||||
* @since 4.2.3
|
||||
*/
|
||||
public static Module requireExplicitBindingsModule() {
|
||||
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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue