blob: 01225daf0b96f25e88a937135c8a4640a2fceb0d [file] [log] [blame]
/**
* 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.inject.util.Objects.nonNull;
import com.google.inject.util.StackTraceElements;
import com.google.inject.util.ToStringBuilder;
import com.google.inject.util.Annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
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}.
*
* @author crazybob@google.com (Bob Lee)
*/
public abstract class Key<T> {
final AnnotationStrategy annotationStrategy;
final TypeLiteral<T> typeLiteral;
final int hashCode;
/**
* 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
= (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
= (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 = NULL_STRATEGY;
this.typeLiteral
= (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 = (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 = typeLiteral;
this.hashCode = computeHashCode();
}
private int computeHashCode() {
return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode();
}
/**
* Gets the key type.
*/
public TypeLiteral<T> getTypeLiteral() {
return typeLiteral;
}
/**
* Gets the annotation type.
*/
public Class<? extends Annotation> getAnnotationType() {
return annotationStrategy.getAnnotationType();
}
/**
* Gets the annotation.
*/
public 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();
}
public int hashCode() {
return this.hashCode;
}
Class<? super T> getRawType() {
return typeLiteral.getRawType();
}
public 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);
}
public String toString() {
return new ToStringBuilder(Key.class)
.add("type", typeLiteral)
.add("annotation", annotationStrategy)
.toString();
}
/**
* Gets a key for an injection type and an annotation strategy.
*/
static <T> Key<T> get(Class<T> type,
AnnotationStrategy annotationStrategy) {
return new SimpleKey<T>(type, annotationStrategy);
}
/**
* Gets a key for an injection type.
*/
public static <T> Key<T> get(Class<T> type) {
return new SimpleKey<T>(type, NULL_STRATEGY);
}
/**
* 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 SimpleKey<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 SimpleKey<T>(type, strategyFor(annotation));
}
/**
* Gets a key for an injection type.
*/
public static Key<?> get(Type type) {
return new SimpleKey<Object>(type, NULL_STRATEGY);
}
/**
* Gets a key for an injection type and an annotation type.
*/
public static Key<?> get(Type type,
Class<? extends Annotation> annotationType) {
return new SimpleKey<Object>(type, strategyFor(annotationType));
}
/**
* Gets a key for an injection type and an annotation.
*/
public static Key<?> get(Type type, Annotation annotation) {
return new SimpleKey<Object>(type, strategyFor(annotation));
}
/**
* Gets a key for an injection type.
*/
public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
return new SimpleKey<T>(typeLiteral, NULL_STRATEGY);
}
/**
* 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 SimpleKey<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 SimpleKey<T>(typeLiteral, strategyFor(annotation));
}
/**
* Gets a key for the given type, member and annotations.
*/
static Key<?> get(Type type, Member member, Annotation[] annotations,
ErrorHandler errorHandler) {
Annotation found = null;
for (Annotation annotation : annotations) {
if (annotation.annotationType().getAnnotation(BindingAnnotation.class) != null) {
if (found == null) {
found = annotation;
} else {
errorHandler.handle(StackTraceElements.forMember(member),
ErrorMessages.DUPLICATE_ANNOTATIONS, found, annotation);
}
}
}
Key<?> key = found == null ? Key.get(type) : Key.get(type, found);
return key;
}
/**
* Returns a new key of the specified type with the same annotation as this
* key.
*/
<T> Key<T> ofType(Class<T> type) {
return new SimpleKey<T>(type, annotationStrategy);
}
/**
* Returns a new key of the specified type with the same annotation as this
* key.
*/
Key<?> ofType(Type type) {
return new SimpleKey<Object>(type, annotationStrategy);
}
/**
* Returns true if this key has annotation attributes.
* @return
*/
boolean hasAttributes() {
return annotationStrategy.hasAttributes();
}
/**
* Returns this key without annotation attributes, i.e. with only the
* annotation type.
*/
Key<T> withoutAttributes() {
return new SimpleKey<T>(typeLiteral, annotationStrategy.withoutAttributes());
}
private static class SimpleKey<T> extends Key<T> {
private SimpleKey(Type type, AnnotationStrategy annotationStrategy) {
super(type, annotationStrategy);
}
private SimpleKey(TypeLiteral<T> typeLiteral,
AnnotationStrategy annotationStrategy) {
super(typeLiteral, annotationStrategy);
}
}
interface AnnotationStrategy {
Annotation getAnnotation();
Class<? extends Annotation> getAnnotationType();
boolean hasAttributes();
AnnotationStrategy withoutAttributes();
}
static final AnnotationStrategy NULL_STRATEGY = new AnnotationStrategy() {
public boolean hasAttributes() {
return false;
}
public AnnotationStrategy withoutAttributes() {
throw new UnsupportedOperationException("Key already has no attributes.");
}
public Annotation getAnnotation() {
return null;
}
public Class<? extends Annotation> getAnnotationType() {
return null;
}
public boolean equals(Object o) {
return o == NULL_STRATEGY;
}
public int hashCode() {
return 0;
}
public String toString() {
return "[none]";
}
};
/**
* Returns {@code true} if the given annotation type has no attributes.
*/
static boolean isMarker(Class<? extends Annotation> annotationType) {
return annotationType.getDeclaredMethods().length == 0;
}
/**
* Gets the strategy for an annotation.
*/
static AnnotationStrategy strategyFor(Annotation annotation) {
nonNull(annotation, "annotation");
Class<? extends Annotation> annotationType = annotation.annotationType();
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
return new AnnotationInstanceStrategy(annotation);
}
/**
* Gets the strategy for an annotation type.
*/
static AnnotationStrategy strategyFor(
Class<? extends Annotation> annotationType) {
nonNull(annotationType, "annotation type");
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
return new AnnotationTypeStrategy(annotationType, null);
}
private static void ensureRetainedAtRuntime(
Class<? extends Annotation> annotationType) {
if (!Annotations.isRetainedAtRuntime(annotationType)) {
throw new IllegalArgumentException(annotationType.getName()
+ " is not retained at runtime."
+ " Please annotate it with @Retention(RUNTIME).");
}
}
private static void ensureIsBindingAnnotation(
Class<? extends Annotation> annotationType) {
if (!isBindingAnnotation(annotationType)) {
throw new IllegalArgumentException(annotationType.getName()
+ " is not a binding annotation."
+ " Please annotate it with @BindingAnnotation.");
}
}
// this class not test-covered
static class AnnotationInstanceStrategy implements AnnotationStrategy {
final Annotation annotation;
AnnotationInstanceStrategy(Annotation annotation) {
this.annotation = nonNull(annotation, "annotation");
}
public boolean hasAttributes() {
return true;
}
public AnnotationStrategy withoutAttributes() {
return new AnnotationTypeStrategy(getAnnotationType(), annotation);
}
public Annotation getAnnotation() {
return annotation;
}
public Class<? extends Annotation> getAnnotationType() {
return annotation.annotationType();
}
public boolean equals(Object o) {
if (!(o instanceof AnnotationInstanceStrategy)) {
return false;
}
AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o;
return annotation.equals(other.annotation);
}
public int hashCode() {
return annotation.hashCode();
}
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 = nonNull(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<? extends Annotation> getAnnotationType() {
return annotationType;
}
public boolean equals(Object o) {
if (!(o instanceof AnnotationTypeStrategy)) {
return false;
}
AnnotationTypeStrategy other = (AnnotationTypeStrategy) o;
return annotationType.equals(other.annotationType);
}
public int hashCode() {
return annotationType.hashCode();
}
public String toString() {
return "@" + annotationType.getName();
}
}
static boolean isBindingAnnotation(Annotation annotation) {
return isBindingAnnotation(annotation.annotationType());
}
static boolean isBindingAnnotation(
Class<? extends Annotation> annotationType) {
return annotationType.isAnnotationPresent(BindingAnnotation.class);
}
}