1 /* 2 * Copyright 2014 Google LLC 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 package com.google.auto.common; 17 18 import static com.google.auto.common.MoreElements.isAnnotationPresent; 19 import static com.google.auto.common.MoreStreams.toImmutableSet; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static java.util.Collections.unmodifiableMap; 22 23 import com.google.common.base.Equivalence; 24 import com.google.common.collect.ImmutableMap; 25 import com.google.common.collect.ImmutableSet; 26 import java.lang.annotation.Annotation; 27 import java.util.Arrays; 28 import java.util.Map; 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.DeclaredType; 35 import javax.lang.model.util.ElementFilter; 36 import javax.lang.model.util.Elements; 37 38 /** 39 * A utility class for working with {@link AnnotationMirror} instances. 40 * 41 * @author Gregory Kick 42 */ 43 public final class AnnotationMirrors { 44 private static final Equivalence<AnnotationMirror> ANNOTATION_MIRROR_EQUIVALENCE = 45 new Equivalence<AnnotationMirror>() { 46 @Override 47 protected boolean doEquivalent(AnnotationMirror left, AnnotationMirror right) { 48 return MoreTypes.equivalence() 49 .equivalent(left.getAnnotationType(), right.getAnnotationType()) 50 && AnnotationValues.equivalence() 51 .pairwise() 52 .equivalent( 53 getAnnotationValuesWithDefaults(left).values(), 54 getAnnotationValuesWithDefaults(right).values()); 55 } 56 57 @Override 58 protected int doHash(AnnotationMirror annotation) { 59 DeclaredType type = annotation.getAnnotationType(); 60 Iterable<AnnotationValue> annotationValues = 61 getAnnotationValuesWithDefaults(annotation).values(); 62 return Arrays.hashCode( 63 new int[] { 64 MoreTypes.equivalence().hash(type), 65 AnnotationValues.equivalence().pairwise().hash(annotationValues) 66 }); 67 } 68 69 @Override 70 public String toString() { 71 return "AnnotationMirrors.equivalence()"; 72 } 73 }; 74 75 /** 76 * Returns an {@link Equivalence} for {@link AnnotationMirror} as some implementations 77 * delegate equality tests to {@link Object#equals} whereas the documentation explicitly 78 * states that instance/reference equality is not the proper test. 79 */ equivalence()80 public static Equivalence<AnnotationMirror> equivalence() { 81 return ANNOTATION_MIRROR_EQUIVALENCE; 82 } 83 84 /** 85 * Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed by {@link 86 * ExecutableElement}, supplying default values from the annotation if the annotation property has 87 * not been set. This is equivalent to {@link 88 * Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be called statically without 89 * an {@link Elements} instance. 90 * 91 * <p>The iteration order of elements of the returned map will be the order in which the {@link 92 * ExecutableElement}s are defined in {@code annotation}'s {@linkplain 93 * AnnotationMirror#getAnnotationType() type}. 94 */ getAnnotationValuesWithDefaults( AnnotationMirror annotation)95 public static ImmutableMap<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults( 96 AnnotationMirror annotation) { 97 ImmutableMap.Builder<ExecutableElement, AnnotationValue> values = ImmutableMap.builder(); 98 // Use unmodifiableMap to eliminate wildcards, which cause issues for our nullness checker. 99 @SuppressWarnings("GetElementValues") 100 Map<ExecutableElement, AnnotationValue> declaredValues = 101 unmodifiableMap(annotation.getElementValues()); 102 for (ExecutableElement method : 103 ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) { 104 // Must iterate and put in this order, to ensure consistency in generated code. 105 if (declaredValues.containsKey(method)) { 106 values.put(method, declaredValues.get(method)); 107 } else if (method.getDefaultValue() != null) { 108 values.put(method, method.getDefaultValue()); 109 } else { 110 throw new IllegalStateException( 111 "Unset annotation value without default should never happen: " 112 + MoreElements.asType(method.getEnclosingElement()).getQualifiedName() 113 + '.' 114 + method.getSimpleName() 115 + "()"); 116 } 117 } 118 return values.build(); 119 } 120 121 /** 122 * Returns an {@link AnnotationValue} for the named element if such an element was 123 * either declared in the usage represented by the provided {@link AnnotationMirror}, or if 124 * such an element was defined with a default. 125 * 126 * @throws IllegalArgumentException if no element is defined with the given elementName. 127 */ getAnnotationValue( AnnotationMirror annotationMirror, String elementName)128 public static AnnotationValue getAnnotationValue( 129 AnnotationMirror annotationMirror, String elementName) { 130 return getAnnotationElementAndValue(annotationMirror, elementName).getValue(); 131 } 132 133 /** 134 * Returns a {@link ExecutableElement} and its associated {@link AnnotationValue} if such 135 * an element was either declared in the usage represented by the provided 136 * {@link AnnotationMirror}, or if such an element was defined with a default. 137 * 138 * @throws IllegalArgumentException if no element is defined with the given elementName. 139 */ getAnnotationElementAndValue( AnnotationMirror annotationMirror, final String elementName)140 public static Map.Entry<ExecutableElement, AnnotationValue> getAnnotationElementAndValue( 141 AnnotationMirror annotationMirror, final String elementName) { 142 checkNotNull(annotationMirror); 143 checkNotNull(elementName); 144 for (Map.Entry<ExecutableElement, AnnotationValue> entry : 145 getAnnotationValuesWithDefaults(annotationMirror).entrySet()) { 146 if (entry.getKey().getSimpleName().contentEquals(elementName)) { 147 return entry; 148 } 149 } 150 throw new IllegalArgumentException( 151 String.format( 152 "@%s does not define an element %s()", 153 MoreElements.asType(annotationMirror.getAnnotationType().asElement()) 154 .getQualifiedName(), 155 elementName)); 156 } 157 158 /** 159 * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link 160 * Element} which are themselves annotated with {@code annotationClass}. 161 */ getAnnotatedAnnotations( Element element, Class<? extends Annotation> annotationClass)162 public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations( 163 Element element, Class<? extends Annotation> annotationClass) { 164 String name = annotationClass.getCanonicalName(); 165 if (name == null) { 166 return ImmutableSet.of(); 167 } 168 return getAnnotatedAnnotations(element, name); 169 } 170 171 /** 172 * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link 173 * Element} which are themselves annotated with {@code annotation}. 174 */ getAnnotatedAnnotations( Element element, TypeElement annotation)175 public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations( 176 Element element, TypeElement annotation) { 177 return element.getAnnotationMirrors().stream() 178 .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotation)) 179 .collect(toImmutableSet()); 180 } 181 182 /** 183 * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link 184 * Element} which are themselves annotated with an annotation whose type's canonical name is 185 * {@code annotationName}. 186 */ getAnnotatedAnnotations( Element element, String annotationName)187 public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations( 188 Element element, String annotationName) { 189 return element.getAnnotationMirrors().stream() 190 .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) 191 .collect(toImmutableSet()); 192 } 193 194 /** 195 * Returns a string representation of the given annotation mirror, suitable for inclusion in a 196 * Java source file to reproduce the annotation in source form. 197 * 198 * <p>Fully qualified names are used for types in annotations, class literals, and enum constants, 199 * ensuring that the source form will compile without requiring additional imports. 200 */ toString(AnnotationMirror annotationMirror)201 public static String toString(AnnotationMirror annotationMirror) { 202 return AnnotationOutput.toString(annotationMirror); 203 } 204 AnnotationMirrors()205 private AnnotationMirrors() {} 206 } 207