/** * 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.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; /** * 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. * * @author crazybob@google.com (Bob Lee) */ public class Key { private final AnnotationStrategy annotationStrategy; private final TypeLiteral typeLiteral; private final int hashCode; private final Supplier toStringSupplier; /** * 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(); this.toStringSupplier = createToStringSupplier(); } /** * 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(); this.toStringSupplier = createToStringSupplier(); } /** * 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(); this.toStringSupplier = createToStringSupplier(); } /** * 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(); this.toStringSupplier = createToStringSupplier(); } /** 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(); this.toStringSupplier = createToStringSupplier(); } /** * Computes the hash code for this key. */ private int computeHashCode() { return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); } /** * @return a {@link Supplier} which memoizes the value for lazy initialization. */ private Supplier createToStringSupplier() { // The performance hit on access is acceptable since the intended use is for non-performance- // critical applications such as debugging and logging. return Suppliers.memoize(new Supplier() { @Override public String get() { return "Key[type=" + typeLiteral + ", annotation=" + annotationStrategy + "]"; } }); } /** * 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() { return toStringSupplier.get(); } /** * Gets a key for an injection type and an annotation strategy. */ static Key get(Class type, AnnotationStrategy annotationStrategy) { return new Key(type, annotationStrategy); } /** * Gets a key for an injection type. */ public static Key get(Class type) { return new Key(type, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(Class type, Class annotationType) { return new Key(type, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(Class type, Annotation annotation) { return new Key(type, strategyFor(annotation)); } /** * Gets a key for an injection type. */ public static Key get(Type type) { return new Key(type, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(Type type, Class annotationType) { return new Key(type, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(Type type, Annotation annotation) { return new Key(type, strategyFor(annotation)); } /** * Gets a key for an injection type. */ public static Key get(TypeLiteral typeLiteral) { return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); } /** * Gets a key for an injection type and an annotation type. */ public static Key get(TypeLiteral typeLiteral, Class annotationType) { return new Key(typeLiteral, strategyFor(annotationType)); } /** * Gets a key for an injection type and an annotation. */ public static Key get(TypeLiteral typeLiteral, Annotation annotation) { return new Key(typeLiteral, strategyFor(annotation)); } /** * Returns a new key of the specified type with the same annotation as this * key. * * @since 3.0 */ public Key ofType(Class type) { return new Key(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. * * @since 3.0 */ public Key ofType(Type type) { return new Key(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. * * @since 3.0 */ public Key ofType(TypeLiteral type) { return new Key(type, annotationStrategy); } /** * 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 withoutAttributes() { return new Key(typeLiteral, annotationStrategy.withoutAttributes()); } interface AnnotationStrategy { Annotation getAnnotation(); Class getAnnotationType(); boolean hasAttributes(); AnnotationStrategy withoutAttributes(); } /** * Gets the strategy for an annotation. */ static AnnotationStrategy strategyFor(Annotation annotation) { checkNotNull(annotation, "annotation"); Class annotationType = annotation.annotationType(); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); if (Annotations.isMarker(annotationType)) { return new AnnotationTypeStrategy(annotationType, annotation); } return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation)); } /** * Gets the strategy for an annotation type. */ static AnnotationStrategy strategyFor(Class annotationType) { annotationType = Annotations.canonicalizeIfNamed(annotationType); if (isAllDefaultMethods(annotationType)) { return strategyFor(generateAnnotation(annotationType)); } checkNotNull(annotationType, "annotation type"); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); return new AnnotationTypeStrategy(annotationType, null); } private static void ensureRetainedAtRuntime( Class annotationType) { checkArgument(Annotations.isRetainedAtRuntime(annotationType), "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", annotationType.getName()); } private static void ensureIsBindingAnnotation(Class annotationType) { checkArgument(Annotations.isBindingAnnotation(annotationType), "%s is not a binding annotation. Please annotate it with @BindingAnnotation.", annotationType.getName()); } static enum NullAnnotationStrategy implements AnnotationStrategy { INSTANCE; public boolean hasAttributes() { return false; } public AnnotationStrategy withoutAttributes() { throw new UnsupportedOperationException("Key already has no attributes."); } public Annotation getAnnotation() { return null; } public Class 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"); } public boolean hasAttributes() { return true; } public AnnotationStrategy withoutAttributes() { return new AnnotationTypeStrategy(getAnnotationType(), annotation); } public Annotation getAnnotation() { return annotation; } 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; } public boolean hasAttributes() { return false; } public AnnotationStrategy withoutAttributes() { throw new UnsupportedOperationException("Key already has no attributes."); } public Annotation getAnnotation() { return annotation; } 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(); } } }