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

org.elasticsearch.common.inject.Key Maven / Gradle / Ivy

There is a newer version: 8.13.2
Show newest version
/*
 * 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 org.elasticsearch.common.inject;

import org.elasticsearch.common.inject.internal.Annotations;
import org.elasticsearch.common.inject.internal.MoreTypes;
import org.elasticsearch.common.inject.internal.ToStringBuilder;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Objects;

/**
 * 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 [email protected] (Bob Lee) */ public class Key { private final AnnotationStrategy annotationStrategy; private final TypeLiteral typeLiteral; private final int hashCode; /** * 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 = (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 = (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 = (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.makeKeySafe((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.makeKeySafe(typeLiteral); this.hashCode = computeHashCode(); } 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() { return new ToStringBuilder(Key.class) .add("type", typeLiteral) .add("annotation", annotationStrategy) .toString(); } /** * 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. */ Key ofType(Class type) { return new Key<>(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. */ Key ofType(Type type) { return new Key(type, annotationStrategy); } /** * Returns a new key of the specified type with the same annotation as this * key. */ Key ofType(TypeLiteral type) { return new Key<>(type, annotationStrategy); } /** * Returns true if this key has annotation attributes. */ boolean hasAttributes() { return annotationStrategy.hasAttributes(); } /** * Returns this key without annotation attributes, i.e. with only the * annotation type. */ Key withoutAttributes() { return new Key<>(typeLiteral, annotationStrategy.withoutAttributes()); } interface AnnotationStrategy { Annotation getAnnotation(); Class getAnnotationType(); boolean hasAttributes(); AnnotationStrategy withoutAttributes(); } /** * Returns {@code true} if the given annotation type has no attributes. */ static boolean isMarker(Class annotationType) { return annotationType.getMethods().length == 0; } /** * Gets the strategy for an annotation. */ static AnnotationStrategy strategyFor(Annotation annotation) { Objects.requireNonNull(annotation, "annotation"); Class annotationType = annotation.annotationType(); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); if (annotationType.getMethods().length == 0) { return new AnnotationTypeStrategy(annotationType, annotation); } return new AnnotationInstanceStrategy(annotation); } /** * Gets the strategy for an annotation type. */ static AnnotationStrategy strategyFor(Class annotationType) { Objects.requireNonNull(annotationType, "annotation type"); ensureRetainedAtRuntime(annotationType); ensureIsBindingAnnotation(annotationType); return new AnnotationTypeStrategy(annotationType, null); } private static void ensureRetainedAtRuntime( Class 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 annotationType) { if (!isBindingAnnotation(annotationType)) { throw new IllegalArgumentException( annotationType.getName() + " is not a binding annotation. Please annotate it with @BindingAnnotation." ); } } 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]"; } } // this class not test-covered static class AnnotationInstanceStrategy implements AnnotationStrategy { final Annotation annotation; AnnotationInstanceStrategy(Annotation annotation) { this.annotation = Objects.requireNonNull(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 = Objects.requireNonNull(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(); } } static boolean isBindingAnnotation(Annotation annotation) { return isBindingAnnotation(annotation.annotationType()); } static boolean isBindingAnnotation( Class annotationType) { return annotationType.getAnnotation(BindingAnnotation.class) != null; } }