blob: 2fe2d2b25cf2bb56b390281ec487d6512ca16381 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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 libcore.reflect;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import java.util.ArrayList;
/**
* Implementation of {@link AnnotatedElement}'s 1.8 methods.
*
* <p>This implementation is shared between all the classes implementing {@link AnnotatedElement},
* avoiding code duplication.</p>
*
* @hide
*/
public final class AnnotatedElements {
/**
* Default implementation for {@link AnnotatedElement#getDeclaredAnnotation}.
*
* @return Directly present annotation of type {@code annotationClass} for {@code element},
* or {@code null} if none was found.
*/
public static <T extends Annotation> T getDeclaredAnnotation(AnnotatedElement element,
Class<T> annotationClass) {
if (annotationClass == null) {
throw new NullPointerException("annotationClass");
}
Annotation[] annotations = element.getDeclaredAnnotations();
// Safeguard: getDeclaredAnnotations should never return null.
if (annotations == null) {
return null;
}
// The annotation might be directly present:
// Return the first (and only) annotation whose class matches annotationClass.
for (int i = 0; i < annotations.length; ++i) {
if (annotationClass.isInstance(annotations[i])) {
return (T)annotations[i]; // Safe because of above guard.
}
}
// The annotation was *not* directly present:
// If the array was empty, or we found no matches, return null.
return null;
}
/**
* Default implementation for {@link AnnotatedElement#getDeclaredAnnotationsByType}.
*
* @return Directly/indirectly present list of annotations of type {@code annotationClass} for
* {@code element}, or an empty array if none were found.
*/
public static <T extends Annotation> T[] getDeclaredAnnotationsByType(AnnotatedElement element,
Class<T> annotationClass) {
if (annotationClass == null) {
throw new NullPointerException("annotationClass");
}
Annotation[] annotations = element.getDeclaredAnnotations();
// Store a list of repeatable annotations that have been extracted from their container.
ArrayList<T> unfoldedAnnotations = new ArrayList<T>();
Class<? extends Annotation> repeatableAnnotationClass =
getRepeatableAnnotationContainerClassFor(annotationClass);
for (int i = 0; i < annotations.length; ++i) {
if (annotationClass.isInstance(annotations[i])) {
// Is it directly present?
unfoldedAnnotations.add((T)annotations[i]); // Safe, guarded by above check.
} else if (repeatableAnnotationClass != null &&
repeatableAnnotationClass.isInstance(annotations[i])) {
// Is it repeatably (indirectly) present?
insertAnnotationValues(annotations[i], annotationClass, unfoldedAnnotations);
}
}
return unfoldedAnnotations.toArray((T[])Array.newInstance(annotationClass, 0));
}
/**
* Extracts annotations from a container annotation and inserts them into a list.
*
* <p>
* Given a complex annotation "annotation", it should have a "T[] value()" method on it.
* Call that method and add all of the nested annotations into unfoldedAnnotations list.
* </p>
*/
private static <T extends Annotation> void insertAnnotationValues(Annotation annotation,
Class<T> annotationClass, ArrayList<T> unfoldedAnnotations) {
// annotation is a complex annotation which has elements of instance annotationClass
// (whose static type is T).
//
// @interface SomeName { <--- = annotation.getClass()
// ...
// T[] value(); <--- T.class == annotationClass
// }
//
// Use reflection to access these values.
Class<T[]> annotationArrayClass =
(Class<T[]>)((T[])Array.newInstance(annotationClass, 0)).getClass();
Method valuesMethod;
try {
valuesMethod = annotation.getClass().getDeclaredMethod("value");
// This will always succeed unless the annotation and its repeatable annotation class were
// recompiled separately, then this is a binary incompatibility error.
} catch (NoSuchMethodException e) {
throw new AssertionError("annotation container = " + annotation +
"annotation element class = " + annotationClass + "; missing value() method");
} catch (SecurityException e) {
throw new IncompleteAnnotationException(annotation.getClass(), "value");
}
// Ensure that value() returns a T[]
if (!valuesMethod.getReturnType().isArray()) {
throw new AssertionError("annotation container = " + annotation +
"annotation element class = " + annotationClass + "; value() doesn't return array");
}
// Ensure that the T[] value() is actually the correct type (T==annotationClass).
if (!annotationClass.equals(valuesMethod.getReturnType().getComponentType())) {
throw new AssertionError("annotation container = " + annotation +
"annotation element class = " + annotationClass + "; value() returns incorrect type");
}
// Append those values into the existing list.
T[] nestedAnnotations;
try {
nestedAnnotations = (T[])valuesMethod.invoke(annotation); // Safe because of #getMethod.
} catch (IllegalAccessException|InvocationTargetException e) {
throw new AssertionError(e);
}
for (int i = 0; i < nestedAnnotations.length; ++i) {
unfoldedAnnotations.add(nestedAnnotations[i]);
}
}
/**
* Find the {@code \@Repeatable} container annotation class for an annotation class, or
* {@code null}.
*
* <p>
* Given:
*
* <code>
* @Repeatable(X.class)
* @interface SomeName { <--- = annotationClass
* }...
* </code>
*
* <p>
* Returns {@code X.class}
*
* Otherwise if there was no {@code \@Repeatable} annotation, return {@code null}.
* </p>
*/
private static <T extends Annotation> Class<? extends Annotation>
getRepeatableAnnotationContainerClassFor(Class<T> annotationClass) {
Repeatable repeatableAnnotation = annotationClass.getDeclaredAnnotation(Repeatable.class);
return (repeatableAnnotation == null) ? null : repeatableAnnotation.value();
}
/**
* Default implementation of {@link AnnotatedElement#getAnnotationsByType}.
*
* <p>
* This method does not handle inherited annotations and is
* intended for use for {@code Method}, {@code Field}, {@code Package}.
* The {@link Class#getAnnotationsByType} is implemented explicitly.
* </p>
*
* @return Associated annotations of type {@code annotationClass} for {@code element}.
*/
public static <T extends Annotation> T[] getAnnotationsByType(AnnotatedElement element,
Class<T> annotationClass) {
if (annotationClass == null) {
throw new NullPointerException("annotationClass");
}
// Find any associated annotations [directly or repeatably (indirectly) present on this class].
T[] annotations = element.getDeclaredAnnotationsByType(annotationClass);
if (annotations == null) {
throw new AssertionError("annotations must not be null"); // Internal error.
}
// If nothing was found, we would look for associated annotations recursively up to the root
// class. However this can only happen if AnnotatedElement is a Class, which is handled
// in the Class override of this method.
return annotations;
}
private AnnotatedElements() {
throw new AssertionError("Instances of AnnotatedElements not allowed");
}
}