mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
283 lines
12 KiB
Java
283 lines
12 KiB
Java
/*
|
|
* Copyright (c) 2012, 2013, 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.misc.SharedSecrets;
|
|
import jdk.internal.misc.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())) {
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|