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 /* 179 * Reflectively invoke the values-method of the given annotation 180 * (container), cast it to an array of annotations and return the 181 * result. 182 */ 183 public static <A extends Annotation> A[] getValueArray(Annotation container) { 184 try { 185 // According to JLS the container must have an array-valued value 186 // method. Get the AnnotationType, get the "value" method and invoke 187 // it to get the content. 188 189 Class<? extends Annotation> containerClass = container.annotationType(); 190 AnnotationType annoType = AnnotationType.getInstance(containerClass); 191 if (annoType == null) 192 throw invalidContainerException(container, null); 193 194 Method m = annoType.members().get("value"); 195 if (m == null) 196 throw invalidContainerException(container, null); 197 198 m.setAccessible(true); 199 200 // This will erase to (Annotation[]) but we do a runtime cast on the 201 // return-value in the method that call this method. 202 @SuppressWarnings("unchecked") 203 A[] values = (A[]) m.invoke(container); 204 return values; 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 throw invalidContainerException(container, e); 210 } 211 } 212 213 214 private static AnnotationFormatError invalidContainerException(Annotation anno, 215 Throwable cause) { 216 return new AnnotationFormatError( 217 anno + " is an invalid container for repeating annotations", 218 cause); 219 } 220 221 222 /* Sanity check type of all the annotation instances of type {@code annoClass} 223 * from {@code container}. 224 */ 225 private static <A extends Annotation> void checkTypes(A[] annotations, 226 Annotation container, 227 Class<A> annoClass) { 228 for (A a : annotations) { 229 if (!annoClass.isInstance(a)) { 230 throw new AnnotationFormatError( 231 String.format("%s is an invalid container for " + 232 "repeating annotations of type: %s", 233 container, annoClass)); 234 } 235 } 236 } 237 }