/* * 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; 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. * * <p>For example, {@code Key.get(Service.class, Transactional.class)} will match: * * <pre> * {@literal @}Inject * public void setService({@literal @}Transactional Service service) { * ... * } * </pre> * * <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. * * @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. private String toString; /** * 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>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.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>Example usage for a binding of type {@code Foo} annotated with {@code @Bar}: * * <p>{@code new Key<Foo>(new Bar()) {}}. */ @SuppressWarnings("unchecked") protected Key(Annotation annotation) { // no usages, not test-covered this.annotationStrategy = strategyFor(annotation); 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>Example usage for a binding of type {@code Foo}: * * <p>{@code new Key<Foo>() {}}. */ @SuppressWarnings("unchecked") protected Key() { this.annotationStrategy = NullAnnotationStrategy.INSTANCE; this.typeLiteral = MoreTypes.canonicalizeForKey( (TypeLiteral<T>) 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<T>) TypeLiteral.get(type)); this.hashCode = computeHashCode(); } /** 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(); } /** Computes the hash code for this key. */ private int computeHashCode() { return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); } /** Gets the key type. */ public final TypeLiteral<T> getTypeLiteral() { return typeLiteral; } /** Gets the annotation type. */ public final Class<? extends Annotation> 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<? super T> getRawType() { return typeLiteral.getRawType(); } /** Gets the key of this key's provider. */ Key<Provider<T>> 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; } /** 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)); } /** * Returns a new key of the specified type with the same annotation as this key. * * @since 3.0 */ public <T> Key<T> ofType(Class<T> type) { return new Key<T>(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); } /** * 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); } /** * 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. * * @since 3.0 */ public Key<T> withoutAttributes() { return new Key<T>(typeLiteral, annotationStrategy.withoutAttributes()); } 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 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<? extends Annotation> getAnnotationType() { return null; } @Override public String toString() { return "[none]"; } } // 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<? extends Annotation> 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<? extends Annotation> annotationType; // Keep the instance around if we have it so the client can request it. final Annotation annotation; AnnotationTypeStrategy(Class<? extends Annotation> 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<? extends Annotation> 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(); } } }