/* * Copyright (c) 2012, 2018, 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.security.AccessController; import java.security.PrivilegedAction; 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[] getDirectlyAndIndirectlyPresent( Map, Annotation> annotations, Class annoClass) { List 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[] getIndirectlyPresent( Map, Annotation> annotations, Class annoClass) { Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class); if (repeatable == null) return null; // Not repeatable -> no indirectly present annotations Class 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 boolean containerBeforeContainee( Map, Annotation> annotations, Class annoClass) { Class containerClass = annoClass.getDeclaredAnnotation(Repeatable.class).value(); for (Class 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[] getAssociatedAnnotations( Map, Annotation> declaredAnnotations, Class decl, Class 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[] 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 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())) { if (System.getSecurityManager() != null) { toInvoke = AccessController.doPrivileged(new PrivilegedAction() { @Override public Method run() { Method res = ReflectionFactory.getReflectionFactory().leafCopyMethod(m); res.setAccessible(true); return res; } }); } else { 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 void checkTypes(A[] annotations, Annotation container, Class 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)); } } } }