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