• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.value.processor;
17 
18 import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
19 import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
20 
21 import com.google.auto.common.MoreElements;
22 import com.google.auto.common.SuperficialValidation;
23 import com.google.auto.service.AutoService;
24 import com.google.common.base.Preconditions;
25 import com.google.common.base.Throwables;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.primitives.Primitives;
29 import com.google.errorprone.annotations.FormatMethod;
30 import java.io.IOException;
31 import java.io.Writer;
32 import java.util.Collection;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Optional;
37 import java.util.Set;
38 import javax.annotation.processing.AbstractProcessor;
39 import javax.annotation.processing.ProcessingEnvironment;
40 import javax.annotation.processing.Processor;
41 import javax.annotation.processing.RoundEnvironment;
42 import javax.annotation.processing.SupportedAnnotationTypes;
43 import javax.lang.model.SourceVersion;
44 import javax.lang.model.element.AnnotationValue;
45 import javax.lang.model.element.Element;
46 import javax.lang.model.element.ElementKind;
47 import javax.lang.model.element.ExecutableElement;
48 import javax.lang.model.element.Modifier;
49 import javax.lang.model.element.TypeElement;
50 import javax.lang.model.element.VariableElement;
51 import javax.lang.model.type.ArrayType;
52 import javax.lang.model.type.DeclaredType;
53 import javax.lang.model.type.PrimitiveType;
54 import javax.lang.model.type.TypeKind;
55 import javax.lang.model.type.TypeMirror;
56 import javax.lang.model.type.WildcardType;
57 import javax.lang.model.util.ElementFilter;
58 import javax.lang.model.util.Elements;
59 import javax.lang.model.util.Types;
60 import javax.tools.Diagnostic;
61 import javax.tools.JavaFileObject;
62 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
63 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
64 
65 /**
66  * Javac annotation processor (compiler plugin) to generate annotation implementations. User code
67  * never references this class.
68  *
69  * @author emcmanus@google.com (Éamonn McManus)
70  */
71 @AutoService(Processor.class)
72 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
73 @SupportedAnnotationTypes(AUTO_ANNOTATION_NAME)
74 public class AutoAnnotationProcessor extends AbstractProcessor {
AutoAnnotationProcessor()75   public AutoAnnotationProcessor() {}
76 
77   @Override
getSupportedSourceVersion()78   public SourceVersion getSupportedSourceVersion() {
79     return SourceVersion.latestSupported();
80   }
81 
82   /**
83    * Issue a compilation error. This method does not throw an exception, since we want to continue
84    * processing and perhaps report other errors.
85    */
86   @FormatMethod
reportError(Element e, String msg, Object... msgParams)87   private void reportError(Element e, String msg, Object... msgParams) {
88     String formattedMessage = String.format(msg, msgParams);
89     processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, formattedMessage, e);
90   }
91 
92   /**
93    * Issue a compilation error and return an exception that, when thrown, will cause the processing
94    * of this class to be abandoned. This does not prevent the processing of other classes.
95    */
96   @FormatMethod
abortWithError(Element e, String msg, Object... msgParams)97   private AbortProcessingException abortWithError(Element e, String msg, Object... msgParams) {
98     reportError(e, msg, msgParams);
99     return new AbortProcessingException();
100   }
101 
102   private Elements elementUtils;
103   private Types typeUtils;
104 
105   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)106   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
107     elementUtils = processingEnv.getElementUtils();
108     typeUtils = processingEnv.getTypeUtils();
109     boolean claimed =
110         (annotations.size() == 1
111             && annotations
112                 .iterator()
113                 .next()
114                 .getQualifiedName()
115                 .contentEquals(AUTO_ANNOTATION_NAME));
116     if (claimed) {
117       process(roundEnv);
118       return true;
119     } else {
120       return false;
121     }
122   }
123 
process(RoundEnvironment roundEnv)124   private void process(RoundEnvironment roundEnv) {
125     TypeElement autoAnnotation = elementUtils.getTypeElement(AUTO_ANNOTATION_NAME);
126     Collection<? extends Element> annotatedElements =
127         roundEnv.getElementsAnnotatedWith(autoAnnotation);
128     List<ExecutableElement> methods = ElementFilter.methodsIn(annotatedElements);
129     if (!SuperficialValidation.validateElements(methods) || methodsAreOverloaded(methods)) {
130       return;
131     }
132     for (ExecutableElement method : methods) {
133       try {
134         processMethod(method);
135       } catch (AbortProcessingException e) {
136         // We abandoned this type, but continue with the next.
137       } catch (RuntimeException e) {
138         String trace = Throwables.getStackTraceAsString(e);
139         reportError(method, "@AutoAnnotation processor threw an exception: %s", trace);
140         throw e;
141       }
142     }
143   }
144 
processMethod(ExecutableElement method)145   private void processMethod(ExecutableElement method) {
146     if (!method.getModifiers().contains(Modifier.STATIC)) {
147       throw abortWithError(method, "@AutoAnnotation method must be static");
148     }
149 
150     TypeElement annotationElement = getAnnotationReturnType(method);
151 
152     Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method);
153 
154     ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement);
155     TypeElement methodClass = (TypeElement) method.getEnclosingElement();
156     String pkg = TypeSimplifier.packageNameOf(methodClass);
157 
158     ImmutableMap<String, AnnotationValue> defaultValues = getDefaultValues(annotationElement);
159     ImmutableMap<String, Member> members = getMembers(method, memberMethods);
160     ImmutableMap<String, Parameter> parameters = getParameters(annotationElement, method, members);
161     validateParameters(annotationElement, method, members, parameters, defaultValues);
162 
163     String generatedClassName = generatedClassName(method);
164 
165     AutoAnnotationTemplateVars vars = new AutoAnnotationTemplateVars();
166     vars.annotationFullName = annotationElement.toString();
167     vars.annotationName = TypeEncoder.encode(annotationElement.asType());
168     vars.className = generatedClassName;
169     vars.generated = getGeneratedTypeName();
170     vars.members = members;
171     vars.params = parameters;
172     vars.pkg = pkg;
173     vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections;
174     vars.gwtCompatible = isGwtCompatible(annotationElement);
175     ImmutableMap<String, Integer> invariableHashes = invariableHashes(members, parameters.keySet());
176     vars.invariableHashSum = 0;
177     for (int h : invariableHashes.values()) {
178       vars.invariableHashSum += h;
179     }
180     vars.invariableHashes = invariableHashes.keySet();
181     String text = vars.toText();
182     text = TypeEncoder.decode(text, processingEnv, pkg, annotationElement.asType());
183     text = Reformatter.fixup(text);
184     String fullName = fullyQualifiedName(pkg, generatedClassName);
185     writeSourceFile(fullName, text, methodClass);
186   }
187 
getGeneratedTypeName()188   private String getGeneratedTypeName() {
189     return generatedAnnotation(elementUtils, processingEnv.getSourceVersion())
190         .map(generatedAnnotation -> TypeEncoder.encode(generatedAnnotation.asType()))
191         .orElse("");
192   }
193 
194   /**
195    * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always
196    * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or
197    * an enum constant does potentially change in different runs of the same program. The hashCode of
198    * an array doesn't change if the hashCodes of its elements don't. Although we could have a
199    * similar rule for nested annotation values, we currently don't.
200    */
invariableHash(AnnotationValue annotationValue)201   private static Optional<Integer> invariableHash(AnnotationValue annotationValue) {
202     Object value = annotationValue.getValue();
203     if (value instanceof String || Primitives.isWrapperType(value.getClass())) {
204       return Optional.of(value.hashCode());
205     } else if (value instanceof List<?>) {
206       @SuppressWarnings("unchecked") // by specification
207       List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) value;
208       return invariableHash(list);
209     } else {
210       return Optional.empty();
211     }
212   }
213 
invariableHash( List<? extends AnnotationValue> annotationValues)214   private static Optional<Integer> invariableHash(
215       List<? extends AnnotationValue> annotationValues) {
216     int h = 1;
217     for (AnnotationValue annotationValue : annotationValues) {
218       Optional<Integer> maybeHash = invariableHash(annotationValue);
219       if (!maybeHash.isPresent()) {
220         return Optional.empty();
221       }
222       h = h * 31 + maybeHash.get();
223     }
224     return Optional.of(h);
225   }
226 
227   /**
228    * Returns a map from the names of members with invariable hashCodes to the values of those
229    * hashCodes.
230    */
invariableHashes( ImmutableMap<String, Member> members, ImmutableSet<String> parameters)231   private static ImmutableMap<String, Integer> invariableHashes(
232       ImmutableMap<String, Member> members, ImmutableSet<String> parameters) {
233     ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
234     for (String element : members.keySet()) {
235       if (!parameters.contains(element)) {
236         Member member = members.get(element);
237         AnnotationValue annotationValue = member.method.getDefaultValue();
238         Optional<Integer> invariableHash = invariableHash(annotationValue);
239         if (invariableHash.isPresent()) {
240           builder.put(element, (element.hashCode() * 127) ^ invariableHash.get());
241         }
242       }
243     }
244     return builder.build();
245   }
246 
methodsAreOverloaded(List<ExecutableElement> methods)247   private boolean methodsAreOverloaded(List<ExecutableElement> methods) {
248     boolean overloaded = false;
249     Set<String> classNames = new HashSet<String>();
250     for (ExecutableElement method : methods) {
251       String qualifiedClassName =
252           fullyQualifiedName(
253               MoreElements.getPackage(method).getQualifiedName().toString(),
254               generatedClassName(method));
255       if (!classNames.add(qualifiedClassName)) {
256         overloaded = true;
257         reportError(method, "@AutoAnnotation methods cannot be overloaded");
258       }
259     }
260     return overloaded;
261   }
262 
generatedClassName(ExecutableElement method)263   private String generatedClassName(ExecutableElement method) {
264     TypeElement type = (TypeElement) method.getEnclosingElement();
265     String name = type.getSimpleName().toString();
266     while (type.getEnclosingElement() instanceof TypeElement) {
267       type = (TypeElement) type.getEnclosingElement();
268       name = type.getSimpleName() + "_" + name;
269     }
270     return "AutoAnnotation_" + name + "_" + method.getSimpleName();
271   }
272 
getAnnotationReturnType(ExecutableElement method)273   private TypeElement getAnnotationReturnType(ExecutableElement method) {
274     TypeMirror returnTypeMirror = method.getReturnType();
275     if (returnTypeMirror.getKind() == TypeKind.DECLARED) {
276       Element returnTypeElement = typeUtils.asElement(method.getReturnType());
277       if (returnTypeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
278         return (TypeElement) returnTypeElement;
279       }
280     }
281     throw abortWithError(
282         method,
283         "Return type of @AutoAnnotation method must be an annotation type, not %s",
284         returnTypeMirror);
285   }
286 
getMemberMethods(TypeElement annotationElement)287   private ImmutableMap<String, ExecutableElement> getMemberMethods(TypeElement annotationElement) {
288     ImmutableMap.Builder<String, ExecutableElement> members = ImmutableMap.builder();
289     for (ExecutableElement member :
290         ElementFilter.methodsIn(annotationElement.getEnclosedElements())) {
291       String name = member.getSimpleName().toString();
292       members.put(name, member);
293     }
294     return members.build();
295   }
296 
getMembers( Element context, ImmutableMap<String, ExecutableElement> memberMethods)297   private ImmutableMap<String, Member> getMembers(
298       Element context, ImmutableMap<String, ExecutableElement> memberMethods) {
299     ImmutableMap.Builder<String, Member> members = ImmutableMap.builder();
300     for (Map.Entry<String, ExecutableElement> entry : memberMethods.entrySet()) {
301       ExecutableElement memberMethod = entry.getValue();
302       String name = memberMethod.getSimpleName().toString();
303       members.put(name, new Member(processingEnv, context, memberMethod));
304     }
305     return members.build();
306   }
307 
getDefaultValues(TypeElement annotationElement)308   private ImmutableMap<String, AnnotationValue> getDefaultValues(TypeElement annotationElement) {
309     ImmutableMap.Builder<String, AnnotationValue> defaultValues = ImmutableMap.builder();
310     for (ExecutableElement member :
311         ElementFilter.methodsIn(annotationElement.getEnclosedElements())) {
312       String name = member.getSimpleName().toString();
313       AnnotationValue defaultValue = member.getDefaultValue();
314       if (defaultValue != null) {
315         defaultValues.put(name, defaultValue);
316       }
317     }
318     return defaultValues.build();
319   }
320 
getParameters( TypeElement annotationElement, ExecutableElement method, Map<String, Member> members)321   private ImmutableMap<String, Parameter> getParameters(
322       TypeElement annotationElement, ExecutableElement method, Map<String, Member> members) {
323     ImmutableMap.Builder<String, Parameter> parameters = ImmutableMap.builder();
324     boolean error = false;
325     for (VariableElement parameter : method.getParameters()) {
326       String name = parameter.getSimpleName().toString();
327       Member member = members.get(name);
328       if (member == null) {
329         reportError(
330             parameter,
331             "@AutoAnnotation method parameter '%s' must have the same name as a member of %s",
332             name,
333             annotationElement);
334         error = true;
335       } else {
336         TypeMirror parameterType = parameter.asType();
337         TypeMirror memberType = member.getTypeMirror();
338         if (compatibleTypes(parameterType, memberType)) {
339           parameters.put(name, new Parameter(parameterType));
340         } else {
341           reportError(
342               parameter,
343               "@AutoAnnotation method parameter '%s' has type %s but %s.%s has type %s",
344               name,
345               parameterType,
346               annotationElement,
347               name,
348               memberType);
349           error = true;
350         }
351       }
352     }
353     if (error) {
354       throw new AbortProcessingException();
355     }
356     return parameters.build();
357   }
358 
validateParameters( TypeElement annotationElement, ExecutableElement method, ImmutableMap<String, Member> members, ImmutableMap<String, Parameter> parameters, ImmutableMap<String, AnnotationValue> defaultValues)359   private void validateParameters(
360       TypeElement annotationElement,
361       ExecutableElement method,
362       ImmutableMap<String, Member> members,
363       ImmutableMap<String, Parameter> parameters,
364       ImmutableMap<String, AnnotationValue> defaultValues) {
365     boolean error = false;
366     for (String memberName : members.keySet()) {
367       if (!parameters.containsKey(memberName) && !defaultValues.containsKey(memberName)) {
368         reportError(
369             method,
370             "@AutoAnnotation method needs a parameter with name '%s' and type %s"
371                 + " corresponding to %s.%s, which has no default value",
372             memberName,
373             members.get(memberName).getType(),
374             annotationElement,
375             memberName);
376         error = true;
377       }
378     }
379     if (error) {
380       throw new AbortProcessingException();
381     }
382   }
383 
384   /**
385    * Returns true if {@code parameterType} can be used to provide the value of an annotation member
386    * of type {@code memberType}. They must either be the same type, or the member type must be an
387    * array and the parameter type must be a collection of a compatible type.
388    */
compatibleTypes(TypeMirror parameterType, TypeMirror memberType)389   private boolean compatibleTypes(TypeMirror parameterType, TypeMirror memberType) {
390     if (typeUtils.isAssignable(parameterType, memberType)) {
391       // parameterType assignable to memberType, which in the restricted world of annotations
392       // means they are the same type, or maybe memberType is an annotation type and parameterType
393       // is a subtype of that annotation interface (why would you do that?).
394       return true;
395     }
396     // They're not the same, but we could still consider them compatible if for example
397     // parameterType is List<Integer> and memberType is int[]. We accept any type that is assignable
398     // to Collection<Integer> (in this example).
399     if (memberType.getKind() != TypeKind.ARRAY) {
400       return false;
401     }
402     TypeMirror arrayElementType = ((ArrayType) memberType).getComponentType();
403     TypeMirror wrappedArrayElementType =
404         arrayElementType.getKind().isPrimitive()
405             ? typeUtils.boxedClass((PrimitiveType) arrayElementType).asType()
406             : arrayElementType;
407     TypeElement javaUtilCollection =
408         elementUtils.getTypeElement(Collection.class.getCanonicalName());
409     DeclaredType collectionOfElement =
410         typeUtils.getDeclaredType(javaUtilCollection, wrappedArrayElementType);
411     return typeUtils.isAssignable(parameterType, collectionOfElement);
412   }
413 
414   /**
415    * Returns the wrapper types ({@code Integer.class} etc) that are used in collection parameters
416    * like {@code List<Integer>}. This is needed because we will emit a helper method for each such
417    * type, for example to convert {@code Collection<Integer>} into {@code int[]}.
418    */
wrapperTypesUsedInCollections(ExecutableElement method)419   private Set<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) {
420     TypeElement javaUtilCollection = elementUtils.getTypeElement(Collection.class.getName());
421     ImmutableSet.Builder<Class<?>> usedInCollections = ImmutableSet.builder();
422     for (Class<?> wrapper : Primitives.allWrapperTypes()) {
423       DeclaredType collectionOfWrapper =
424           typeUtils.getDeclaredType(javaUtilCollection, getTypeMirror(wrapper));
425       for (VariableElement parameter : method.getParameters()) {
426         if (typeUtils.isAssignable(parameter.asType(), collectionOfWrapper)) {
427           usedInCollections.add(wrapper);
428           break;
429         }
430       }
431     }
432     return usedInCollections.build();
433   }
434 
getTypeMirror(Class<?> c)435   private TypeMirror getTypeMirror(Class<?> c) {
436     return elementUtils.getTypeElement(c.getName()).asType();
437   }
438 
isGwtCompatible(TypeElement annotationElement)439   private static boolean isGwtCompatible(TypeElement annotationElement) {
440     return annotationElement
441         .getAnnotationMirrors()
442         .stream()
443         .map(mirror -> mirror.getAnnotationType().asElement())
444         .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible"));
445   }
446 
fullyQualifiedName(String pkg, String cls)447   private static String fullyQualifiedName(String pkg, String cls) {
448     return pkg.isEmpty() ? cls : pkg + "." + cls;
449   }
450 
writeSourceFile(String className, String text, TypeElement originatingType)451   private void writeSourceFile(String className, String text, TypeElement originatingType) {
452     try {
453       JavaFileObject sourceFile =
454           processingEnv.getFiler().createSourceFile(className, originatingType);
455       try (Writer writer = sourceFile.openWriter()) {
456         writer.write(text);
457       }
458     } catch (IOException e) {
459       // This should really be an error, but we make it a warning in the hope of resisting Eclipse
460       // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599. If that bug manifests, we may get
461       // invoked more than once for the same file, so ignoring the ability to overwrite it is the
462       // right thing to do. If we are unable to write for some other reason, we should get a compile
463       // error later because user code will have a reference to the code we were supposed to
464       // generate (new AutoValue_Foo() or whatever) and that reference will be undefined.
465       processingEnv
466           .getMessager()
467           .printMessage(
468               Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e);
469     }
470   }
471 
472   public static class Member {
473     private final ProcessingEnvironment processingEnv;
474     private final Element context;
475     private final ExecutableElement method;
476 
Member(ProcessingEnvironment processingEnv, Element context, ExecutableElement method)477     Member(ProcessingEnvironment processingEnv, Element context, ExecutableElement method) {
478       this.processingEnv = processingEnv;
479       this.context = context;
480       this.method = method;
481     }
482 
483     @Override
toString()484     public String toString() {
485       return method.getSimpleName().toString();
486     }
487 
getType()488     public String getType() {
489       return TypeEncoder.encode(getTypeMirror());
490     }
491 
getComponentType()492     public String getComponentType() {
493       Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY);
494       ArrayType arrayType = (ArrayType) getTypeMirror();
495       return TypeEncoder.encode(arrayType.getComponentType());
496     }
497 
getTypeMirror()498     public TypeMirror getTypeMirror() {
499       return method.getReturnType();
500     }
501 
getKind()502     public TypeKind getKind() {
503       return getTypeMirror().getKind();
504     }
505 
506     // Used as part of the hashCode() computation.
507     // See https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Annotation.html#hashCode--
getNameHash()508     public int getNameHash() {
509       return 127 * toString().hashCode();
510     }
511 
isArrayOfClassWithBounds()512     public boolean isArrayOfClassWithBounds() {
513       if (getTypeMirror().getKind() != TypeKind.ARRAY) {
514         return false;
515       }
516       TypeMirror componentType = ((ArrayType) getTypeMirror()).getComponentType();
517       if (componentType.getKind() != TypeKind.DECLARED) {
518         return false;
519       }
520       DeclaredType declared = (DeclaredType) componentType;
521       if (!((TypeElement) processingEnv.getTypeUtils().asElement(componentType))
522           .getQualifiedName()
523           .contentEquals("java.lang.Class")) {
524         return false;
525       }
526       if (declared.getTypeArguments().size() != 1) {
527         return false;
528       }
529       TypeMirror parameter = declared.getTypeArguments().get(0);
530       if (parameter.getKind() != TypeKind.WILDCARD) {
531         return true; // for Class<Foo>
532       }
533       WildcardType wildcard = (WildcardType) parameter;
534       // In theory, we should check if getExtendsBound() != Object, since '?' is equivalent to
535       // '? extends Object', but, experimentally, neither javac or ecj will sets getExtendsBound()
536       // to 'Object', so there isn't a point in checking.
537       return wildcard.getSuperBound() != null || wildcard.getExtendsBound() != null;
538     }
539 
getDefaultValue()540     public String getDefaultValue() {
541       AnnotationValue defaultValue = method.getDefaultValue();
542       if (defaultValue == null) {
543         return null;
544       } else {
545         return AnnotationOutput.sourceFormForInitializer(
546             defaultValue, processingEnv, method.getSimpleName().toString(), context);
547       }
548     }
549   }
550 
551   public static class Parameter {
552     private final String typeName;
553     private final TypeKind kind;
554 
Parameter(TypeMirror type)555     Parameter(TypeMirror type) {
556       this.typeName = TypeEncoder.encode(type);
557       this.kind = type.getKind();
558     }
559 
getType()560     public String getType() {
561       return typeName;
562     }
563 
getKind()564     public TypeKind getKind() {
565       return kind;
566     }
567   }
568 }
569