• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.MoreElements.getLocalAndInheritedMethods;
19 import static com.google.auto.common.MoreElements.getPackage;
20 import static com.google.auto.common.MoreStreams.toImmutableList;
21 import static com.google.auto.common.MoreStreams.toImmutableSet;
22 import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
23 import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
24 import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
25 import static java.util.stream.Collectors.joining;
26 import static java.util.stream.Collectors.toCollection;
27 import static java.util.stream.Collectors.toMap;
28 import static javax.lang.model.util.ElementFilter.constructorsIn;
29 import static javax.lang.model.util.ElementFilter.methodsIn;
30 
31 import com.google.auto.common.AnnotationMirrors;
32 import com.google.auto.common.AnnotationValues;
33 import com.google.auto.common.MoreElements;
34 import com.google.auto.common.MoreTypes;
35 import com.google.auto.common.Visibility;
36 import com.google.auto.service.AutoService;
37 import com.google.auto.value.processor.BuilderSpec.PropertyGetter;
38 import com.google.auto.value.processor.MissingTypes.MissingTypeException;
39 import com.google.common.base.Ascii;
40 import com.google.common.base.VerifyException;
41 import com.google.common.collect.ImmutableList;
42 import com.google.common.collect.ImmutableMap;
43 import com.google.common.collect.ImmutableSet;
44 import com.google.common.collect.Maps;
45 import java.lang.reflect.Field;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.NavigableSet;
49 import java.util.Optional;
50 import java.util.Set;
51 import java.util.TreeSet;
52 import java.util.stream.Stream;
53 import javax.annotation.processing.ProcessingEnvironment;
54 import javax.annotation.processing.Processor;
55 import javax.annotation.processing.SupportedAnnotationTypes;
56 import javax.lang.model.element.AnnotationMirror;
57 import javax.lang.model.element.AnnotationValue;
58 import javax.lang.model.element.Element;
59 import javax.lang.model.element.ElementKind;
60 import javax.lang.model.element.ExecutableElement;
61 import javax.lang.model.element.Modifier;
62 import javax.lang.model.element.PackageElement;
63 import javax.lang.model.element.TypeElement;
64 import javax.lang.model.element.VariableElement;
65 import javax.lang.model.type.TypeKind;
66 import javax.lang.model.type.TypeMirror;
67 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
68 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
69 
70 /**
71  * Javac annotation processor (compiler plugin) for builders; user code never references this class.
72  *
73  * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
74  * @author Éamonn McManus
75  */
76 @AutoService(Processor.class)
77 @SupportedAnnotationTypes(AUTO_BUILDER_NAME)
78 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
79 public class AutoBuilderProcessor extends AutoValueishProcessor {
80   private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";
81 
AutoBuilderProcessor()82   public AutoBuilderProcessor() {
83     super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true);
84   }
85 
86   @Override
getSupportedOptions()87   public Set<String> getSupportedOptions() {
88     return ImmutableSet.of(OMIT_IDENTIFIERS_OPTION, ALLOW_OPTION);
89   }
90 
91   private TypeMirror javaLangVoid;
92 
93   @Override
init(ProcessingEnvironment processingEnv)94   public synchronized void init(ProcessingEnvironment processingEnv) {
95     super.init(processingEnv);
96     javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
97   }
98 
99   @Override
processType(TypeElement autoBuilderType)100   void processType(TypeElement autoBuilderType) {
101     if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
102       errorReporter().reportWarning(autoBuilderType, "The -A%s option is obsolete", ALLOW_OPTION);
103     }
104     // The annotation is guaranteed to be present by the contract of Processor#process
105     AnnotationMirror autoBuilderAnnotation =
106         getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get();
107     TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation);
108     checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass");
109     String callMethod = findCallMethodValue(autoBuilderAnnotation);
110     ImmutableSet<ExecutableElement> methods =
111         abstractMethodsIn(
112             getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils()));
113     ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods);
114     BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter());
115     BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType);
116     TypeMirror builtType = builtType(executable);
117     Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier =
118         BuilderMethodClassifierForAutoBuilder.classify(
119             methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType);
120     if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) {
121       // We've already output one or more error messages.
122       return;
123     }
124     BuilderMethodClassifier<VariableElement> classifier = maybeClassifier.get();
125     Map<String, String> propertyToGetterName =
126         Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
127     AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
128     vars.props = propertySet(autoBuilderType, executable, propertyToGetterName);
129     builder.defineVars(vars, classifier);
130     vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
131     String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
132     vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName);
133     vars.builtType = TypeEncoder.encode(builtType);
134     vars.build = build(executable);
135     vars.types = typeUtils();
136     vars.toBuilderConstructor = false;
137     vars.toBuilderMethods = ImmutableList.of();
138     defineSharedVarsForType(autoBuilderType, ImmutableSet.of(), vars);
139     String text = vars.toText();
140     text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType());
141     text = Reformatter.fixup(text);
142     writeSourceFile(generatedClassName, text, autoBuilderType);
143   }
144 
propertySet( TypeElement autoBuilderType, ExecutableElement executable, Map<String, String> propertyToGetterName)145   private ImmutableSet<Property> propertySet(
146       TypeElement autoBuilderType,
147       ExecutableElement executable,
148       Map<String, String> propertyToGetterName) {
149     boolean autoAnnotation =
150         MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent();
151     ImmutableMap<String, String> builderInitializers =
152         autoAnnotation
153             ? autoAnnotationInitializers(autoBuilderType, executable)
154             : ImmutableMap.of();
155     // Fix any parameter names that are reserved words in Java. Java source code can't have
156     // such parameter names, but Kotlin code might, for example.
157     Map<VariableElement, String> identifiers =
158         executable.getParameters().stream()
159             .collect(toMap(v -> v, v -> v.getSimpleName().toString()));
160     fixReservedIdentifiers(identifiers);
161     return executable.getParameters().stream()
162         .map(
163             v -> {
164               String name = v.getSimpleName().toString();
165               return newProperty(
166                   v,
167                   identifiers.get(v),
168                   propertyToGetterName.get(name),
169                   Optional.ofNullable(builderInitializers.get(name)));
170             })
171         .collect(toImmutableSet());
172   }
173 
newProperty( VariableElement var, String identifier, String getterName, Optional<String> builderInitializer)174   private Property newProperty(
175       VariableElement var,
176       String identifier,
177       String getterName,
178       Optional<String> builderInitializer) {
179     String name = var.getSimpleName().toString();
180     TypeMirror type = var.asType();
181     Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType());
182     return new Property(
183         name,
184         identifier,
185         TypeEncoder.encode(type),
186         type,
187         nullableAnnotation,
188         getterName,
189         builderInitializer);
190   }
191 
autoAnnotationInitializers( TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod)192   private ImmutableMap<String, String> autoAnnotationInitializers(
193       TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) {
194     // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't,
195     // AutoAnnotation will presumably complain, so we don't need to complain further.
196     TypeMirror returnType = autoAnnotationMethod.getReturnType();
197     if (!returnType.getKind().equals(TypeKind.DECLARED)) {
198       return ImmutableMap.of();
199     }
200     // This might not actually be an annotation (if the code is wrong), but if that's the case we
201     // just won't see any contained ExecutableElement where getDefaultValue() returns something.
202     TypeElement annotation = MoreTypes.asTypeElement(returnType);
203     ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
204     for (ExecutableElement method : methodsIn(annotation.getEnclosedElements())) {
205       AnnotationValue defaultValue = method.getDefaultValue();
206       if (defaultValue != null) {
207         String memberName = method.getSimpleName().toString();
208         builder.put(
209             memberName,
210             AnnotationOutput.sourceFormForInitializer(
211                 defaultValue, processingEnv, memberName, autoBuilderType));
212       }
213     }
214     return builder.build();
215   }
216 
findExecutable( TypeElement ofClass, String callMethod, TypeElement autoBuilderType, ImmutableSet<ExecutableElement> methods)217   private ExecutableElement findExecutable(
218       TypeElement ofClass,
219       String callMethod,
220       TypeElement autoBuilderType,
221       ImmutableSet<ExecutableElement> methods) {
222     List<ExecutableElement> executables =
223         findRelevantExecutables(ofClass, callMethod, autoBuilderType);
224     String description =
225         callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\"";
226     switch (executables.size()) {
227       case 0:
228         throw errorReporter()
229             .abortWithError(
230                 autoBuilderType,
231                 "[AutoBuilderNoVisible] No visible %s for %s",
232                 description,
233                 ofClass);
234       case 1:
235         return executables.get(0);
236       default:
237         return matchingExecutable(autoBuilderType, executables, methods, description);
238     }
239   }
240 
findRelevantExecutables( TypeElement ofClass, String callMethod, TypeElement autoBuilderType)241   private ImmutableList<ExecutableElement> findRelevantExecutables(
242       TypeElement ofClass, String callMethod, TypeElement autoBuilderType) {
243     List<? extends Element> elements = ofClass.getEnclosedElements();
244     Stream<ExecutableElement> relevantExecutables =
245         callMethod.isEmpty()
246             ? constructorsIn(elements).stream()
247             : methodsIn(elements).stream()
248                 .filter(m -> m.getSimpleName().contentEquals(callMethod))
249                 .filter(m -> m.getModifiers().contains(Modifier.STATIC));
250     return relevantExecutables
251         .filter(c -> visibleFrom(c, getPackage(autoBuilderType)))
252         .collect(toImmutableList());
253   }
254 
matchingExecutable( TypeElement autoBuilderType, List<ExecutableElement> executables, ImmutableSet<ExecutableElement> methods, String description)255   private ExecutableElement matchingExecutable(
256       TypeElement autoBuilderType,
257       List<ExecutableElement> executables,
258       ImmutableSet<ExecutableElement> methods,
259       String description) {
260     // There's more than one visible executable (constructor or method). We try to find the one that
261     // corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're
262     // basically just looking for an executable where all the parameter names correspond to setters
263     // or property builders in the interface. We might find out after choosing one that it is wrong
264     // for whatever reason (types don't match, spurious methods, etc). But it is likely that if the
265     // names are all accounted for in the methods, and if there's no other matching executable with
266     // more parameters, then this is indeed the one we want. If we later get errors when we try to
267     // analyze the interface in detail, those are probably legitimate errors and not because we
268     // picked the wrong executable.
269     ImmutableList<ExecutableElement> matches =
270         executables.stream().filter(x -> executableMatches(x, methods)).collect(toImmutableList());
271     switch (matches.size()) {
272       case 0:
273         throw errorReporter()
274             .abortWithError(
275                 autoBuilderType,
276                 "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of"
277                     + " any %s:\n%s",
278                 description,
279                 executableListString(executables));
280       case 1:
281         return matches.get(0);
282       default:
283         // More than one match, let's see if we can find the best one.
284     }
285     int max = matches.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt();
286     ImmutableList<ExecutableElement> maxMatches =
287         matches.stream().filter(c -> c.getParameters().size() == max).collect(toImmutableList());
288     if (maxMatches.size() > 1) {
289       throw errorReporter()
290           .abortWithError(
291               autoBuilderType,
292               "[AutoBuilderAmbiguous] Property names correspond to more than one %s:\n%s",
293               description,
294               executableListString(maxMatches));
295     }
296     return maxMatches.get(0);
297   }
298 
executableListString(List<ExecutableElement> executables)299   private String executableListString(List<ExecutableElement> executables) {
300     return executables.stream()
301         .map(AutoBuilderProcessor::executableString)
302         .collect(joining("\n  ", "  ", ""));
303   }
304 
executableString(ExecutableElement executable)305   static String executableString(ExecutableElement executable) {
306     Element nameSource =
307         executable.getKind() == ElementKind.CONSTRUCTOR
308             ? executable.getEnclosingElement()
309             : executable;
310     return nameSource.getSimpleName()
311         + executable.getParameters().stream()
312             .map(v -> v.asType() + " " + v.getSimpleName())
313             .collect(joining(", ", "(", ")"));
314   }
315 
executableMatches( ExecutableElement executable, ImmutableSet<ExecutableElement> methods)316   private boolean executableMatches(
317       ExecutableElement executable, ImmutableSet<ExecutableElement> methods) {
318     // Start with the complete set of parameter names and remove them one by one as we find
319     // corresponding methods. We ignore case, under the assumption that it is unlikely that a case
320     // difference is going to allow a candidate to match when another one is better.
321     // A parameter named foo could be matched by methods like this:
322     //    X foo(Y)
323     //    X setFoo(Y)
324     //    X fooBuilder()
325     //    X fooBuilder(Y)
326     // There are further constraints, including on the types X and Y, that will later be imposed by
327     // BuilderMethodClassifier, but here we just require that there be at least one method with
328     // one of these shapes for foo.
329     NavigableSet<String> parameterNames =
330         executable.getParameters().stream()
331             .map(v -> v.getSimpleName().toString())
332             .collect(toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
333     for (ExecutableElement method : methods) {
334       String name = method.getSimpleName().toString();
335       if (name.endsWith("Builder")) {
336         String property = name.substring(0, name.length() - "Builder".length());
337         parameterNames.remove(property);
338       }
339       if (method.getParameters().size() == 1) {
340         parameterNames.remove(name);
341         if (name.startsWith("set")) {
342           parameterNames.remove(name.substring(3));
343         }
344       }
345       if (parameterNames.isEmpty()) {
346         return true;
347       }
348     }
349     return false;
350   }
351 
visibleFrom(Element element, PackageElement fromPackage)352   private boolean visibleFrom(Element element, PackageElement fromPackage) {
353     Visibility visibility = Visibility.effectiveVisibilityOfElement(element);
354     switch (visibility) {
355       case PUBLIC:
356         return true;
357       case PROTECTED:
358         // We care about whether the constructor is visible from the generated class. The generated
359         // class is never going to be a subclass of the class containing the constructor, so
360         // protected and default access are equivalent.
361       case DEFAULT:
362         return getPackage(element).equals(fromPackage);
363       default:
364         return false;
365     }
366   }
367 
builtType(ExecutableElement executable)368   private TypeMirror builtType(ExecutableElement executable) {
369     switch (executable.getKind()) {
370       case CONSTRUCTOR:
371         return executable.getEnclosingElement().asType();
372       case METHOD:
373         return executable.getReturnType();
374       default:
375         throw new VerifyException("Unexpected executable kind " + executable.getKind());
376     }
377   }
378 
build(ExecutableElement executable)379   private String build(ExecutableElement executable) {
380     TypeElement enclosing = MoreElements.asType(executable.getEnclosingElement());
381     String type = TypeEncoder.encodeRaw(enclosing.asType());
382     switch (executable.getKind()) {
383       case CONSTRUCTOR:
384         boolean generic = !enclosing.getTypeParameters().isEmpty();
385         String typeParams = generic ? "<>" : "";
386         return "new " + type + typeParams;
387       case METHOD:
388         return type + "." + executable.getSimpleName();
389       default:
390         throw new VerifyException("Unexpected executable kind " + executable.getKind());
391     }
392   }
393 
394   private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();
395 
elementKindRecord()396   private static ElementKind elementKindRecord() {
397     try {
398       Field record = ElementKind.class.getField("RECORD");
399       return (ElementKind) record.get(null);
400     } catch (ReflectiveOperationException e) {
401       // OK: we must be on a JDK version that predates this.
402       return null;
403     }
404   }
405 
getOfClass( TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation)406   private TypeElement getOfClass(
407       TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation) {
408     TypeElement ofClassValue = findOfClassValue(autoBuilderAnnotation);
409     boolean isDefault = typeUtils().isSameType(ofClassValue.asType(), javaLangVoid);
410     if (!isDefault) {
411       return ofClassValue;
412     }
413     Element enclosing = autoBuilderType.getEnclosingElement();
414     ElementKind enclosingKind = enclosing.getKind();
415     if (enclosing.getKind() != ElementKind.CLASS && enclosingKind != ELEMENT_KIND_RECORD) {
416       errorReporter()
417           .abortWithError(
418               autoBuilderType,
419               "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it"
420                   + " must be nested inside the class to be built; actually nested inside %s %s.",
421               Ascii.toLowerCase(enclosingKind.name()),
422               enclosing);
423     }
424     return MoreElements.asType(enclosing);
425   }
426 
findOfClassValue(AnnotationMirror autoBuilderAnnotation)427   private TypeElement findOfClassValue(AnnotationMirror autoBuilderAnnotation) {
428     AnnotationValue ofClassValue =
429         AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "ofClass");
430     Object value = ofClassValue.getValue();
431     if (value instanceof TypeMirror) {
432       TypeMirror ofClassType = (TypeMirror) value;
433       switch (ofClassType.getKind()) {
434         case DECLARED:
435           return MoreTypes.asTypeElement(ofClassType);
436         case ERROR:
437           throw new MissingTypeException(MoreTypes.asError(ofClassType));
438         default:
439           break;
440       }
441     }
442     throw new MissingTypeException(null);
443   }
444 
findCallMethodValue(AnnotationMirror autoBuilderAnnotation)445   private String findCallMethodValue(AnnotationMirror autoBuilderAnnotation) {
446     AnnotationValue callMethodValue =
447         AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "callMethod");
448     return AnnotationValues.getString(callMethodValue);
449   }
450 
451   @Override
nullableAnnotationForMethod(ExecutableElement propertyMethod)452   Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
453     // TODO(b/183005059): implement
454     return Optional.empty();
455   }
456 }
457