• 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.MoreElements.getLocalAndInheritedMethods;
19 import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror;
20 import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor;
21 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
22 import static com.google.common.collect.Sets.immutableEnumSet;
23 import static java.util.stream.Collectors.toList;
24 import static java.util.stream.Collectors.toSet;
25 import static javax.lang.model.util.ElementFilter.methodsIn;
26 import static javax.lang.model.util.ElementFilter.typesIn;
27 
28 import com.google.auto.common.MoreTypes;
29 import com.google.auto.value.extension.AutoValueExtension;
30 import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property;
31 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
32 import com.google.common.collect.ImmutableBiMap;
33 import com.google.common.collect.ImmutableMap;
34 import com.google.common.collect.ImmutableSet;
35 import com.google.common.collect.Iterables;
36 import com.google.common.collect.Maps;
37 import java.util.LinkedHashSet;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Optional;
41 import java.util.Set;
42 import java.util.function.Function;
43 import javax.annotation.processing.ProcessingEnvironment;
44 import javax.lang.model.element.Element;
45 import javax.lang.model.element.ElementKind;
46 import javax.lang.model.element.ExecutableElement;
47 import javax.lang.model.element.Modifier;
48 import javax.lang.model.element.TypeElement;
49 import javax.lang.model.element.TypeParameterElement;
50 import javax.lang.model.element.VariableElement;
51 import javax.lang.model.type.DeclaredType;
52 import javax.lang.model.type.TypeKind;
53 import javax.lang.model.type.TypeMirror;
54 import javax.lang.model.util.Types;
55 
56 /**
57  * Support for AutoValue builders.
58  *
59  * @author Éamonn McManus
60  */
61 class BuilderSpec {
62   private final TypeElement autoValueClass;
63   private final ProcessingEnvironment processingEnv;
64   private final ErrorReporter errorReporter;
65 
BuilderSpec( TypeElement autoValueClass, ProcessingEnvironment processingEnv, ErrorReporter errorReporter)66   BuilderSpec(
67       TypeElement autoValueClass,
68       ProcessingEnvironment processingEnv,
69       ErrorReporter errorReporter) {
70     this.autoValueClass = autoValueClass;
71     this.processingEnv = processingEnv;
72     this.errorReporter = errorReporter;
73   }
74 
75   private static final ImmutableSet<ElementKind> CLASS_OR_INTERFACE =
76       immutableEnumSet(ElementKind.CLASS, ElementKind.INTERFACE);
77 
78   /**
79    * Determines if the {@code @AutoValue} class for this instance has a correct nested
80    * {@code @AutoValue.Builder} class or interface and return a representation of it in an {@code
81    * Optional} if so.
82    */
getBuilder()83   Optional<Builder> getBuilder() {
84     Optional<TypeElement> builderTypeElement = Optional.empty();
85     for (TypeElement containedClass : typesIn(autoValueClass.getEnclosedElements())) {
86       if (hasAnnotationMirror(containedClass, AUTO_VALUE_BUILDER_NAME)) {
87         if (!CLASS_OR_INTERFACE.contains(containedClass.getKind())) {
88           errorReporter.reportError(
89               containedClass,
90               "[AutoValueBuilderClass] @AutoValue.Builder can only apply to a class or an"
91                   + " interface");
92         } else if (!containedClass.getModifiers().contains(Modifier.STATIC)) {
93           errorReporter.reportError(
94               containedClass,
95               "[AutoValueInnerBuilder] @AutoValue.Builder cannot be applied to a non-static class");
96         } else if (builderTypeElement.isPresent()) {
97           errorReporter.reportError(
98               containedClass,
99               "[AutoValueTwoBuilders] %s already has a Builder: %s",
100               autoValueClass,
101               builderTypeElement.get());
102         } else {
103           builderTypeElement = Optional.of(containedClass);
104         }
105       }
106     }
107 
108     if (builderTypeElement.isPresent()) {
109       return builderFrom(builderTypeElement.get());
110     } else {
111       return Optional.empty();
112     }
113   }
114 
115   /** Representation of an {@code AutoValue.Builder} class or interface. */
116   class Builder implements AutoValueExtension.BuilderContext {
117     private final TypeElement builderTypeElement;
118     private ImmutableSet<ExecutableElement> toBuilderMethods;
119     private ExecutableElement buildMethod;
120     private BuilderMethodClassifier classifier;
121 
Builder(TypeElement builderTypeElement)122     Builder(TypeElement builderTypeElement) {
123       this.builderTypeElement = builderTypeElement;
124     }
125 
126     @Override
builderType()127     public TypeElement builderType() {
128       return builderTypeElement;
129     }
130 
131     @Override
builderMethods()132     public Set<ExecutableElement> builderMethods() {
133       return methodsIn(autoValueClass.getEnclosedElements()).stream()
134           .filter(
135               m ->
136                   m.getParameters().isEmpty()
137                       && m.getModifiers().contains(Modifier.STATIC)
138                       && !m.getModifiers().contains(Modifier.PRIVATE)
139                       && erasedTypeIs(m.getReturnType(), builderTypeElement))
140           .collect(toSet());
141     }
142 
143     @Override
buildMethod()144     public Optional<ExecutableElement> buildMethod() {
145       return methodsIn(builderTypeElement.getEnclosedElements()).stream()
146           .filter(
147               m ->
148                   m.getSimpleName().contentEquals("build")
149                       && !m.getModifiers().contains(Modifier.PRIVATE)
150                       && !m.getModifiers().contains(Modifier.STATIC)
151                       && m.getParameters().isEmpty()
152                       && erasedTypeIs(m.getReturnType(), autoValueClass))
153           .findFirst();
154     }
155 
156     @Override
autoBuildMethod()157     public ExecutableElement autoBuildMethod() {
158       return buildMethod;
159     }
160 
161     @Override
setters()162     public Map<String, Set<ExecutableElement>> setters() {
163       return Maps.transformValues(
164           classifier.propertyNameToSetters().asMap(),
165           propertySetters ->
166               propertySetters.stream().map(PropertySetter::getSetter).collect(toSet()));
167     }
168 
169     @Override
propertyBuilders()170     public Map<String, ExecutableElement> propertyBuilders() {
171       return Maps.transformValues(
172           classifier.propertyNameToPropertyBuilder(), PropertyBuilder::getPropertyBuilderMethod);
173     }
174 
erasedTypeIs(TypeMirror type, TypeElement baseType)175     private boolean erasedTypeIs(TypeMirror type, TypeElement baseType) {
176       return type.getKind().equals(TypeKind.DECLARED)
177           && MoreTypes.asDeclared(type).asElement().equals(baseType);
178     }
179 
180     @Override
toBuilderMethods()181     public Set<ExecutableElement> toBuilderMethods() {
182       return toBuilderMethods;
183     }
184 
185     /**
186      * Finds any methods in the set that return the builder type. If the builder has type parameters
187      * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the
188      * same parameter names. We enforce elsewhere that the names and bounds of the builder
189      * parameters must be the same as those of the @AutoValue class. Here's a correct example:
190      *
191      * <pre>
192      * {@code @AutoValue abstract class Foo<A extends Number, B> {
193      *   abstract int someProperty();
194      *
195      *   abstract Builder<A, B> toBuilder();
196      *
197      *   interface Builder<A extends Number, B> {...}
198      * }}
199      * </pre>
200      *
201      * <p>We currently impose that there cannot be more than one such method.
202      */
toBuilderMethods( Types typeUtils, Set<ExecutableElement> abstractMethods)203     ImmutableSet<ExecutableElement> toBuilderMethods(
204         Types typeUtils, Set<ExecutableElement> abstractMethods) {
205 
206       List<String> builderTypeParamNames =
207           builderTypeElement.getTypeParameters().stream()
208               .map(e -> e.getSimpleName().toString())
209               .collect(toList());
210 
211       ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder();
212       for (ExecutableElement method : abstractMethods) {
213         if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) {
214           methods.add(method);
215           DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType());
216           List<String> typeArguments =
217               returnType.getTypeArguments().stream()
218                   .filter(t -> t.getKind().equals(TypeKind.TYPEVAR))
219                   .map(t -> typeUtils.asElement(t).getSimpleName().toString())
220                   .collect(toList());
221           if (!builderTypeParamNames.equals(typeArguments)) {
222             errorReporter.reportError(
223                 method,
224                 "[AutoValueBuilderConverterReturn] Builder converter method should return %s%s",
225                 builderTypeElement,
226                 TypeSimplifier.actualTypeParametersString(builderTypeElement));
227           }
228         }
229       }
230       ImmutableSet<ExecutableElement> builderMethods = methods.build();
231       if (builderMethods.size() > 1) {
232         errorReporter.reportError(
233             builderMethods.iterator().next(),
234             "[AutoValueTwoBuilderConverters] There can be at most one builder converter method");
235       }
236       this.toBuilderMethods = builderMethods;
237       return builderMethods;
238     }
239 
defineVars( AutoValueTemplateVars vars, ImmutableBiMap<ExecutableElement, String> getterToPropertyName)240     void defineVars(
241         AutoValueTemplateVars vars,
242         ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
243       Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement);
244       boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty();
245       ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType =
246           TypeVariables.rewriteReturnTypes(
247               processingEnv.getElementUtils(),
248               processingEnv.getTypeUtils(),
249               getterToPropertyName.keySet(),
250               autoValueClass,
251               builderTypeElement);
252       Optional<BuilderMethodClassifier> optionalClassifier =
253           BuilderMethodClassifier.classify(
254               builderMethods,
255               errorReporter,
256               processingEnv,
257               autoValueClass,
258               builderTypeElement,
259               getterToPropertyName,
260               getterToPropertyType,
261               autoValueHasToBuilder);
262       if (!optionalClassifier.isPresent()) {
263         return;
264       }
265       for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) {
266         if (method.getSimpleName().contentEquals("builder")
267                 && method.getModifiers().contains(Modifier.STATIC)
268                 && method.getAnnotationMirrors().isEmpty()) {
269           // For now we ignore methods with annotations, because for example we do want to allow
270           // Jackson's @JsonCreator.
271           errorReporter.reportWarning(
272               method,
273               "[AutoValueBuilderInBuilder] Static builder() method should be in the containing"
274                   + " class");
275         }
276       }
277       this.classifier = optionalClassifier.get();
278       Set<ExecutableElement> buildMethods = classifier.buildMethods();
279       if (buildMethods.size() != 1) {
280         Set<? extends Element> errorElements =
281             buildMethods.isEmpty() ? ImmutableSet.of(builderTypeElement) : buildMethods;
282         for (Element buildMethod : errorElements) {
283           errorReporter.reportError(
284               buildMethod,
285               "[AutoValueBuilderBuild] Builder must have a single no-argument method returning"
286                   + " %s%s",
287               autoValueClass,
288               typeParamsString());
289         }
290         return;
291       }
292       this.buildMethod = Iterables.getOnlyElement(buildMethods);
293       vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE;
294       vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement);
295       vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement);
296       vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement);
297       vars.buildMethod = Optional.of(new SimpleMethod(buildMethod));
298       vars.builderGetters = classifier.builderGetters();
299       vars.builderSetters = classifier.propertyNameToSetters();
300 
301       vars.builderPropertyBuilders =
302           ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder());
303 
304       Set<Property> required = new LinkedHashSet<>(vars.props);
305       for (Property property : vars.props) {
306         if (property.isNullable()
307             || property.getOptional() != null
308             || vars.builderPropertyBuilders.containsKey(property.getName())) {
309           required.remove(property);
310         }
311       }
312       vars.builderRequiredProperties = ImmutableSet.copyOf(required);
313     }
314   }
315 
316   /**
317    * Information about a builder property getter, referenced from the autovalue.vm template. A
318    * property called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a
319    * getter method in the builder with the same name ({@code foo()} or {@code getFoo()}) and a
320    * return type of either {@code T} or {@code Optional<T>}. The {@code Optional<T>} form can be
321    * used to tell whether the property has been set. Here, {@code Optional<T>} can be either {@code
322    * java.util.Optional} or {@code com.google.common.base.Optional}. If {@code T} is {@code int},
323    * {@code long}, or {@code double}, then instead of {@code Optional<T>} we can have {@code
324    * OptionalInt} etc. If {@code T} is a primitive type (including these ones but also the other
325    * five) then {@code Optional<T>} can be the corresponding boxed type.
326    */
327   public static class PropertyGetter {
328     private final String access;
329     private final String type;
330     private final Optionalish optional;
331 
332     /**
333      * Makes a new {@code PropertyGetter} instance.
334      *
335      * @param method the source method which this getter is implementing.
336      * @param type the type that the getter returns. This is written to take imports into account,
337      *     so it might be {@code List<String>} for example. It is either identical to the type of
338      *     the corresponding getter in the {@code @AutoValue} class, or it is an optional wrapper,
339      *     like {@code Optional<List<String>>}.
340      * @param optional a representation of the {@code Optional} type that the getter returns, if
341      *     this is an optional getter, or null otherwise. An optional getter is one that returns
342      *     {@code Optional<T>} rather than {@code T}, as explained above.
343      */
PropertyGetter(ExecutableElement method, String type, Optionalish optional)344     PropertyGetter(ExecutableElement method, String type, Optionalish optional) {
345       this.access = SimpleMethod.access(method);
346       this.type = type;
347       this.optional = optional;
348     }
349 
getAccess()350     public String getAccess() {
351       return access;
352     }
353 
getType()354     public String getType() {
355       return type;
356     }
357 
getOptional()358     public Optionalish getOptional() {
359       return optional;
360     }
361   }
362 
363   /**
364    * Specifies how to copy a parameter value into the target type. This might be the identity, or
365    * it might be something like {@code ImmutableList.of(...)} or {@code Optional.ofNullable(...)}.
366    */
367   static class Copier {
368     static final Copier IDENTITY = acceptingNull(x -> x);
369 
370     private final Function<String, String> copy;
371     private final boolean acceptsNull;
372 
Copier(Function<String, String> copy, boolean acceptsNull)373     private Copier(Function<String, String> copy, boolean acceptsNull) {
374       this.copy = copy;
375       this.acceptsNull = acceptsNull;
376     }
377 
acceptingNull(Function<String, String> copy)378     static Copier acceptingNull(Function<String, String> copy) {
379       return new Copier(copy, true);
380     }
381 
notAcceptingNull(Function<String, String> copy)382     static Copier notAcceptingNull(Function<String, String> copy) {
383       return new Copier(copy, false);
384     }
385   }
386 
387   /**
388    * Information about a property setter, referenced from the autovalue.vm template. A property
389    * called foo (defined by a method {@code T foo()} or {@code T getFoo()}) can have a setter method
390    * {@code foo(T)} or {@code setFoo(T)} that returns the builder type. Additionally, it can have a
391    * setter with a type that can be copied to {@code T} through a {@code copyOf} method; for example
392    * a property {@code foo} of type {@code ImmutableSet<String>} can be set with a method {@code
393    * setFoo(Collection<String> foos)}. And, if {@code T} is {@code Optional}, it can have a setter
394    * with a type that can be copied to {@code T} through {@code Optional.of}.
395    */
396   public static class PropertySetter {
397     private final ExecutableElement setter;
398     private final String access;
399     private final String name;
400     private final String parameterTypeString;
401     private final boolean primitiveParameter;
402     private final String nullableAnnotation;
403     private final Copier copier;
404 
PropertySetter(ExecutableElement setter, TypeMirror parameterType, Copier copier)405     PropertySetter(ExecutableElement setter, TypeMirror parameterType, Copier copier) {
406       this.setter = setter;
407       this.copier = copier;
408       this.access = SimpleMethod.access(setter);
409       this.name = setter.getSimpleName().toString();
410       primitiveParameter = parameterType.getKind().isPrimitive();
411       this.parameterTypeString = parameterTypeString(setter, parameterType);
412       VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
413       Optional<String> maybeNullable = nullableAnnotationFor(parameterElement, parameterType);
414       this.nullableAnnotation = maybeNullable.orElse("");
415     }
416 
getSetter()417     ExecutableElement getSetter() {
418       return setter;
419     }
420 
parameterTypeString(ExecutableElement setter, TypeMirror parameterType)421     private static String parameterTypeString(ExecutableElement setter, TypeMirror parameterType) {
422       if (setter.isVarArgs()) {
423         TypeMirror componentType = MoreTypes.asArray(parameterType).getComponentType();
424         // This is a bit ugly. It's OK to annotate just the component type, because if it is
425         // say `@Nullable String` then we will end up with `@Nullable String...`. Unlike the
426         // normal array case, we can't have the situation where the array itself is annotated;
427         // you can write `String @Nullable []` to mean that, but you can't write
428         // `String @Nullable ...`.
429         return TypeEncoder.encodeWithAnnotations(componentType) + "...";
430       } else {
431         return TypeEncoder.encodeWithAnnotations(parameterType);
432       }
433     }
434 
getAccess()435     public String getAccess() {
436       return access;
437     }
438 
getName()439     public String getName() {
440       return name;
441     }
442 
getParameterType()443     public String getParameterType() {
444       return parameterTypeString;
445     }
446 
getPrimitiveParameter()447     public boolean getPrimitiveParameter() {
448       return primitiveParameter;
449     }
450 
getNullableAnnotation()451     public String getNullableAnnotation() {
452       return nullableAnnotation;
453     }
454 
copy(AutoValueProcessor.Property property)455     public String copy(AutoValueProcessor.Property property) {
456       String copy = copier.copy.apply(property.toString());
457       if (property.isNullable() && !copier.acceptsNull) {
458         copy = String.format("(%s == null ? null : %s)", property, copy);
459       }
460       return copy;
461     }
462   }
463 
464   /**
465    * Returns a representation of the given {@code @AutoValue.Builder} class or interface. If the
466    * class or interface has abstract methods that could not be part of any builder, emits error
467    * messages and returns Optional.empty().
468    */
builderFrom(TypeElement builderTypeElement)469   private Optional<Builder> builderFrom(TypeElement builderTypeElement) {
470 
471     // We require the builder to have the same type parameters as the @AutoValue class, meaning the
472     // same names and bounds. In principle the type parameters could have different names, but that
473     // would be confusing, and our code would reject it anyway because it wouldn't consider that
474     // the return type of Foo<U> build() was really the same as the declaration of Foo<T>. This
475     // check produces a better error message in that case and similar ones.
476 
477     if (!sameTypeParameters(autoValueClass, builderTypeElement)) {
478       errorReporter.reportError(
479           builderTypeElement,
480           "[AutoValueTypeParamMismatch] Type parameters of %s must have same names and bounds as"
481               + " type parameters of %s",
482           builderTypeElement,
483           autoValueClass);
484       return Optional.empty();
485     }
486     return Optional.of(new Builder(builderTypeElement));
487   }
488 
sameTypeParameters(TypeElement a, TypeElement b)489   private static boolean sameTypeParameters(TypeElement a, TypeElement b) {
490     int nTypeParameters = a.getTypeParameters().size();
491     if (nTypeParameters != b.getTypeParameters().size()) {
492       return false;
493     }
494     for (int i = 0; i < nTypeParameters; i++) {
495       TypeParameterElement aParam = a.getTypeParameters().get(i);
496       TypeParameterElement bParam = b.getTypeParameters().get(i);
497       if (!aParam.getSimpleName().equals(bParam.getSimpleName())) {
498         return false;
499       }
500       Set<TypeMirror> autoValueBounds = new TypeMirrorSet(aParam.getBounds());
501       Set<TypeMirror> builderBounds = new TypeMirrorSet(bParam.getBounds());
502       if (!autoValueBounds.equals(builderBounds)) {
503         return false;
504       }
505     }
506     return true;
507   }
508 
509   /**
510    * Returns a set of all abstract methods in the given TypeElement or inherited from ancestors. If
511    * any of the abstract methods has a return type or parameter type that is not currently defined
512    * then this method will throw an exception that will cause us to defer processing of the current
513    * class until a later annotation-processing round.
514    */
abstractMethods(TypeElement typeElement)515   private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) {
516     Set<ExecutableElement> methods =
517         getLocalAndInheritedMethods(
518             typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
519     ImmutableSet.Builder<ExecutableElement> abstractMethods = ImmutableSet.builder();
520     for (ExecutableElement method : methods) {
521       if (method.getModifiers().contains(Modifier.ABSTRACT)) {
522         MissingTypes.deferIfMissingTypesIn(method);
523         abstractMethods.add(method);
524       }
525     }
526     return abstractMethods.build();
527   }
528 
typeParamsString()529   private String typeParamsString() {
530     return TypeSimplifier.actualTypeParametersString(autoValueClass);
531   }
532 }
533