| /* | |
| * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.reflect.annotation; | |
| import java.lang.annotation.*; | |
| import java.lang.reflect.*; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.access.JavaLangAccess; | |
| import jdk.internal.reflect.ReflectionFactory; | |
| public final class AnnotationSupport { | |
| private static final JavaLangAccess LANG_ACCESS = SharedSecrets.getJavaLangAccess(); | |
| /** | |
| * Finds and returns all annotations in {@code annotations} matching | |
| * the given {@code annoClass}. | |
| * | |
| * Apart from annotations directly present in {@code annotations} this | |
| * method searches for annotations inside containers i.e. indirectly | |
| * present annotations. | |
| * | |
| * The order of the elements in the array returned depends on the iteration | |
| * order of the provided map. Specifically, the directly present annotations | |
| * come before the indirectly present annotations if and only if the | |
| * directly present annotations come before the indirectly present | |
| * annotations in the map. | |
| * | |
| * @param annotations the {@code Map} in which to search for annotations | |
| * @param annoClass the type of annotation to search for | |
| * | |
| * @return an array of instances of {@code annoClass} or an empty | |
| * array if none were found | |
| */ | |
| public static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent( | |
| Map<Class<? extends Annotation>, Annotation> annotations, | |
| Class<A> annoClass) { | |
| List<A> result = new ArrayList<>(); | |
| @SuppressWarnings("unchecked") | |
| A direct = (A) annotations.get(annoClass); | |
| if (direct != null) | |
| result.add(direct); | |
| A[] indirect = getIndirectlyPresent(annotations, annoClass); | |
| if (indirect != null && indirect.length != 0) { | |
| boolean indirectFirst = direct == null || | |
| containerBeforeContainee(annotations, annoClass); | |
| result.addAll((indirectFirst ? 0 : 1), Arrays.asList(indirect)); | |
| } | |
| @SuppressWarnings("unchecked") | |
| A[] arr = (A[]) Array.newInstance(annoClass, result.size()); | |
| return result.toArray(arr); | |
| } | |
| /** | |
| * Finds and returns all annotations matching the given {@code annoClass} | |
| * indirectly present in {@code annotations}. | |
| * | |
| * @param annotations annotations to search indexed by their types | |
| * @param annoClass the type of annotation to search for | |
| * | |
| * @return an array of instances of {@code annoClass} or an empty array if no | |
| * indirectly present annotations were found | |
| */ | |
| private static <A extends Annotation> A[] getIndirectlyPresent( | |
| Map<Class<? extends Annotation>, Annotation> annotations, | |
| Class<A> annoClass) { | |
| Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class); | |
| if (repeatable == null) | |
| return null; // Not repeatable -> no indirectly present annotations | |
| Class<? extends Annotation> containerClass = repeatable.value(); | |
| Annotation container = annotations.get(containerClass); | |
| if (container == null) | |
| return null; | |
| // Unpack container | |
| A[] valueArray = getValueArray(container); | |
| checkTypes(valueArray, container, annoClass); | |
| return valueArray; | |
| } | |
| /** | |
| * Figures out if container class comes before containee class among the | |
| * keys of the given map. | |
| * | |
| * @return true if container class is found before containee class when | |
| * iterating over annotations.keySet(). | |
| */ | |
| private static <A extends Annotation> boolean containerBeforeContainee( | |
| Map<Class<? extends Annotation>, Annotation> annotations, | |
| Class<A> annoClass) { | |
| Class<? extends Annotation> containerClass = | |
| annoClass.getDeclaredAnnotation(Repeatable.class).value(); | |
| for (Class<? extends Annotation> c : annotations.keySet()) { | |
| if (c == containerClass) return true; | |
| if (c == annoClass) return false; | |
| } | |
| // Neither containee nor container present | |
| return false; | |
| } | |
| /** | |
| * Finds and returns all associated annotations matching the given class. | |
| * | |
| * The order of the elements in the array returned depends on the iteration | |
| * order of the provided maps. Specifically, the directly present annotations | |
| * come before the indirectly present annotations if and only if the | |
| * directly present annotations come before the indirectly present | |
| * annotations in the relevant map. | |
| * | |
| * @param declaredAnnotations the declared annotations indexed by their types | |
| * @param decl the class declaration on which to search for annotations | |
| * @param annoClass the type of annotation to search for | |
| * | |
| * @return an array of instances of {@code annoClass} or an empty array if none were found. | |
| */ | |
| public static <A extends Annotation> A[] getAssociatedAnnotations( | |
| Map<Class<? extends Annotation>, Annotation> declaredAnnotations, | |
| Class<?> decl, | |
| Class<A> annoClass) { | |
| Objects.requireNonNull(decl); | |
| // Search declared | |
| A[] result = getDirectlyAndIndirectlyPresent(declaredAnnotations, annoClass); | |
| // Search inherited | |
| if(AnnotationType.getInstance(annoClass).isInherited()) { | |
| Class<?> superDecl = decl.getSuperclass(); | |
| while (result.length == 0 && superDecl != null) { | |
| result = getDirectlyAndIndirectlyPresent(LANG_ACCESS.getDeclaredAnnotationMap(superDecl), annoClass); | |
| superDecl = superDecl.getSuperclass(); | |
| } | |
| } | |
| return result; | |
| } | |
| /* Reflectively invoke the values-method of the given annotation | |
| * (container), cast it to an array of annotations and return the result. | |
| */ | |
| private static <A extends Annotation> A[] getValueArray(Annotation container) { | |
| try { | |
| // According to JLS the container must have an array-valued value | |
| // method. Get the AnnotationType, get the "value" method and invoke | |
| // it to get the content. | |
| Class<? extends Annotation> containerClass = container.annotationType(); | |
| AnnotationType annoType = AnnotationType.getInstance(containerClass); | |
| if (annoType == null) | |
| throw invalidContainerException(container, null); | |
| Method m = annoType.members().get("value"); | |
| if (m == null) | |
| throw invalidContainerException(container, null); | |
| if (Proxy.isProxyClass(container.getClass())) { | |
| // Invoke by invocation handler | |
| InvocationHandler handler = Proxy.getInvocationHandler(container); | |
| try { | |
| // This will erase to (Annotation[]) but we do a runtime cast on the | |
| // return-value in the method that call this method. | |
| @SuppressWarnings("unchecked") | |
| A[] values = (A[]) handler.invoke(container, m, null); | |
| return values; | |
| } catch (Throwable t) { // from InvocationHandler::invoke | |
| throw invalidContainerException(container, t); | |
| } | |
| } else { | |
| // In theory there might be instances of Annotations that are not | |
| // implemented using Proxies. Try to invoke the "value" element with | |
| // reflection. | |
| // Declaring class should be an annotation type | |
| Class<?> iface = m.getDeclaringClass(); | |
| if (!iface.isAnnotation()) | |
| throw new UnsupportedOperationException("Unsupported container annotation type."); | |
| // Method must be public | |
| if (!Modifier.isPublic(m.getModifiers())) | |
| throw new UnsupportedOperationException("Unsupported value member."); | |
| // Interface might not be public though | |
| final Method toInvoke; | |
| if (!Modifier.isPublic(iface.getModifiers())) { | |
| toInvoke = ReflectionFactory.getReflectionFactory().leafCopyMethod(m); | |
| toInvoke.setAccessible(true); | |
| } else { | |
| toInvoke = m; | |
| } | |
| // This will erase to (Annotation[]) but we do a runtime cast on the | |
| // return-value in the method that call this method. | |
| @SuppressWarnings("unchecked") | |
| A[] values = (A[]) toInvoke.invoke(container); | |
| return values; | |
| } | |
| } catch (IllegalAccessException | // couldn't loosen security | |
| IllegalArgumentException | // parameters doesn't match | |
| InvocationTargetException | // the value method threw an exception | |
| ClassCastException e) { | |
| throw invalidContainerException(container, e); | |
| } | |
| } | |
| private static AnnotationFormatError invalidContainerException(Annotation anno, | |
| Throwable cause) { | |
| return new AnnotationFormatError( | |
| anno + " is an invalid container for repeating annotations", | |
| cause); | |
| } | |
| /* Sanity check type of all the annotation instances of type {@code annoClass} | |
| * from {@code container}. | |
| */ | |
| private static <A extends Annotation> void checkTypes(A[] annotations, | |
| Annotation container, | |
| Class<A> annoClass) { | |
| for (A a : annotations) { | |
| if (!annoClass.isInstance(a)) { | |
| throw new AnnotationFormatError( | |
| String.format("%s is an invalid container for " + | |
| "repeating annotations of type: %s", | |
| container, annoClass)); | |
| } | |
| } | |
| } | |
| /** | |
| * Gets an unmodifiable view of {@code a}'s elements. | |
| * | |
| * @return a map from element names to element values | |
| */ | |
| public static Map<String, Object> memberValues(Annotation a) { | |
| return ((AnnotationInvocationHandler) Proxy.getInvocationHandler(a)).memberValues(); | |
| } | |
| } |