1 /*
   2  * Copyright (c) 2012, 2013, 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.misc.SharedSecrets;
  39 import jdk.internal.misc.JavaLangAccess;
  40 import sun.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      */
  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      */
  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      */
 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      */
 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      */
 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 
 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      */
 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 }