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 }