• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 java.util.stream.Collectors.collectingAndThen;
19 import static java.util.stream.Collectors.joining;
20 import static java.util.stream.Collectors.toMap;
21 
22 import com.google.auto.common.MoreElements;
23 import com.google.auto.common.MoreTypes;
24 import com.google.common.collect.ImmutableMap;
25 import com.google.common.collect.ImmutableSet;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.function.Function;
30 import java.util.function.Predicate;
31 import javax.lang.model.element.ElementKind;
32 import javax.lang.model.element.ExecutableElement;
33 import javax.lang.model.element.Modifier;
34 import javax.lang.model.element.TypeElement;
35 import javax.lang.model.type.DeclaredType;
36 import javax.lang.model.type.ExecutableType;
37 import javax.lang.model.type.TypeKind;
38 import javax.lang.model.type.TypeMirror;
39 import javax.lang.model.util.ElementFilter;
40 import javax.lang.model.util.Elements;
41 import javax.lang.model.util.Types;
42 
43 /**
44  * Classifies methods inside builder types that return builders for properties. For example, if
45  * {@code @AutoValue} class Foo has a method {@code ImmutableList<String> bar()} then Foo.Builder
46  * can have a method {@code ImmutableList.Builder<String> barBuilder()}. This class checks that a
47  * method like {@code barBuilder()} follows the rules, and if so constructs a {@link
48  * PropertyBuilder} instance with information about {@code barBuilder}.
49  *
50  * @author Éamonn McManus
51  */
52 class PropertyBuilderClassifier {
53   private final ErrorReporter errorReporter;
54   private final Types typeUtils;
55   private final Elements elementUtils;
56   private final BuilderMethodClassifier<?> builderMethodClassifier;
57   private final Predicate<String> propertyIsNullable;
58   private final ImmutableMap<String, TypeMirror> propertyTypes;
59   private final EclipseHack eclipseHack;
60 
PropertyBuilderClassifier( ErrorReporter errorReporter, Types typeUtils, Elements elementUtils, BuilderMethodClassifier<?> builderMethodClassifier, Predicate<String> propertyIsNullable, ImmutableMap<String, TypeMirror> propertyTypes, EclipseHack eclipseHack)61   PropertyBuilderClassifier(
62       ErrorReporter errorReporter,
63       Types typeUtils,
64       Elements elementUtils,
65       BuilderMethodClassifier<?> builderMethodClassifier,
66       Predicate<String> propertyIsNullable,
67       ImmutableMap<String, TypeMirror> propertyTypes,
68       EclipseHack eclipseHack) {
69     this.errorReporter = errorReporter;
70     this.typeUtils = typeUtils;
71     this.elementUtils = elementUtils;
72     this.builderMethodClassifier = builderMethodClassifier;
73     this.propertyIsNullable = propertyIsNullable;
74     this.propertyTypes = propertyTypes;
75     this.eclipseHack = eclipseHack;
76   }
77 
78   /**
79    * Information about a property builder, referenced from the autovalue.vm template. A property
80    * called bar (defined by a method bar() or getBar()) can have a property builder called
81    * barBuilder(). For example, if {@code bar()} returns {@code ImmutableSet<String>} then {@code
82    * barBuilder()} might return {@code ImmutableSet.Builder<String>}.
83    */
84   public static class PropertyBuilder {
85     private final ExecutableElement propertyBuilderMethod;
86     private final String name;
87     private final String builderType;
88     private final TypeMirror builderTypeMirror;
89     private final String initializer;
90     private final String beforeInitDefault;
91     private final String initDefault;
92     private final String builtToBuilder;
93     private final String copyAll;
94 
PropertyBuilder( ExecutableElement propertyBuilderMethod, String builderType, TypeMirror builderTypeMirror, String initializer, String beforeInitDefault, String initDefault, String builtToBuilder, String copyAll)95     PropertyBuilder(
96         ExecutableElement propertyBuilderMethod,
97         String builderType,
98         TypeMirror builderTypeMirror,
99         String initializer,
100         String beforeInitDefault,
101         String initDefault,
102         String builtToBuilder,
103         String copyAll) {
104       this.propertyBuilderMethod = propertyBuilderMethod;
105       this.name = propertyBuilderMethod.getSimpleName() + "$";
106       this.builderType = builderType;
107       this.builderTypeMirror = builderTypeMirror;
108       this.initializer = initializer;
109       this.beforeInitDefault = beforeInitDefault;
110       this.initDefault = initDefault;
111       this.builtToBuilder = builtToBuilder;
112       this.copyAll = copyAll;
113     }
114 
115     /** The property builder method, for example {@code barBuilder()}. */
getPropertyBuilderMethod()116     public ExecutableElement getPropertyBuilderMethod() {
117       return propertyBuilderMethod;
118     }
119 
120     /** The property builder method parameters, for example {@code Comparator<T> comparator} */
getPropertyBuilderMethodParameters()121     public String getPropertyBuilderMethodParameters() {
122       return propertyBuilderMethod.getParameters().stream()
123           .map(
124               parameter -> TypeEncoder.encode(parameter.asType()) + " " + parameter.getSimpleName())
125           .collect(joining(", "));
126     }
127 
getAccess()128     public String getAccess() {
129       return SimpleMethod.access(propertyBuilderMethod);
130     }
131 
132     /** The name of the field to hold this builder. */
getName()133     public String getName() {
134       return name;
135     }
136 
137     /** The type of the builder, for example {@code ImmutableSet.Builder<String>}. */
getBuilderType()138     public String getBuilderType() {
139       return builderType;
140     }
141 
getBuilderTypeMirror()142     TypeMirror getBuilderTypeMirror() {
143       return builderTypeMirror;
144     }
145 
146     /** An initializer for the builder field, for example {@code ImmutableSet.builder()}. */
getInitializer()147     public String getInitializer() {
148       return initializer;
149     }
150 
151     /**
152      * An empty string, or a complete statement to be included before the expression returned by
153      * {@link #getInitDefault()}.
154      */
getBeforeInitDefault()155     public String getBeforeInitDefault() {
156       return beforeInitDefault;
157     }
158 
159     /**
160      * An expression to return a default instance of the type that this builder builds. For example,
161      * if this is an {@code ImmutableList<String>} then the method {@code ImmutableList.of()} will
162      * correctly return an empty {@code ImmutableList<String>}, assuming the appropriate context for
163      * type inference. The expression here can assume that the statement from {@link
164      * #getBeforeInitDefault} has preceded it.
165      */
getInitDefault()166     public String getInitDefault() {
167       return initDefault;
168     }
169 
170     /**
171      * A method to convert the built type back into a builder. Unfortunately Guava collections don't
172      * have this (you can't say {@code myImmutableMap.toBuilder()}), but for other types such as
173      * {@code @AutoValue} types this is {@code toBuilder()}.
174      */
getBuiltToBuilder()175     public String getBuiltToBuilder() {
176       return builtToBuilder;
177     }
178 
179     /**
180      * The method to copy another collection into this builder. It is {@code addAll} for
181      * one-dimensional collections like {@code ImmutableList} and {@code ImmutableSet}, and it is
182      * {@code putAll} for two-dimensional collections like {@code ImmutableMap} and {@code
183      * ImmutableTable}.
184      */
getCopyAll()185     public String getCopyAll() {
186       return copyAll;
187     }
188   }
189 
190   // Our @AutoValue class `Foo` has a property `Bar bar()` or `Bar getBar()` and we've encountered
191   // a builder method like `BarBuilder barBuilder()`. Here `BarBuilder` can have any name (its name
192   // doesn't have to be the name of `Bar` with `Builder` stuck on the end), but `barBuilder()` does
193   // have to be the name of the property with `Builder` stuck on the end. The requirements for the
194   // `BarBuilder` type are:
195   // (1) It must have an instance method called `build()` that returns `Bar`. If the type of
196   //     `bar()` is `Bar<String>` then the type of `build()` must be `Bar<String>`.
197   // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a static method
198   //     `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`. The
199   //     `naturalOrder()` case is specifically for ImmutableSortedSet and ImmutableSortedMap.
200   // (3) If `Foo` has a `toBuilder()` method, or if we have both `barBuilder()` and `setBar(Bar)`
201   //     methods, then `Bar` must have an instance method `BarBuilder toBuilder()`, or `BarBuilder`
202   //     must have an `addAll` or `putAll` method that accepts an argument of type `Bar`.
203   //
204   // This method outputs an error and returns Optional.empty() if the barBuilder() method has a
205   // problem.
makePropertyBuilder(ExecutableElement method, String property)206   Optional<PropertyBuilder> makePropertyBuilder(ExecutableElement method, String property) {
207     TypeMirror barBuilderTypeMirror = builderMethodClassifier.builderMethodReturnType(method);
208     if (barBuilderTypeMirror.getKind() != TypeKind.DECLARED) {
209       errorReporter.reportError(
210           method,
211           "[AutoValueOddBuilderMethod] Method looks like a property builder, but its return type"
212               + " is not a class or interface");
213       return Optional.empty();
214     }
215     DeclaredType barBuilderDeclaredType = MoreTypes.asDeclared(barBuilderTypeMirror);
216     TypeElement barBuilderTypeElement = MoreTypes.asTypeElement(barBuilderTypeMirror);
217     Map<String, ExecutableElement> barBuilderNoArgMethods = noArgMethodsOf(barBuilderTypeElement);
218 
219     TypeMirror barTypeMirror = propertyTypes.get(property);
220     if (barTypeMirror.getKind() != TypeKind.DECLARED) {
221       errorReporter.reportError(
222           method,
223           "[AutoValueBadBuilderMethod] Method looks like a property builder, but the type of"
224               + " property %s is not a class or interface",
225           property);
226       return Optional.empty();
227     }
228     if (propertyIsNullable.test(property)) {
229       errorReporter.reportError(
230           method,
231           "[AutoValueNullBuilder] Property %s is @Nullable so it cannot have a property builder",
232           property);
233     }
234     TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror);
235     Map<String, ExecutableElement> barNoArgMethods = noArgMethodsOf(barTypeElement);
236 
237     // Condition (1), must have build() method returning Bar.
238     ExecutableElement build = barBuilderNoArgMethods.get("build");
239     if (build == null || build.getModifiers().contains(Modifier.STATIC)) {
240       errorReporter.reportError(
241           method,
242           "[AutoValueBuilderNotBuildable] Method looks like a property builder, but it returns %s"
243               + " which does not have a non-static build() method",
244           barBuilderTypeElement);
245       return Optional.empty();
246     }
247 
248     // We've determined that `BarBuilder` has a method `build()`. But it must return `Bar`.
249     // And if the type of `bar()` is Bar<String> then `BarBuilder.build()` must return Bar<String>.
250     TypeMirror buildType = eclipseHack.methodReturnType(build, barBuilderDeclaredType);
251     if (!MoreTypes.equivalence().equivalent(barTypeMirror, buildType)) {
252       errorReporter.reportError(
253           method,
254           "[AutoValueBuilderWrongType] Property builder for %s has type %s whose build() method"
255               + " returns %s instead of %s",
256           property,
257           barBuilderTypeElement,
258           buildType,
259           barTypeMirror);
260       return Optional.empty();
261     }
262 
263     Optional<ExecutableElement> maybeBuilderMaker;
264     if (method.getParameters().isEmpty()) {
265       maybeBuilderMaker = noArgBuilderMaker(barNoArgMethods, barBuilderTypeElement);
266     } else {
267       Map<String, ExecutableElement> barOneArgMethods = oneArgumentMethodsOf(barTypeElement);
268       maybeBuilderMaker = oneArgBuilderMaker(barOneArgMethods, barBuilderTypeElement);
269     }
270     if (!maybeBuilderMaker.isPresent()) {
271       errorReporter.reportError(
272           method,
273           "[AutoValueCantMakePropertyBuilder] Method looks like a property builder, but its type"
274               + " %s does not have a public constructor and %s does not have a static builder() or"
275               + " newBuilder() method that returns %s",
276           barBuilderTypeElement,
277           barTypeElement,
278           barBuilderTypeElement);
279       return Optional.empty();
280     }
281     ExecutableElement builderMaker = maybeBuilderMaker.get();
282 
283     String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror);
284     String rawBarType = TypeEncoder.encodeRaw(barTypeMirror);
285     String arguments =
286         method.getParameters().isEmpty()
287             ? "()"
288             : "(" + method.getParameters().get(0).getSimpleName() + ")";
289     String initializer =
290         (builderMaker.getKind() == ElementKind.CONSTRUCTOR)
291             ? "new " + barBuilderType + arguments
292             : rawBarType + "." + builderMaker.getSimpleName() + arguments;
293     String builtToBuilder = null;
294     String copyAll = null;
295     ExecutableElement toBuilder = barNoArgMethods.get("toBuilder");
296     if (toBuilder != null
297         && !toBuilder.getModifiers().contains(Modifier.STATIC)
298         && typeUtils.isAssignable(
299             typeUtils.erasure(toBuilder.getReturnType()),
300             typeUtils.erasure(barBuilderTypeMirror))) {
301       builtToBuilder = toBuilder.getSimpleName().toString();
302     } else {
303       Optional<ExecutableElement> maybeCopyAll =
304           addAllPutAll(barBuilderTypeElement, barBuilderDeclaredType, barTypeMirror);
305       if (maybeCopyAll.isPresent()) {
306         copyAll = maybeCopyAll.get().getSimpleName().toString();
307       }
308     }
309     ExecutableElement barOf = barNoArgMethods.get("of");
310     boolean hasOf = (barOf != null && barOf.getModifiers().contains(Modifier.STATIC));
311     // An expression (initDefault) to make a default one of these, plus optionally a statement
312     // (beforeInitDefault) that prepares the expression. For a collection, beforeInitDefault is
313     // empty and initDefault is (e.g.) `ImmutableList.of()`. For a nested value type,
314     // beforeInitDefault is (e.g.)
315     //   `NestedAutoValueType.Builder foo$builder = NestedAutoValueType.builder();`
316     // and initDefault is `foo$builder.build();`. The reason for the separate statement is to
317     // exploit type inference rather than having to write `NestedAutoValueType.<Bar>build();`.
318     String beforeInitDefault;
319     String initDefault;
320     if (hasOf) {
321       beforeInitDefault = "";
322       initDefault = rawBarType + ".of()";
323     } else {
324       String localBuilder = property + "$builder";
325       beforeInitDefault = barBuilderType + " " + localBuilder + " = " + initializer + ";";
326       initDefault = localBuilder + ".build()";
327     }
328 
329     PropertyBuilder propertyBuilder =
330         new PropertyBuilder(
331             method,
332             barBuilderType,
333             barBuilderTypeMirror,
334             initializer,
335             beforeInitDefault,
336             initDefault,
337             builtToBuilder,
338             copyAll);
339     return Optional.of(propertyBuilder);
340   }
341 
342   private static final ImmutableSet<String> BUILDER_METHOD_NAMES =
343       ImmutableSet.of("naturalOrder", "builder", "newBuilder");
344 
345   // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a visible static
346   //      method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`; or,
347   //      if we have a foosBuilder(T) method, then `BarBuilder` must have a public constructor with
348   //      a single parameter assignable from T, or a visible static `builder(T)` method.
noArgBuilderMaker( Map<String, ExecutableElement> barNoArgMethods, TypeElement barBuilderTypeElement)349   private Optional<ExecutableElement> noArgBuilderMaker(
350       Map<String, ExecutableElement> barNoArgMethods, TypeElement barBuilderTypeElement) {
351     return builderMaker(BUILDER_METHOD_NAMES, barNoArgMethods, barBuilderTypeElement, 0);
352   }
353 
354   private static final ImmutableSet<String> ONE_ARGUMENT_BUILDER_METHOD_NAMES =
355       ImmutableSet.of("builder");
356 
oneArgBuilderMaker( Map<String, ExecutableElement> barOneArgMethods, TypeElement barBuilderTypeElement)357   private Optional<ExecutableElement> oneArgBuilderMaker(
358       Map<String, ExecutableElement> barOneArgMethods, TypeElement barBuilderTypeElement) {
359 
360     return builderMaker(
361         ONE_ARGUMENT_BUILDER_METHOD_NAMES, barOneArgMethods, barBuilderTypeElement, 1);
362   }
363 
builderMaker( ImmutableSet<String> methodNamesToCheck, Map<String, ExecutableElement> methods, TypeElement barBuilderTypeElement, int argumentCount)364   private Optional<ExecutableElement> builderMaker(
365       ImmutableSet<String> methodNamesToCheck,
366       Map<String, ExecutableElement> methods,
367       TypeElement barBuilderTypeElement,
368       int argumentCount) {
369     Optional<ExecutableElement> maybeMethod =
370         methodNamesToCheck.stream()
371             .map(methods::get)
372             .filter(Objects::nonNull)
373             .filter(method -> method.getModifiers().contains(Modifier.STATIC))
374             .filter(
375                 method ->
376                     typeUtils.isSameType(
377                         typeUtils.erasure(method.getReturnType()),
378                         typeUtils.erasure(barBuilderTypeElement.asType())))
379             .findFirst();
380 
381     if (maybeMethod.isPresent()) {
382       // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue
383       // builders. By not checking visibility we risk accepting something as a builder maker
384       // and then failing when the generated code tries to call Bar.builder(). But the risk
385       // seems small.
386       return maybeMethod;
387     }
388 
389     return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements()).stream()
390         .filter(c -> c.getParameters().size() == argumentCount)
391         .filter(c -> c.getModifiers().contains(Modifier.PUBLIC))
392         .findFirst();
393   }
394 
noArgMethodsOf(TypeElement type)395   private Map<String, ExecutableElement> noArgMethodsOf(TypeElement type) {
396     return methodsOf(type, 0);
397   }
398 
oneArgumentMethodsOf(TypeElement type)399   private ImmutableMap<String, ExecutableElement> oneArgumentMethodsOf(TypeElement type) {
400     return methodsOf(type, 1);
401   }
402 
methodsOf(TypeElement type, int argumentCount)403   private ImmutableMap<String, ExecutableElement> methodsOf(TypeElement type, int argumentCount) {
404     return ElementFilter.methodsIn(elementUtils.getAllMembers(type)).stream()
405         .filter(method -> method.getParameters().size() == argumentCount)
406         .filter(method -> !isStaticInterfaceMethodNotIn(method, type))
407         .collect(
408             collectingAndThen(
409                 toMap(
410                     method -> method.getSimpleName().toString(),
411                     Function.identity(),
412                     (method1, method2) -> method1),
413                 ImmutableMap::copyOf));
414   }
415 
416   // Work around an Eclipse compiler bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=547185
417   // The result of Elements.getAllMembers includes static methods declared in superinterfaces.
418   // That's wrong because those aren't inherited. So this method checks whether the given method is
419   // a static interface method not in the given type.
isStaticInterfaceMethodNotIn(ExecutableElement method, TypeElement type)420   private static boolean isStaticInterfaceMethodNotIn(ExecutableElement method, TypeElement type) {
421     return method.getModifiers().contains(Modifier.STATIC)
422         && !method.getEnclosingElement().equals(type)
423         && method.getEnclosingElement().getKind().equals(ElementKind.INTERFACE);
424   }
425 
426   private static final ImmutableSet<String> ADD_ALL_PUT_ALL = ImmutableSet.of("addAll", "putAll");
427 
428   // We have `Bar bar()` and `Foo.Builder toBuilder()` in the @AutoValue type Foo, and we have
429   // `BarBuilder barBuilder()` in Foo.Builder. That means that we need to be able to make a
430   // `BarBuilder` from a `Bar` as part of the implementation of `Foo.toBuilder()`. We can do that
431   // if `Bar` has a method `BarBuilder toBuilder()`, but what if it doesn't? For example, Guava's
432   // `ImmutableList` doesn't have a method `ImmutableList.Builder toBuilder()`. So we also allow it
433   // to work if `BarBuilder` has a method `addAll(T)` or `putAll(T)`, where `Bar` is assignable to
434   // `T`. `ImmutableList.Builder<E>` does have a method `addAll(Iterable<? extends E>)` and
435   // `ImmutableList<E>` is assignable to `Iterable<? extends E>`, so that works.
addAllPutAll( TypeElement barBuilderTypeElement, DeclaredType barBuilderDeclaredType, TypeMirror barTypeMirror)436   private Optional<ExecutableElement> addAllPutAll(
437       TypeElement barBuilderTypeElement,
438       DeclaredType barBuilderDeclaredType,
439       TypeMirror barTypeMirror) {
440     return MoreElements.getLocalAndInheritedMethods(barBuilderTypeElement, typeUtils, elementUtils)
441         .stream()
442         .filter(
443             method ->
444                 ADD_ALL_PUT_ALL.contains(method.getSimpleName().toString())
445                     && method.getParameters().size() == 1)
446         .filter(
447             method -> {
448               ExecutableType methodMirror =
449                   MoreTypes.asExecutable(typeUtils.asMemberOf(barBuilderDeclaredType, method));
450               return typeUtils.isAssignable(barTypeMirror, methodMirror.getParameterTypes().get(0));
451             })
452         .findFirst();
453   }
454 }
455