/* * 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. * *

This implementation is shared between all the classes implementing {@link AnnotatedElement}, * avoiding code duplication.

* * @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 getDeclaredAnnotation(AnnotatedElement element, Class 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[] getDeclaredAnnotationsByType(AnnotatedElement element, Class 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 unfoldedAnnotations = new ArrayList(); Class 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. * *

* 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. *

*/ private static void insertAnnotationValues(Annotation annotation, Class annotationClass, ArrayList 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 annotationArrayClass = (Class)((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}. * *

* Given: * * * @Repeatable(X.class) * @interface SomeName { <--- = annotationClass * }... * * *

* Returns {@code X.class} * * Otherwise if there was no {@code \@Repeatable} annotation, return {@code null}. *

*/ private static Class getRepeatableAnnotationContainerClassFor(Class annotationClass) { Repeatable repeatableAnnotation = annotationClass.getDeclaredAnnotation(Repeatable.class); return (repeatableAnnotation == null) ? null : repeatableAnnotation.value(); } /** * Default implementation of {@link AnnotatedElement#getAnnotationsByType}. * *

* 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. *

* * @return Associated annotations of type {@code annotationClass} for {@code element}. */ public static T[] getAnnotationsByType(AnnotatedElement element, Class 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"); } }