• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 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.value.processor.AutoValueishProcessor.nullableAnnotationFor;
19 
20 import com.google.auto.common.MoreElements;
21 import com.google.auto.common.MoreTypes;
22 import com.google.auto.value.processor.BuilderSpec.Copier;
23 import com.google.auto.value.processor.BuilderSpec.PropertySetter;
24 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
25 import com.google.common.base.Equivalence;
26 import com.google.common.collect.ImmutableBiMap;
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.ImmutableMultimap;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.collect.Iterables;
32 import com.google.common.collect.LinkedListMultimap;
33 import com.google.common.collect.Multimap;
34 import java.util.LinkedHashMap;
35 import java.util.LinkedHashSet;
36 import java.util.Map;
37 import java.util.Optional;
38 import java.util.Set;
39 import java.util.function.Function;
40 import java.util.stream.Stream;
41 import javax.annotation.processing.ProcessingEnvironment;
42 import javax.lang.model.element.Element;
43 import javax.lang.model.element.ExecutableElement;
44 import javax.lang.model.element.Modifier;
45 import javax.lang.model.element.TypeElement;
46 import javax.lang.model.element.VariableElement;
47 import javax.lang.model.type.DeclaredType;
48 import javax.lang.model.type.ExecutableType;
49 import javax.lang.model.type.TypeKind;
50 import javax.lang.model.type.TypeMirror;
51 import javax.lang.model.util.ElementFilter;
52 import javax.lang.model.util.Elements;
53 import javax.lang.model.util.Types;
54 
55 /**
56  * Classifies methods inside builder types, based on their names and parameter and return types.
57  *
58  * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is
59  *     {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods,
60  *     and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method
61  *     parameters.
62  * @author Éamonn McManus
63  */
64 abstract class BuilderMethodClassifier<E extends Element> {
65   private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence();
66 
67   private final ErrorReporter errorReporter;
68   private final Types typeUtils;
69   private final Elements elementUtils;
70   private final TypeMirror builtType;
71   private final TypeElement builderType;
72   private final ImmutableSet<String> propertiesWithDefaults;
73 
74   /**
75    * Property types, rewritten to refer to type variables in the builder. For example, suppose you
76    * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a
77    * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract
78    * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code
79    * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of
80    * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code
81    * Foo.Builder<T>}.
82    *
83    * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must
84    * be careful to look at the original type as reported by the {@link #originalPropertyType}
85    * method.
86    */
87   private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes;
88 
89   private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>();
90   private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>();
91   private final Map<String, PropertyBuilder> propertyNameToPropertyBuilder = new LinkedHashMap<>();
92   private final Multimap<String, PropertySetter> propertyNameToPrefixedSetters =
93       LinkedListMultimap.create();
94   private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters =
95       LinkedListMultimap.create();
96   private final EclipseHack eclipseHack;
97   private final Nullables nullables;
98 
99   private boolean settersPrefixed;
100 
BuilderMethodClassifier( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, TypeMirror builtType, TypeElement builderType, ImmutableMap<String, TypeMirror> rewrittenPropertyTypes, ImmutableSet<String> propertiesWithDefaults, Nullables nullables)101   BuilderMethodClassifier(
102       ErrorReporter errorReporter,
103       ProcessingEnvironment processingEnv,
104       TypeMirror builtType,
105       TypeElement builderType,
106       ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
107       ImmutableSet<String> propertiesWithDefaults,
108       Nullables nullables) {
109     this.errorReporter = errorReporter;
110     this.typeUtils = processingEnv.getTypeUtils();
111     this.elementUtils = processingEnv.getElementUtils();
112     this.builtType = builtType;
113     this.builderType = builderType;
114     this.rewrittenPropertyTypes = rewrittenPropertyTypes;
115     this.propertiesWithDefaults = propertiesWithDefaults;
116     this.eclipseHack = new EclipseHack(processingEnv);
117     this.nullables = nullables;
118   }
119 
120   /**
121    * Returns a multimap from the name of a property to the methods that set it. If the property is
122    * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code
123    * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map
124    * where the key is {@code "foo"} and the value describes a method in the builder called {@code
125    * foo} or {@code setFoo}.
126    */
propertyNameToSetters()127   ImmutableMultimap<String, PropertySetter> propertyNameToSetters() {
128     return ImmutableMultimap.copyOf(
129         settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters);
130   }
131 
propertyNameToPropertyBuilder()132   Map<String, PropertyBuilder> propertyNameToPropertyBuilder() {
133     return propertyNameToPropertyBuilder;
134   }
135 
136   /**
137    * Returns the set of properties that have getters in the builder. If a property is defined by an
138    * abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()} then
139    * the name of the property is {@code foo}, If the builder also has a method of the same name
140    * ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
141    */
builderGetters()142   ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters() {
143     return ImmutableMap.copyOf(builderGetters);
144   }
145 
146   /**
147    * Returns the methods that were identified as {@code build()} methods. These are methods that
148    * have no parameters and return the {@code @AutoValue} type, conventionally called {@code
149    * build()}.
150    */
buildMethods()151   Set<ExecutableElement> buildMethods() {
152     return ImmutableSet.copyOf(buildMethods);
153   }
154 
155   /** Classifies the given methods and sets the state of this object based on what is found. */
classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder)156   boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) {
157     int startErrorCount = errorReporter.errorCount();
158     for (ExecutableElement method : methods) {
159       classifyMethod(method);
160     }
161     if (errorReporter.errorCount() > startErrorCount) {
162       return false;
163     }
164     Multimap<String, PropertySetter> propertyNameToSetter;
165     if (propertyNameToPrefixedSetters.isEmpty()) {
166       propertyNameToSetter = propertyNameToUnprefixedSetters;
167       this.settersPrefixed = false;
168     } else if (propertyNameToUnprefixedSetters.isEmpty()) {
169       propertyNameToSetter = propertyNameToPrefixedSetters;
170       this.settersPrefixed = true;
171     } else {
172       errorReporter.reportError(
173           propertyNameToUnprefixedSetters.values().iterator().next().getSetter(),
174           "[%sSetNotSet] If any setter methods use the setFoo convention then all must",
175           autoWhat());
176       return false;
177     }
178     for (String property : rewrittenPropertyTypes.keySet()) {
179       TypeMirror propertyType = rewrittenPropertyTypes.get(property);
180       boolean hasSetter = propertyNameToSetter.containsKey(property);
181       PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
182       boolean hasBuilder = propertyBuilder != null;
183       if (hasBuilder) {
184         // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
185         // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
186         // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
187         // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
188         // method that accepts a Bar argument.
189         boolean canMakeBarBuilder =
190             (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null);
191         boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
192         if (needToMakeBarBuilder && !canMakeBarBuilder) {
193           errorReporter.reportError(
194               propertyBuilder.getPropertyBuilderMethod(),
195               "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no"
196                   + " way to make that type from %2$s: %2$s does not have a non-static"
197                   + " toBuilder() method that returns %1$s, and %1$s does not have a method"
198                   + " addAll or putAll that accepts an argument of type %2$s",
199               propertyBuilder.getBuilderTypeMirror(),
200               propertyType);
201         }
202       } else if (!hasSetter && !propertiesWithDefaults.contains(property)) {
203         // We have neither barBuilder() nor setBar(Bar), so we should complain.
204         String setterName = settersPrefixed ? prefixWithSet(property) : property;
205         errorReporter.reportError(
206             builderType,
207             "[%sBuilderMissingMethod] Expected a method with this signature: %s"
208                 + " %s(%s), or a %sBuilder() method",
209             autoWhat(),
210             builderType.asType(),
211             setterName,
212             propertyType,
213             property);
214       }
215     }
216     return errorReporter.errorCount() == startErrorCount;
217   }
218 
219   /** Classifies a method and update the state of this object based on what is found. */
classifyMethod(ExecutableElement method)220   private void classifyMethod(ExecutableElement method) {
221     switch (method.getParameters().size()) {
222       case 0:
223         classifyMethodNoArgs(method);
224         break;
225       case 1:
226         classifyMethodOneArg(method);
227         break;
228       default:
229         errorReporter.reportError(
230             method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat());
231     }
232   }
233 
234   /**
235    * Classifies a method given that it has no arguments. Currently a method with no arguments can be
236    * a {@code build()} method, meaning that its return type must be the {@code @AutoValue} class; it
237    * can be a getter, with the same signature as one of the property getters in the
238    * {@code @AutoValue} class; or it can be a property builder, like {@code
239    * ImmutableList.Builder<String> foosBuilder()} for the property defined by {@code
240    * ImmutableList<String> foos()} or {@code getFoos()}.
241    */
classifyMethodNoArgs(ExecutableElement method)242   private void classifyMethodNoArgs(ExecutableElement method) {
243     Optional<String> getterProperty = propertyForBuilderGetter(method);
244     if (getterProperty.isPresent()) {
245       classifyGetter(method, getterProperty.get());
246       return;
247     }
248 
249     String methodName = method.getSimpleName().toString();
250     TypeMirror returnType = builderMethodReturnType(method);
251 
252     if (methodName.endsWith("Builder")) {
253       String prefix = methodName.substring(0, methodName.length() - "Builder".length());
254       String property =
255           rewrittenPropertyTypes.containsKey(prefix)
256               ? prefix
257               : rewrittenPropertyTypes.keySet().stream()
258                   .filter(p -> PropertyNames.decapitalizeNormally(p).equals(prefix))
259                   .findFirst()
260                   .orElse(null);
261       if (property != null) {
262         PropertyBuilderClassifier propertyBuilderClassifier =
263             new PropertyBuilderClassifier(
264                 errorReporter,
265                 typeUtils,
266                 elementUtils,
267                 this,
268                 this::propertyIsNullable,
269                 rewrittenPropertyTypes,
270                 eclipseHack,
271                 nullables);
272         Optional<PropertyBuilder> propertyBuilder =
273             propertyBuilderClassifier.makePropertyBuilder(method, property);
274         if (propertyBuilder.isPresent()) {
275           propertyNameToPropertyBuilder.put(property, propertyBuilder.get());
276         }
277         return;
278       }
279     }
280 
281     if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) {
282       buildMethods.add(method);
283     } else {
284       errorReporter.reportError(
285           method,
286           "[%1$sBuilderNoArg] Method without arguments should be a build method returning"
287               + " %2$s, or a getter method with the same name and type as %3$s,"
288               + " or fooBuilder() where %4$s is %3$s",
289           // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..."
290           autoWhat(),
291           builtType,
292           getterMustMatch(),
293           fooBuilderMustMatch());
294     }
295   }
296 
classifyGetter(ExecutableElement builderGetter, String propertyName)297   private void classifyGetter(ExecutableElement builderGetter, String propertyName) {
298     TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName);
299     TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
300     String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType);
301     if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
302       builderGetters.put(
303           propertyName,
304           new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null));
305       return;
306     }
307     Optionalish optional = Optionalish.createIfOptional(builderGetterType);
308     if (optional != null) {
309       TypeMirror containedType = optional.getContainedType(typeUtils);
310       // If the original method is int getFoo() then we allow Optional<Integer> here.
311       // boxedOriginalType is Integer, and containedType is also Integer.
312       // We don't need any special code for OptionalInt because containedType will be int then.
313       TypeMirror boxedOriginalType =
314           originalGetterType.getKind().isPrimitive()
315               ? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType)).asType()
316               : null;
317       if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType)
318           || TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) {
319         builderGetters.put(
320             propertyName,
321             new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional));
322         return;
323       }
324     }
325     errorReporter.reportError(
326         builderGetter,
327         "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s"
328             + " instead of %3$s or an Optional wrapping of %3$s",
329         builtType,
330         builderGetterType,
331         originalGetterType);
332   }
333 
334   /**
335    * Classifies a method given that it has one argument. A method with one argument can be:
336    *
337    * <ul>
338    *   <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the
339    *       {@code AutoValue} class has a property called {@code foo} of type {@code T};
340    *   <li>a property builder with one argument, meaning it looks like {@code
341    *       ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue}
342    *       class has a property called {@code foos} with a type whose builder can be made with an
343    *       argument of the given type.
344    * </ul>
345    */
classifyMethodOneArg(ExecutableElement method)346   private void classifyMethodOneArg(ExecutableElement method) {
347     if (classifyPropertyBuilderOneArg(method)) {
348       return;
349     }
350     String methodName = method.getSimpleName().toString();
351     ImmutableMap<String, E> propertyElements = propertyElements();
352     String propertyName = null;
353     E propertyElement = propertyElements.get(methodName);
354     Multimap<String, PropertySetter> propertyNameToSetters = null;
355     if (propertyElement != null) {
356       propertyNameToSetters = propertyNameToUnprefixedSetters;
357       propertyName = methodName;
358     } else if (methodName.startsWith("set") && methodName.length() > 3) {
359       propertyNameToSetters = propertyNameToPrefixedSetters;
360       propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3));
361       propertyElement = propertyElements.get(propertyName);
362       if (propertyElement == null) {
363         // If our property is defined by a getter called getOAuth() then it is called "OAuth"
364         // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter
365         // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter
366         // called oAuth() then it is called "oAuth", but you would still expect to be able to set it
367         // using setOAuth(x). Hence the second try using a decapitalize method without the quirky
368         // two-leading-capitals rule.
369         propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3));
370         propertyElement = propertyElements.get(propertyName);
371       }
372     } else {
373       // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and
374       // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called
375       // OAuth(x). Iterating over the properties here is a bit clunky but this case should be
376       // unusual.
377       propertyNameToSetters = propertyNameToUnprefixedSetters;
378       for (Map.Entry<String, E> entry : propertyElements.entrySet()) {
379         if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) {
380           propertyName = entry.getKey();
381           propertyElement = entry.getValue();
382           break;
383         }
384       }
385     }
386     if (propertyElement == null || propertyNameToSetters == null) {
387       // The second disjunct isn't needed but convinces control-flow checkers that
388       // propertyNameToSetters can't be null when we call put on it below.
389       errorReporter.reportError(
390           method,
391           "[%sBuilderWhatProp] Method %s does not correspond to %s",
392           autoWhat(),
393           methodName,
394           getterMustMatch());
395       checkForFailedJavaBean(method);
396       return;
397     }
398     Optional<Copier> function = getSetterFunction(propertyElement, method);
399     if (function.isPresent()) {
400       DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
401       ExecutableType methodMirror =
402           MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method));
403       TypeMirror returnType = methodMirror.getReturnType();
404       if (typeUtils.isSubtype(builderType.asType(), returnType)
405           && !MoreTypes.isTypeOf(Object.class, returnType)) {
406         if (nullableAnnotationFor(method, returnType).isPresent()) {
407           errorReporter.
408               reportWarning(
409                   method,
410                   "[%sBuilderSetterNullable] Setter methods always return the Builder so @Nullable"
411                       + " is not appropriate", autoWhat());
412         }
413         // We allow the return type to be a supertype (other than Object), to support step builders.
414         TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes());
415         propertyNameToSetters.put(
416             propertyName, new PropertySetter(method, parameterType, function.get()));
417       } else {
418         errorReporter.reportError(
419             method,
420             "[%sBuilderRet] Setter methods must return %s or a supertype",
421             autoWhat(),
422             builderType.asType());
423       }
424     }
425   }
426 
427   /**
428    * Classifies a method given that it has one argument and is a property builder with a parameter,
429    * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}.
430    *
431    * @param method A method to classify
432    * @return true if method has been classified successfully
433    */
classifyPropertyBuilderOneArg(ExecutableElement method)434   private boolean classifyPropertyBuilderOneArg(ExecutableElement method) {
435     String methodName = method.getSimpleName().toString();
436     if (!methodName.endsWith("Builder")) {
437       return false;
438     }
439     String property = methodName.substring(0, methodName.length() - "Builder".length());
440     if (!rewrittenPropertyTypes.containsKey(property)) {
441       return false;
442     }
443     PropertyBuilderClassifier propertyBuilderClassifier =
444         new PropertyBuilderClassifier(
445             errorReporter,
446             typeUtils,
447             elementUtils,
448             this,
449             this::propertyIsNullable,
450             rewrittenPropertyTypes,
451             eclipseHack,
452             nullables);
453     Optional<PropertyBuilder> maybePropertyBuilder =
454         propertyBuilderClassifier.makePropertyBuilder(method, property);
455     maybePropertyBuilder.ifPresent(
456         propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder));
457     return maybePropertyBuilder.isPresent();
458   }
459 
460   /**
461    * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
462    * to the getter's return type, or {@code Optional.empty()} if the conversion isn't possible. An
463    * error will have been reported in the latter case. We can convert if they are already the same
464    * type, when the returned function will be the identity; or if the setter type can be copied
465    * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned
466    * function will be something like {@code s -> "Optional.of(" + s + ")"}.
467    */
getSetterFunction(E propertyElement, ExecutableElement setter)468   private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) {
469     VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
470     boolean nullableParameter =
471         nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent();
472     String property = propertyElements().inverse().get(propertyElement);
473     TypeMirror targetType = rewrittenPropertyTypes.get(property);
474     ExecutableType finalSetter =
475         MoreTypes.asExecutable(
476             typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter));
477     TypeMirror parameterType = finalSetter.getParameterTypes().get(0);
478     // Two types are assignable to each other if they are the same type, or if one is primitive and
479     // the other is the corresponding boxed type. There might be other cases where this is true, but
480     // we're likely to want to accept those too.
481     if (typeUtils.isAssignable(parameterType, targetType)
482         && typeUtils.isAssignable(targetType, parameterType)) {
483       if (nullableParameter) {
484         boolean nullableProperty =
485             nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement))
486                 .isPresent();
487         if (!nullableProperty) {
488           errorReporter.reportError(
489               setter,
490               "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not",
491               autoWhat(),
492               propertyString(propertyElement));
493           return Optional.empty();
494         }
495       }
496       return Optional.of(Copier.IDENTITY);
497     }
498 
499     // Parameter type is not equal to property type, but might be convertible with copyOf.
500     ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter);
501     if (!copyOfMethods.isEmpty()) {
502       return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType);
503     }
504     errorReporter.reportError(
505         setter,
506         "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s",
507         autoWhat(),
508         parameterType,
509         targetType,
510         propertyString(propertyElement));
511     return Optional.empty();
512   }
513 
514   /**
515    * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
516    * to the getter's return type using one of the given methods, or {@code Optional.empty()} if the
517    * conversion isn't possible. An error will have been reported in the latter case.
518    */
getConvertingSetterFunction( ImmutableList<ExecutableElement> copyOfMethods, E propertyElement, ExecutableElement setter, TypeMirror parameterType)519   private Optional<Copier> getConvertingSetterFunction(
520       ImmutableList<ExecutableElement> copyOfMethods,
521       E propertyElement,
522       ExecutableElement setter,
523       TypeMirror parameterType) {
524     String property = propertyElements().inverse().get(propertyElement);
525     DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property));
526     for (ExecutableElement copyOfMethod : copyOfMethods) {
527       Optional<Copier> function =
528           getConvertingSetterFunction(copyOfMethod, targetType, parameterType);
529       if (function.isPresent()) {
530         return function;
531       }
532     }
533     String targetTypeSimpleName = targetType.asElement().getSimpleName().toString();
534     errorReporter.reportError(
535         setter,
536         "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it"
537             + " should be a type that can be passed to %s.%s to produce %s",
538         autoWhat(),
539         parameterType,
540         targetType,
541         propertyString(propertyElement),
542         targetTypeSimpleName,
543         copyOfMethods.get(0).getSimpleName(),
544         targetType);
545     return Optional.empty();
546   }
547 
548   /**
549    * Returns an {@code Optional} containing a function to use {@code copyOfMethod} to copy the
550    * {@code parameterType} to the {@code targetType}, or {@code Optional.empty()} if the method
551    * can't be used. For example, we might have a property of type {@code ImmutableSet<T>} and our
552    * setter has a parameter of type {@code Set<? extends T>}. Can we use {@code ImmutableSet<E>
553    * ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about {@code
554    * ImmutableSet<E> ImmutableSet.copyOf(E[])}?
555    *
556    * <p>The example here is deliberately complicated, in that it has a type parameter of its own,
557    * presumably because the {@code @AutoValue} class is {@code Foo<T>}. One subtle point is that the
558    * builder will then be {@code Builder<T>} where this {@code T} is a <i>different</i> type
559    * variable. However, we've used {@link TypeVariables} to ensure that the {@code T} in {@code
560    * ImmutableSet<T>} is actually the one from {@code Builder<T>} instead of the original one from
561    * {@code Foo<T>}.}
562    *
563    * @param copyOfMethod the candidate method to do the copy, {@code
564    *     ImmutableSet.copyOf(Collection<? extends E>)} or {@code ImmutableSet.copyOf(E[])} in the
565    *     examples.
566    * @param targetType the type of the property to be set, {@code ImmutableSet<T>} in the example.
567    * @param parameterType the type of the setter parameter, {@code Set<? extends T>} in the example.
568    * @return a function that maps a string parameter to a method call using that parameter. For
569    *     example it might map {@code foo} to {@code ImmutableList.copyOf(foo)}.
570    */
getConvertingSetterFunction( ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType)571   private Optional<Copier> getConvertingSetterFunction(
572       ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType) {
573     // We have a parameter type, for example Set<? extends T>, and we want to know if it can be
574     // passed to the given copyOf method, which might for example be one of these methods from
575     // ImmutableSet:
576     //    public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements)
577     //    public static <E> ImmutableSet<E> copyOf(E[] elements)
578     // Additionally, if it can indeed be passed to the method, we want to know whether the result
579     // (here ImmutableSet<? extends T>) is compatible with the property to be set.
580     // We can't use Types.asMemberOf to do the substitution for us, because the methods in question
581     // are static. So even if our target type is ImmutableSet<String>, if we ask what the type of
582     // copyOf is in ImmutableSet<String> it will still tell us <T> Optional<T> (T).
583     // Instead, we do the variable substitutions ourselves.
584     if (TypeVariables.canAssignStaticMethodResult(
585         copyOfMethod, parameterType, targetType, typeUtils)) {
586       String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName();
587       Function<String, String> callMethod = s -> method + "(" + s + ")";
588       // This is a big old hack. We guess that the method can accept a null parameter if it has
589       // "Nullable" in the name, which java.util.Optional.ofNullable and
590       // com.google.common.base.Optional.fromNullable do.
591       Copier copier =
592           method.contains("Nullable")
593               ? Copier.acceptingNull(callMethod)
594               : Copier.notAcceptingNull(callMethod);
595       return Optional.of(copier);
596     }
597     return Optional.empty();
598   }
599 
600   /**
601    * Returns {@code copyOf} methods from the given type. These are static methods with a single
602    * parameter, called {@code copyOf} or {@code copyOfSorted} for Guava collection types, and called
603    * {@code of} or {@code ofNullable} for {@code Optional}. All of Guava's concrete immutable
604    * collection types have at least one such method, but we will also accept other classes with an
605    * appropriate {@code copyOf} method, such as {@link java.util.EnumSet}.
606    */
copyOfMethods( TypeMirror targetType, boolean nullableParameter)607   private ImmutableList<ExecutableElement> copyOfMethods(
608       TypeMirror targetType, boolean nullableParameter) {
609     if (!targetType.getKind().equals(TypeKind.DECLARED)) {
610       return ImmutableList.of();
611     }
612     ImmutableSet<String> copyOfNames;
613     Optionalish optionalish = Optionalish.createIfOptional(targetType);
614     if (optionalish == null) {
615       copyOfNames = ImmutableSet.of("copyOfSorted", "copyOf");
616     } else {
617       copyOfNames = ImmutableSet.of(nullableParameter ? optionalish.ofNullable() : "of");
618     }
619     TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType));
620     ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder();
621     for (String copyOfName : copyOfNames) {
622       for (ExecutableElement method :
623           ElementFilter.methodsIn(targetTypeElement.getEnclosedElements())) {
624         if (method.getSimpleName().contentEquals(copyOfName)
625             && method.getParameters().size() == 1
626             && method.getModifiers().contains(Modifier.STATIC)) {
627           copyOfMethods.add(method);
628         }
629       }
630     }
631     return copyOfMethods.build();
632   }
633 
634   /**
635    * Returns the return type of the given method from the builder. This should be the final type of
636    * the method when any bound type variables are substituted. Consider this example:
637    *
638    * <pre>{@code
639    * abstract static class ParentBuilder<B extends ParentBuilder> {
640    *   B setFoo(String s);
641    * }
642    * abstract static class ChildBuilder extends ParentBuilder<ChildBuilder> {
643    *   ...
644    * }
645    * }</pre>
646    *
647    * If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also {@code
648    * ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim.
649    *
650    * <p>If the caller is in a version of Eclipse with <a
651    * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this bug</a> then the {@code
652    * asMemberOf} call will fail if the method is inherited from an interface. We work around that
653    * for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we
654    * don't try to do so here because it should be much less likely. You might need to change {@code
655    * ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to
656    * do that anyway.
657    */
builderMethodReturnType(ExecutableElement builderMethod)658   TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
659     DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
660     TypeMirror methodMirror;
661     try {
662       methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
663     } catch (IllegalArgumentException e) {
664       // Presumably we've hit the Eclipse bug cited.
665       return builderMethod.getReturnType();
666     }
667     return MoreTypes.asExecutable(methodMirror).getReturnType();
668   }
669 
prefixWithSet(String propertyName)670   private static String prefixWithSet(String propertyName) {
671     // This is not internationalizationally correct, but it corresponds to what
672     // Introspector.decapitalize does.
673     return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
674   }
675 
676   /**
677    * True if the given property is nullable, either because its type has a {@code @Nullable} type
678    * annotation, or because its getter method has a {@code @Nullable} method annotation.
679    */
propertyIsNullable(String property)680   private boolean propertyIsNullable(String property) {
681     E propertyElement = propertyElements().get(property);
682     return Stream.of(propertyElement, originalPropertyType(propertyElement))
683         .flatMap(ac -> ac.getAnnotationMirrors().stream())
684         .map(a -> a.getAnnotationType().asElement().getSimpleName())
685         .anyMatch(n -> n.contentEquals("Nullable"));
686   }
687 
688   /**
689    * Returns a map from property names to the corresponding source program elements. For AutoValue,
690    * these elements are the abstract getter methods in the {@code @AutoValue} class. For
691    * AutoBuilder, they are the parameters of the constructor or method that the generated builder
692    * will call.
693    */
propertyElements()694   abstract ImmutableBiMap<String, E> propertyElements();
695 
696   /**
697    * Returns the property type as it appears on the original source program element. This can be
698    * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to
699    * type variables in the builder rather than in the original class. Also, {@link
700    * #rewrittenPropertyTypes} will not have type annotations even if they were present on the
701    * original element, so {@code originalPropertyType} is the right thing to use for those.
702    */
originalPropertyType(E propertyElement)703   abstract TypeMirror originalPropertyType(E propertyElement);
704 
705   /**
706    * A string identifying the given property element, which is a method for AutoValue or a parameter
707    * for AutoBuilder.
708    */
propertyString(E propertyElement)709   abstract String propertyString(E propertyElement);
710 
711   /**
712    * Returns the name of the property that the given no-arg builder method queries, if
713    * any. For example, if your {@code @AutoValue} class has a method {@code abstract String
714    * getBar()} then an abstract method in its builder with the same signature will query the {@code
715    * bar} property.
716    */
propertyForBuilderGetter(ExecutableElement method)717   abstract Optional<String> propertyForBuilderGetter(ExecutableElement method);
718 
719   /**
720    * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match
721    * anything, and emits a compiler Note if detected. A frequent source of problems is where the
722    * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers
723    * that they haven't been followed at all, so you might have a property called getFoo where you
724    * thought it was called just foo, and you might not understand why your setter called setFoo is
725    * rejected (it would have to be called setGetFoo).
726    *
727    * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The
728    * parameter names are unambiguously the same as the property names.
729    */
checkForFailedJavaBean(ExecutableElement rejectedSetter)730   abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter);
731 
732   /**
733    * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}.
734    */
autoWhat()735   abstract String autoWhat();
736 
737   /**
738    * A string describing what a builder getter must match: a property method for AutoValue, a
739    * parameter for AutoBuilder.
740    */
getterMustMatch()741   abstract String getterMustMatch();
742 
743   /**
744    * A string describing what a property builder for property {@code foo} must match, {@code foo()
745    * or getFoo()} for AutoValue, {@code foo} for AutoBuilder.
746    */
fooBuilderMustMatch()747   abstract String fooBuilderMustMatch();
748 }
749