blob: 55b6edea730af9af598deb2e563344cab0c75e34 [file] [log] [blame]
/*
* Copyright (c) 2012, 2021, 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 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.
*/
@SuppressWarnings("removal")
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())) {
if (System.getSecurityManager() != null) {
toInvoke = AccessController.doPrivileged(new PrivilegedAction<Method>() {
@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 <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));
}
}
}
}