1 /*
2  * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.reflect.annotation;
27 
28 import java.lang.annotation.*;
29 import java.lang.reflect.*;
30 import java.security.AccessController;
31 import java.security.PrivilegedAction;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 
38 import jdk.internal.access.SharedSecrets;
39 import jdk.internal.access.JavaLangAccess;
40 import jdk.internal.reflect.ReflectionFactory;
41 
42 public final class AnnotationSupport {
43     private static final JavaLangAccess LANG_ACCESS = SharedSecrets.getJavaLangAccess();
44 
45     /**
46      * Finds and returns all annotations in {@code annotations} matching
47      * the given {@code annoClass}.
48      *
49      * Apart from annotations directly present in {@code annotations} this
50      * method searches for annotations inside containers i.e. indirectly
51      * present annotations.
52      *
53      * The order of the elements in the array returned depends on the iteration
54      * order of the provided map. Specifically, the directly present annotations
55      * come before the indirectly present annotations if and only if the
56      * directly present annotations come before the indirectly present
57      * annotations in the map.
58      *
59      * @param annotations the {@code Map} in which to search for annotations
60      * @param annoClass the type of annotation to search for
61      *
62      * @return an array of instances of {@code annoClass} or an empty
63      *         array if none were found
64      */
getDirectlyAndIndirectlyPresent( Map<Class<? extends Annotation>, Annotation> annotations, Class<A> annoClass)65     public static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent(
66             Map<Class<? extends Annotation>, Annotation> annotations,
67             Class<A> annoClass) {
68         List<A> result = new ArrayList<>();
69 
70         @SuppressWarnings("unchecked")
71         A direct = (A) annotations.get(annoClass);
72         if (direct != null)
73             result.add(direct);
74 
75         A[] indirect = getIndirectlyPresent(annotations, annoClass);
76         if (indirect != null && indirect.length != 0) {
77             boolean indirectFirst = direct == null ||
78                                     containerBeforeContainee(annotations, annoClass);
79 
80             result.addAll((indirectFirst ? 0 : 1), Arrays.asList(indirect));
81         }
82 
83         @SuppressWarnings("unchecked")
84         A[] arr = (A[]) Array.newInstance(annoClass, result.size());
85         return result.toArray(arr);
86     }
87 
88     /**
89      * Finds and returns all annotations matching the given {@code annoClass}
90      * indirectly present in {@code annotations}.
91      *
92      * @param annotations annotations to search indexed by their types
93      * @param annoClass the type of annotation to search for
94      *
95      * @return an array of instances of {@code annoClass} or an empty array if no
96      *         indirectly present annotations were found
97      */
getIndirectlyPresent( Map<Class<? extends Annotation>, Annotation> annotations, Class<A> annoClass)98     private static <A extends Annotation> A[] getIndirectlyPresent(
99             Map<Class<? extends Annotation>, Annotation> annotations,
100             Class<A> annoClass) {
101 
102         Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class);
103         if (repeatable == null)
104             return null;  // Not repeatable -> no indirectly present annotations
105 
106         Class<? extends Annotation> containerClass = repeatable.value();
107 
108         Annotation container = annotations.get(containerClass);
109         if (container == null)
110             return null;
111 
112         // Unpack container
113         A[] valueArray = getValueArray(container);
114         checkTypes(valueArray, container, annoClass);
115 
116         return valueArray;
117     }
118 
119 
120     /**
121      * Figures out if container class comes before containee class among the
122      * keys of the given map.
123      *
124      * @return true if container class is found before containee class when
125      *         iterating over annotations.keySet().
126      */
containerBeforeContainee( Map<Class<? extends Annotation>, Annotation> annotations, Class<A> annoClass)127     private static <A extends Annotation> boolean containerBeforeContainee(
128             Map<Class<? extends Annotation>, Annotation> annotations,
129             Class<A> annoClass) {
130 
131         Class<? extends Annotation> containerClass =
132                 annoClass.getDeclaredAnnotation(Repeatable.class).value();
133 
134         for (Class<? extends Annotation> c : annotations.keySet()) {
135             if (c == containerClass) return true;
136             if (c == annoClass) return false;
137         }
138 
139         // Neither containee nor container present
140         return false;
141     }
142 
143 
144     /**
145      * Finds and returns all associated annotations matching the given class.
146      *
147      * The order of the elements in the array returned depends on the iteration
148      * order of the provided maps. Specifically, the directly present annotations
149      * come before the indirectly present annotations if and only if the
150      * directly present annotations come before the indirectly present
151      * annotations in the relevant map.
152      *
153      * @param declaredAnnotations the declared annotations indexed by their types
154      * @param decl the class declaration on which to search for annotations
155      * @param annoClass the type of annotation to search for
156      *
157      * @return an array of instances of {@code annoClass} or an empty array if none were found.
158      */
getAssociatedAnnotations( Map<Class<? extends Annotation>, Annotation> declaredAnnotations, Class<?> decl, Class<A> annoClass)159     public static <A extends Annotation> A[] getAssociatedAnnotations(
160             Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
161             Class<?> decl,
162             Class<A> annoClass) {
163         Objects.requireNonNull(decl);
164 
165         // Search declared
166         A[] result = getDirectlyAndIndirectlyPresent(declaredAnnotations, annoClass);
167 
168         // Search inherited
169         if(AnnotationType.getInstance(annoClass).isInherited()) {
170             Class<?> superDecl = decl.getSuperclass();
171             while (result.length == 0 && superDecl != null) {
172                 result = getDirectlyAndIndirectlyPresent(LANG_ACCESS.getDeclaredAnnotationMap(superDecl), annoClass);
173                 superDecl = superDecl.getSuperclass();
174             }
175         }
176 
177         return result;
178     }
179 
180 
181     /* Reflectively invoke the values-method of the given annotation
182      * (container), cast it to an array of annotations and return the result.
183      */
getValueArray(Annotation container)184     private static <A extends Annotation> A[] getValueArray(Annotation container) {
185         try {
186             // According to JLS the container must have an array-valued value
187             // method. Get the AnnotationType, get the "value" method and invoke
188             // it to get the content.
189 
190             Class<? extends Annotation> containerClass = container.annotationType();
191             AnnotationType annoType = AnnotationType.getInstance(containerClass);
192             if (annoType == null)
193                 throw invalidContainerException(container, null);
194             Method m = annoType.members().get("value");
195             if (m == null)
196                 throw invalidContainerException(container, null);
197 
198             if (Proxy.isProxyClass(container.getClass())) {
199                 // Invoke by invocation handler
200                 InvocationHandler handler = Proxy.getInvocationHandler(container);
201 
202                 try {
203                     // This will erase to (Annotation[]) but we do a runtime cast on the
204                     // return-value in the method that call this method.
205                     @SuppressWarnings("unchecked")
206                     A[] values = (A[]) handler.invoke(container, m, null);
207                     return values;
208                 } catch (Throwable t) { // from InvocationHandler::invoke
209                     throw invalidContainerException(container, t);
210                 }
211             } else {
212                 // In theory there might be instances of Annotations that are not
213                 // implemented using Proxies. Try to invoke the "value" element with
214                 // reflection.
215 
216                 // Declaring class should be an annotation type
217                 Class<?> iface = m.getDeclaringClass();
218                 if (!iface.isAnnotation())
219                     throw new UnsupportedOperationException("Unsupported container annotation type.");
220                 // Method must be public
221                 if (!Modifier.isPublic(m.getModifiers()))
222                     throw new UnsupportedOperationException("Unsupported value member.");
223 
224                 // Interface might not be public though
225                 final Method toInvoke;
226                 if (!Modifier.isPublic(iface.getModifiers())) {
227                     if (System.getSecurityManager() != null) {
228                         toInvoke = AccessController.doPrivileged(new PrivilegedAction<Method>() {
229                             @Override
230                             public Method run() {
231                                 Method res = ReflectionFactory.getReflectionFactory().leafCopyMethod(m);
232                                 res.setAccessible(true);
233                                 return res;
234                             }
235                         });
236                     } else {
237                         toInvoke = ReflectionFactory.getReflectionFactory().leafCopyMethod(m);
238                         toInvoke.setAccessible(true);
239                     }
240                 } else {
241                     toInvoke = m;
242                 }
243 
244                 // This will erase to (Annotation[]) but we do a runtime cast on the
245                 // return-value in the method that call this method.
246                 @SuppressWarnings("unchecked")
247                 A[] values = (A[]) toInvoke.invoke(container);
248 
249                 return values;
250             }
251         } catch (IllegalAccessException    | // couldn't loosen security
252                  IllegalArgumentException  | // parameters doesn't match
253                  InvocationTargetException | // the value method threw an exception
254                  ClassCastException e) {
255             throw invalidContainerException(container, e);
256         }
257     }
258 
259 
invalidContainerException(Annotation anno, Throwable cause)260     private static AnnotationFormatError invalidContainerException(Annotation anno,
261                                                                    Throwable cause) {
262         return new AnnotationFormatError(
263                 anno + " is an invalid container for repeating annotations",
264                 cause);
265     }
266 
267 
268     /* Sanity check type of all the annotation instances of type {@code annoClass}
269      * from {@code container}.
270      */
checkTypes(A[] annotations, Annotation container, Class<A> annoClass)271     private static <A extends Annotation> void checkTypes(A[] annotations,
272                                                           Annotation container,
273                                                           Class<A> annoClass) {
274         for (A a : annotations) {
275             if (!annoClass.isInstance(a)) {
276                 throw new AnnotationFormatError(
277                         String.format("%s is an invalid container for " +
278                                       "repeating annotations of type: %s",
279                                       container, annoClass));
280             }
281         }
282     }
283 }
284