All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.Key Maven / Gradle / Ivy

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;

/**
 * Binding key consisting of an injection type and an optional annotation.
 * Matches the type and annotation at a point of injection.
 *
 * 

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

 *   {@literal @}Inject
 *   public void setService({@literal @}Transactional Service service) {
 *     ...
 *   }
 * 
* *

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

Keys do not differentiate between primitive types (int, char, etc.) and * their corresponding wrapper types (Integer, Character, etc.). Primitive * types will be replaced with their wrapper types when keys are created. */ public class Key { private final AnnotationStrategy annotationStrategy; private final TypeLiteral typeLiteral; private final int hashCode; // This field is updated using the 'Data-Race-Ful' lazy intialization pattern // See http://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html for a detailed // explanation. private String toString; /** * Constructs a new key. Derives the type from this class's type parameter. * *

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

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

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

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

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

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

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

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

{@code new Key() {}}. */ @SuppressWarnings("unchecked") protected Key() { this.annotationStrategy = NullAnnotationStrategy.INSTANCE; this.typeLiteral = MoreTypes.canonicalizeForKey( (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass())); this.hashCode = computeHashCode(); } /** * Unsafe. Constructs a key from a manually specified type. */ @SuppressWarnings("unchecked") private Key(Type type, AnnotationStrategy annotationStrategy) { this.annotationStrategy = annotationStrategy; this.typeLiteral = MoreTypes.canonicalizeForKey((TypeLiteral) TypeLiteral.get(type)); this.hashCode = computeHashCode(); } /** * Constructs a key from a manually specified type. */ private Key(TypeLiteral typeLiteral, AnnotationStrategy annotationStrategy) { this.annotationStrategy = annotationStrategy; this.typeLiteral = MoreTypes.canonicalizeForKey(typeLiteral); this.hashCode = computeHashCode(); } /** * Gets a key for an injection type and an annotation strategy. */ static Key get(Class type, AnnotationStrategy annotationStrategy) { return new Key(type, annotationStrategy); } /** * Gets a key for an injection type. */ public static Key get(Class type) { return new Key(type, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(Class type, Class annotationType) { return new Key(type, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(Class type, Annotation annotation) { return new Key(type, strategyFor(annotation)); } /** * Gets a key for an injection type. */ public static Key get(Type type) { return new Key(type, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(Type type, Class annotationType) { return new Key(type, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(Type type, Annotation annotation) { return new Key(type, strategyFor(annotation)); } /** * Gets a key for an injection type. */ public static Key get(TypeLiteral typeLiteral) { return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(TypeLiteral typeLiteral, Class annotationType) { return new Key(typeLiteral, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(TypeLiteral typeLiteral, Annotation annotation) { return new Key(typeLiteral, strategyFor(annotation)); } /** * Gets the strategy for an annotation. */ static AnnotationStrategy strategyFor(Annotation annotation) { checkNotNull(annotation, "annotation"); Class annotationType = annotation.annotationType(); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); if (Annotations.isMarker(annotationType)) { return new AnnotationTypeStrategy(annotationType, annotation); } return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation)); } /** * Gets the strategy for an annotation type. */ static AnnotationStrategy strategyFor(Class annotationType) { annotationType = Annotations.canonicalizeIfNamed(annotationType); if (isAllDefaultMethods(annotationType)) { return strategyFor(generateAnnotation(annotationType)); } checkNotNull(annotationType, "annotation type"); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); return new AnnotationTypeStrategy(annotationType, null); } private static void ensureRetainedAtRuntime( Class annotationType) { checkArgument(Annotations.isRetainedAtRuntime(annotationType), "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", annotationType.getName()); } private static void ensureIsBindingAnnotation(Class annotationType) { checkArgument(Annotations.isBindingAnnotation(annotationType), "%s is not a binding annotation. Please annotate it with @BindingAnnotation.", annotationType.getName()); } /** * Computes the hash code for this key. */ private int computeHashCode() { return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); } /** * Gets the key type. */ public final TypeLiteral getTypeLiteral() { return typeLiteral; } /** * Gets the annotation type. */ public final Class getAnnotationType() { return annotationStrategy.getAnnotationType(); } /** * Gets the annotation. */ public final Annotation getAnnotation() { return annotationStrategy.getAnnotation(); } boolean hasAnnotationType() { return annotationStrategy.getAnnotationType() != null; } String getAnnotationName() { Annotation annotation = annotationStrategy.getAnnotation(); if (annotation != null) { return annotation.toString(); } // not test-covered return annotationStrategy.getAnnotationType().toString(); } Class getRawType() { return typeLiteral.getRawType(); } /** * Gets the key of this key's provider. */ Key> providerKey() { return ofType(typeLiteral.providerType()); } @Override public final boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Key)) { return false; } Key other = (Key) o; return annotationStrategy.equals(other.annotationStrategy) && typeLiteral.equals(other.typeLiteral); } @Override public final int hashCode() { return this.hashCode; } @Override public final String toString() { // Note: to not introduce dangerous data races the field should only be read once in this // method. String local = toString; if (local == null) { local = "Key[type=" + typeLiteral + ", annotation=" + annotationStrategy + "]"; toString = local; } return local; } /** * Returns a new key of the specified type with the same annotation as this * key. */ public Key ofType(Class type) { return new Key(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. */ public Key ofType(Type type) { return new Key(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. */ public Key ofType(TypeLiteral type) { return new Key(type, annotationStrategy); } /** * Returns true if this key has annotation attributes. */ public boolean hasAttributes() { return annotationStrategy.hasAttributes(); } /** * Returns this key without annotation attributes, i.e. with only the * annotation type. */ public Key withoutAttributes() { return new Key(typeLiteral, annotationStrategy.withoutAttributes()); } enum NullAnnotationStrategy implements AnnotationStrategy { INSTANCE; @Override public boolean hasAttributes() { return false; } @Override public AnnotationStrategy withoutAttributes() { throw new UnsupportedOperationException("Key already has no attributes."); } @Override public Annotation getAnnotation() { return null; } @Override public Class getAnnotationType() { return null; } @Override public String toString() { return "[none]"; } } interface AnnotationStrategy { Annotation getAnnotation(); Class getAnnotationType(); boolean hasAttributes(); AnnotationStrategy withoutAttributes(); } // this class not test-covered static class AnnotationInstanceStrategy implements AnnotationStrategy { final Annotation annotation; AnnotationInstanceStrategy(Annotation annotation) { this.annotation = checkNotNull(annotation, "annotation"); } @Override public boolean hasAttributes() { return true; } @Override public AnnotationStrategy withoutAttributes() { return new AnnotationTypeStrategy(getAnnotationType(), annotation); } @Override public Annotation getAnnotation() { return annotation; } @Override public Class getAnnotationType() { return annotation.annotationType(); } @Override public boolean equals(Object o) { if (!(o instanceof AnnotationInstanceStrategy)) { return false; } AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o; return annotation.equals(other.annotation); } @Override public int hashCode() { return annotation.hashCode(); } @Override public String toString() { return annotation.toString(); } } static class AnnotationTypeStrategy implements AnnotationStrategy { final Class annotationType; // Keep the instance around if we have it so the client can request it. final Annotation annotation; AnnotationTypeStrategy(Class annotationType, Annotation annotation) { this.annotationType = checkNotNull(annotationType, "annotation type"); this.annotation = annotation; } @Override public boolean hasAttributes() { return false; } @Override public AnnotationStrategy withoutAttributes() { throw new UnsupportedOperationException("Key already has no attributes."); } @Override public Annotation getAnnotation() { return annotation; } @Override public Class getAnnotationType() { return annotationType; } @Override public boolean equals(Object o) { if (!(o instanceof AnnotationTypeStrategy)) { return false; } AnnotationTypeStrategy other = (AnnotationTypeStrategy) o; return annotationType.equals(other.annotationType); } @Override public int hashCode() { return annotationType.hashCode(); } @Override public String toString() { return "@" + annotationType.getName(); } } }