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.util.ArrayList;
  31 import java.util.Arrays;
  32 import java.util.Collections;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Objects;
  36 
  37 import sun.misc.JavaLangAccess;
  38 
  39 public final class AnnotationSupport {
  40     private static final JavaLangAccess LANG_ACCESS = sun.misc.SharedSecrets.getJavaLangAccess();
  41 
  42     /**
  43      * Finds and returns all annotations in {@code annotations} matching
  44      * the given {@code annoClass}.
  45      *
  46      * Apart from annotations directly present in {@code annotations} this
  47      * method searches for annotations inside containers i.e. indirectly
  48      * present annotations.
  49      *
  50      * The order of the elements in the array returned depends on the iteration
  51      * order of the provided map. Specifically, the directly present annotations
  52      * come before the indirectly present annotations if and only if the
  53      * directly present annotations come before the indirectly present
  54      * annotations in the map.
  55      *
  56      * @param annotations the {@code Map} in which to search for annotations
  57      * @param annoClass the type of annotation to search for
  58      *
  59      * @return an array of instances of {@code annoClass} or an empty
  60      *         array if none were found
  61      */
  62     public static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent(
  63             Map<Class<? extends Annotation>, Annotation> annotations,
  64             Class<A> annoClass) {
  65         List<A> result = new ArrayList<A>();
  66 
  67         @SuppressWarnings("unchecked")
  68         A direct = (A) annotations.get(annoClass);
  69         if (direct != null)
  70             result.add(direct);
  71 
  72         A[] indirect = getIndirectlyPresent(annotations, annoClass);
  73         if (indirect != null && indirect.length != 0) {
  74             boolean indirectFirst = direct == null ||
  75                                     containerBeforeContainee(annotations, annoClass);
  76 
  77             result.addAll((indirectFirst ? 0 : 1), Arrays.asList(indirect));
  78         }
  79 
  80         @SuppressWarnings("unchecked")
  81         A[] arr = (A[]) Array.newInstance(annoClass, result.size());
  82         return result.toArray(arr);
  83     }
  84 
  85     /**
  86      * Finds and returns all annotations matching the given {@code annoClass}
  87      * indirectly present in {@code annotations}.
  88      *
  89      * @param annotations annotations to search indexed by their types
  90      * @param annoClass the type of annotation to search for
  91      *
  92      * @return an array of instances of {@code annoClass} or an empty array if no
  93      *         indirectly present annotations were found
  94      */
  95     private static <A extends Annotation> A[] getIndirectlyPresent(
  96             Map<Class<? extends Annotation>, Annotation> annotations,
  97             Class<A> annoClass) {
  98 
  99         Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class);
 100         if (repeatable == null)
 101             return null;  // Not repeatable -> no indirectly present annotations
 102 
 103         Class<? extends Annotation> containerClass = repeatable.value();
 104 
 105         Annotation container = annotations.get(containerClass);
 106         if (container == null)
 107             return null;
 108 
 109         // Unpack container
 110         A[] valueArray = getValueArray(container);
 111         checkTypes(valueArray, container, annoClass);
 112 
 113         return valueArray;
 114     }
 115 
 116 
 117     /**
 118      * Figures out if conatiner class comes before containee class among the
 119      * keys of the given map.
 120      *
 121      * @return true if container class is found before containee class when
 122      *         iterating over annotations.keySet().
 123      */
 124     private static <A extends Annotation> boolean containerBeforeContainee(
 125             Map<Class<? extends Annotation>, Annotation> annotations,
 126             Class<A> annoClass) {
 127 
 128         Class<? extends Annotation> containerClass =
 129                 annoClass.getDeclaredAnnotation(Repeatable.class).value();
 130 
 131         for (Class<? extends Annotation> c : annotations.keySet()) {
 132             if (c == containerClass) return true;
 133             if (c == annoClass) return false;
 134         }
 135 
 136         // Neither containee nor container present
 137         return false;
 138     }
 139 
 140 
 141     /**
 142      * Finds and returns all associated annotations matching the given class.
 143      *
 144      * The order of the elements in the array returned depends on the iteration
 145      * order of the provided maps. Specifically, the directly present annotations
 146      * come before the indirectly present annotations if and only if the
 147      * directly present annotations come before the indirectly present
 148      * annotations in the relevant map.
 149      *
 150      * @param declaredAnnotations the declared annotations indexed by their types
 151      * @param decl the class declaration on which to search for annotations
 152      * @param annoClass the type of annotation to search for
 153      *
 154      * @return an array of instances of {@code annoClass} or an empty array if none were found.
 155      */
 156     public static <A extends Annotation> A[] getAssociatedAnnotations(
 157             Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
 158             Class<?> decl,
 159             Class<A> annoClass) {
 160         Objects.requireNonNull(decl);
 161 
 162         // Search declared
 163         A[] result = getDirectlyAndIndirectlyPresent(declaredAnnotations, annoClass);
 164 
 165         // Search inherited
 166         if(AnnotationType.getInstance(annoClass).isInherited()) {
 167             Class<?> superDecl = decl.getSuperclass();
 168             while (result.length == 0 && superDecl != null) {
 169                 result = getDirectlyAndIndirectlyPresent(LANG_ACCESS.getDeclaredAnnotationMap(superDecl), annoClass);
 170                 superDecl = superDecl.getSuperclass();
 171             }
 172         }
 173 
 174         return result;
 175     }
 176 
 177 
 178     /* Reflectively invoke the values-method of the given annotation
 179      * (container), cast it to an array of annotations and return the result.
 180      */
 181     private static <A extends Annotation> A[] getValueArray(Annotation container) {
 182         try {
 183             // According to JLS the container must have an array-valued value
 184             // method. Get the AnnotationType, get the "value" method and invoke
 185             // it to get the content.
 186 
 187             Class<? extends Annotation> containerClass = container.annotationType();
 188             AnnotationType annoType = AnnotationType.getInstance(containerClass);
 189             if (annoType == null)
 190                 throw invalidContainerException(container, null);
 191 
 192             Method m = annoType.members().get("value");
 193             if (m == null)
 194                 throw invalidContainerException(container, null);
 195 
 196             m.setAccessible(true);
 197 
 198             // This will erase to (Annotation[]) but we do a runtime cast on the
 199             // return-value in the method that call this method.
 200             @SuppressWarnings("unchecked")
 201             A[] values = (A[]) m.invoke(container);
 202 
 203             return values;
 204 
 205         } catch (IllegalAccessException    | // couldn't loosen security
 206                  IllegalArgumentException  | // parameters doesn't match
 207                  InvocationTargetException | // the value method threw an exception
 208                  ClassCastException e) {
 209 
 210             throw invalidContainerException(container, e);
 211 
 212         }
 213     }
 214 
 215 
 216     private static AnnotationFormatError invalidContainerException(Annotation anno,
 217                                                                    Throwable cause) {
 218         return new AnnotationFormatError(
 219                 anno + " is an invalid container for repeating annotations",
 220                 cause);
 221     }
 222 
 223 
 224     /* Sanity check type of all the annotation instances of type {@code annoClass}
 225      * from {@code container}.
 226      */
 227     private static <A extends Annotation> void checkTypes(A[] annotations,
 228                                                           Annotation container,
 229                                                           Class<A> annoClass) {
 230         for (A a : annotations) {
 231             if (!annoClass.isInstance(a)) {
 232                 throw new AnnotationFormatError(
 233                         String.format("%s is an invalid container for " +
 234                                       "repeating annotations of type: %s",
 235                                       container, annoClass));
 236             }
 237         }
 238     }
 239 }