1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.processor.view.inspector; 18 19 import androidx.annotation.NonNull; 20 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Optional; 26 import java.util.stream.Collectors; 27 28 import javax.annotation.processing.ProcessingEnvironment; 29 import javax.lang.model.element.AnnotationMirror; 30 import javax.lang.model.element.AnnotationValue; 31 import javax.lang.model.element.Element; 32 import javax.lang.model.element.ExecutableElement; 33 import javax.lang.model.element.TypeElement; 34 import javax.lang.model.type.TypeMirror; 35 import javax.lang.model.util.Elements; 36 import javax.lang.model.util.Types; 37 38 /** 39 * Utilities for working with {@link AnnotationMirror}. 40 */ 41 final class AnnotationUtils { 42 private final Elements mElementUtils; 43 private final Types mTypeUtils; 44 AnnotationUtils(@onNull ProcessingEnvironment processingEnv)45 AnnotationUtils(@NonNull ProcessingEnvironment processingEnv) { 46 mElementUtils = processingEnv.getElementUtils(); 47 mTypeUtils = processingEnv.getTypeUtils(); 48 } 49 50 /** 51 * Get a {@link AnnotationMirror} specified by name from an {@link Element}. 52 * 53 * @param qualifiedName The fully qualified name of the annotation to search for 54 * @param element The element to search for annotations on 55 * @return The mirror of the requested annotation 56 * @throws ProcessingException If there is not exactly one of the requested annotation. 57 */ 58 @NonNull exactlyOneMirror(@onNull String qualifiedName, @NonNull Element element)59 AnnotationMirror exactlyOneMirror(@NonNull String qualifiedName, @NonNull Element element) { 60 final Element targetTypeElment = mElementUtils.getTypeElement(qualifiedName); 61 final TypeMirror targetType = targetTypeElment.asType(); 62 AnnotationMirror result = null; 63 64 for (AnnotationMirror annotation : element.getAnnotationMirrors()) { 65 final TypeMirror annotationType = annotation.getAnnotationType().asElement().asType(); 66 if (mTypeUtils.isSameType(annotationType, targetType)) { 67 if (result == null) { 68 result = annotation; 69 } else { 70 final String message = String.format( 71 "Element had multiple instances of @%s, expected exactly one", 72 targetTypeElment.getSimpleName()); 73 74 throw new ProcessingException(message, element, annotation); 75 } 76 } 77 } 78 79 if (result == null) { 80 final String message = String.format( 81 "Expected an @%s annotation, found none", targetTypeElment.getSimpleName()); 82 throw new ProcessingException(message, element); 83 } else { 84 return result; 85 } 86 } 87 88 /** 89 * Determine if an annotation with the supplied qualified name is present on the element. 90 * 91 * @param element The element to check for the presence of an annotation 92 * @param annotationQualifiedName The name of the annotation to check for 93 * @return True if the annotation is present, false otherwise 94 */ hasAnnotation(@onNull Element element, @NonNull String annotationQualifiedName)95 boolean hasAnnotation(@NonNull Element element, @NonNull String annotationQualifiedName) { 96 final TypeElement namedElement = mElementUtils.getTypeElement(annotationQualifiedName); 97 98 if (namedElement != null) { 99 final TypeMirror annotationType = namedElement.asType(); 100 101 for (AnnotationMirror annotation : element.getAnnotationMirrors()) { 102 if (mTypeUtils.isSubtype(annotation.getAnnotationType(), annotationType)) { 103 return true; 104 } 105 } 106 } 107 108 return false; 109 } 110 111 /** 112 * Get a typed list of values for an annotation array property by name. 113 * 114 * The returned list will be empty if the value was left at the default. 115 * 116 * @param propertyName The name of the property to search for 117 * @param valueClass The expected class of the property value 118 * @param element The element the annotation is on, used for exceptions 119 * @param annotationMirror An annotation mirror to search for the property 120 * @param <T> The type of the value 121 * @return A list containing the requested types 122 */ typedArrayValuesByName( @onNull String propertyName, @NonNull Class<T> valueClass, @NonNull Element element, @NonNull AnnotationMirror annotationMirror)123 <T> List<T> typedArrayValuesByName( 124 @NonNull String propertyName, 125 @NonNull Class<T> valueClass, 126 @NonNull Element element, 127 @NonNull AnnotationMirror annotationMirror) { 128 return untypedArrayValuesByName(propertyName, element, annotationMirror) 129 .stream() 130 .map(annotationValue -> { 131 final Object value = annotationValue.getValue(); 132 133 if (value == null) { 134 throw new ProcessingException( 135 "Unexpected null in array.", 136 element, 137 annotationMirror, 138 annotationValue); 139 } 140 141 if (valueClass.isAssignableFrom(value.getClass())) { 142 return valueClass.cast(value); 143 } else { 144 throw new ProcessingException( 145 String.format( 146 "Expected array entry to have type %s, but got %s.", 147 valueClass.getCanonicalName(), 148 value.getClass().getCanonicalName()), 149 element, 150 annotationMirror, 151 annotationValue); 152 } 153 }) 154 .collect(Collectors.toList()); 155 } 156 157 /** 158 * Get a list of values for an annotation array property by name. 159 * 160 * @param propertyName The name of the property to search for 161 * @param element The element the annotation is on, used for exceptions 162 * @param annotationMirror An annotation mirror to search for the property 163 * @return A list of annotation values, empty list if none found 164 */ 165 @NonNull untypedArrayValuesByName( @onNull String propertyName, @NonNull Element element, @NonNull AnnotationMirror annotationMirror)166 List<AnnotationValue> untypedArrayValuesByName( 167 @NonNull String propertyName, 168 @NonNull Element element, 169 @NonNull AnnotationMirror annotationMirror) { 170 return typedValueByName(propertyName, List.class, element, annotationMirror) 171 .map(untypedValues -> { 172 List<AnnotationValue> typedValues = new ArrayList<>(untypedValues.size()); 173 174 for (Object untypedValue : untypedValues) { 175 if (untypedValue instanceof AnnotationValue) { 176 typedValues.add((AnnotationValue) untypedValue); 177 } else { 178 throw new ProcessingException( 179 "Unable to convert array entry to AnnotationValue", 180 element, 181 annotationMirror); 182 } 183 } 184 185 return typedValues; 186 }).orElseGet(Collections::emptyList); 187 } 188 189 /** 190 * Get the typed value of an annotation property by name. 191 * 192 * The returned optional will be empty if the value was left at the default, or if the value 193 * of the property is null. 194 * 195 * @param propertyName The name of the property to search for 196 * @param valueClass The expected class of the property value 197 * @param element The element the annotation is on, used for exceptions 198 * @param annotationMirror An annotation mirror to search for the property 199 * @param <T> The type of the value 200 * @return An optional containing the typed value of the named property 201 */ 202 @NonNull 203 <T> Optional<T> typedValueByName( 204 String propertyName, 205 Class<T> valueClass, 206 Element element, 207 AnnotationMirror annotationMirror) { 208 return valueByName(propertyName, annotationMirror).map(annotationValue -> { 209 final Object value = annotationValue.getValue(); 210 211 if (value == null) { 212 throw new ProcessingException( 213 String.format( 214 "Unexpected null value for annotation property \"%s\".", 215 propertyName), 216 element, 217 annotationMirror, 218 annotationValue); 219 } 220 221 if (valueClass.isAssignableFrom(value.getClass())) { 222 return valueClass.cast(value); 223 } else { 224 throw new ProcessingException( 225 String.format( 226 "Expected annotation property \"%s\" to have type %s, but got %s.", 227 propertyName, 228 valueClass.getCanonicalName(), 229 value.getClass().getCanonicalName()), 230 element, 231 annotationMirror, 232 annotationValue); 233 } 234 }); 235 } 236 237 /** 238 * Get the untyped value of an annotation property by name. 239 * 240 * The returned optional will be empty if the value was left at the default, or if the value 241 * of the property is null. 242 * 243 * @param propertyName The name of the property to search for 244 * @param element The element the annotation is on, used for exceptions 245 * @param annotationMirror An annotation mirror to search for the property 246 * @return An optional containing the untyped value of the named property 247 * @see AnnotationValue#getValue() 248 */ 249 @NonNull 250 Optional<Object> untypedValueByName( 251 @NonNull String propertyName, 252 @NonNull Element element, 253 @NonNull AnnotationMirror annotationMirror) { 254 return valueByName(propertyName, annotationMirror).map(annotationValue -> { 255 final Object value = annotationValue.getValue(); 256 257 if (value == null) { 258 throw new ProcessingException( 259 String.format( 260 "Unexpected null value for annotation property \"%s\".", 261 propertyName), 262 element, 263 annotationMirror, 264 annotationValue); 265 } 266 267 return value; 268 }); 269 } 270 271 /** 272 * Extract a {@link AnnotationValue} from a mirror by string property name. 273 * 274 * @param propertyName The name of the property requested property 275 * @param annotationMirror The mirror to search for the property 276 * @return The value of the property 277 */ 278 @NonNull 279 Optional<AnnotationValue> valueByName( 280 @NonNull String propertyName, 281 @NonNull AnnotationMirror annotationMirror) { 282 final Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap = 283 annotationMirror.getElementValues(); 284 285 for (ExecutableElement method : valueMap.keySet()) { 286 if (method.getSimpleName().contentEquals(propertyName)) { 287 return Optional.ofNullable(valueMap.get(method)); 288 } 289 } 290 291 // Property not explicitly defined, use default value. 292 return Optional.empty(); 293 } 294 } 295