com.google.inject.Key Maven / Gradle / Ivy
/*
* 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.errorprone.annotations.CheckReturnValue;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.MoreTypes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
/**
* 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.
*
* 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 [email protected] (Bob Lee)
*/
@CheckReturnValue
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 extends Annotation> 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();
}
/** 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. Will be {@code null} if this key lacks an annotation. */
public final Class extends Annotation> getAnnotationType() {
return annotationStrategy.getAnnotationType();
}
/**
* Gets the annotation instance if available. Will be {@code null} if this key lacks an annotation
* or the key was constructed with a {@code Class}.
*
* Warning: 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();
}
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> 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 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 extends Annotation> 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 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 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 extends Annotation> 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 a new key of the same type with the specified annotation.
*
* 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 withAnnotation(Class extends Annotation> annotationType) {
return new Key(typeLiteral, strategyFor(annotationType));
}
/**
* Returns a new key of the same type with the specified annotation.
*
* 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 withAnnotation(Annotation annotation) {
return new Key(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.
*
* @since 3.0
*/
public Key withoutAttributes() {
return new Key(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 Annotations.annotationInstanceClassString(annotationType, /* includePackage= */ true);
}
}
}