aboutsummaryrefslogtreecommitdiff
path: root/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
blob: 591b0cc8ccce64c96669ec5814d3eb8cb39812e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
/*
 * Copyright 2015 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.value.processor;

import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.processor.BuilderSpec.Copier;
import com.google.auto.value.processor.BuilderSpec.PropertySetter;
import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
 * Classifies methods inside builder types, based on their names and parameter and return types.
 *
 * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is
 *     {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods,
 *     and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method
 *     parameters.
 * @author Éamonn McManus
 */
abstract class BuilderMethodClassifier<E extends Element> {
  private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence();

  private final ErrorReporter errorReporter;
  private final Types typeUtils;
  private final Elements elementUtils;
  private final TypeMirror builtType;
  private final TypeElement builderType;
  private final ImmutableSet<String> propertiesWithDefaults;

  /**
   * Property types, rewritten to refer to type variables in the builder. For example, suppose you
   * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a
   * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract
   * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code
   * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of
   * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code
   * Foo.Builder<T>}.
   *
   * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must
   * be careful to look at the original type as reported by the {@link #originalPropertyType}
   * method.
   */
  private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes;

  private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>();
  private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>();
  private final Map<String, PropertyBuilder> propertyNameToPropertyBuilder = new LinkedHashMap<>();
  private final Multimap<String, PropertySetter> propertyNameToPrefixedSetters =
      LinkedListMultimap.create();
  private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters =
      LinkedListMultimap.create();
  private final EclipseHack eclipseHack;
  private final Nullables nullables;

  private boolean settersPrefixed;

  BuilderMethodClassifier(
      ErrorReporter errorReporter,
      ProcessingEnvironment processingEnv,
      TypeMirror builtType,
      TypeElement builderType,
      ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
      ImmutableSet<String> propertiesWithDefaults,
      Nullables nullables) {
    this.errorReporter = errorReporter;
    this.typeUtils = processingEnv.getTypeUtils();
    this.elementUtils = processingEnv.getElementUtils();
    this.builtType = builtType;
    this.builderType = builderType;
    this.rewrittenPropertyTypes = rewrittenPropertyTypes;
    this.propertiesWithDefaults = propertiesWithDefaults;
    this.eclipseHack = new EclipseHack(processingEnv);
    this.nullables = nullables;
  }

  /**
   * Returns a multimap from the name of a property to the methods that set it. If the property is
   * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code
   * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map
   * where the key is {@code "foo"} and the value describes a method in the builder called {@code
   * foo} or {@code setFoo}.
   */
  ImmutableMultimap<String, PropertySetter> propertyNameToSetters() {
    return ImmutableMultimap.copyOf(
        settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters);
  }

  Map<String, PropertyBuilder> propertyNameToPropertyBuilder() {
    return propertyNameToPropertyBuilder;
  }

  /**
   * Returns the set of properties that have getters in the builder. If a property is defined by an
   * abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()} then
   * the name of the property is {@code foo}, If the builder also has a method of the same name
   * ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}.
   */
  ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters() {
    return ImmutableMap.copyOf(builderGetters);
  }

  /**
   * Returns the methods that were identified as {@code build()} methods. These are methods that
   * have no parameters and return the {@code @AutoValue} type, conventionally called {@code
   * build()}.
   */
  Set<ExecutableElement> buildMethods() {
    return ImmutableSet.copyOf(buildMethods);
  }

  /** Classifies the given methods and sets the state of this object based on what is found. */
  boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) {
    int startErrorCount = errorReporter.errorCount();
    for (ExecutableElement method : methods) {
      classifyMethod(method);
    }
    if (errorReporter.errorCount() > startErrorCount) {
      return false;
    }
    Multimap<String, PropertySetter> propertyNameToSetter;
    if (propertyNameToPrefixedSetters.isEmpty()) {
      propertyNameToSetter = propertyNameToUnprefixedSetters;
      this.settersPrefixed = false;
    } else if (propertyNameToUnprefixedSetters.isEmpty()) {
      propertyNameToSetter = propertyNameToPrefixedSetters;
      this.settersPrefixed = true;
    } else {
      errorReporter.reportError(
          propertyNameToUnprefixedSetters.values().iterator().next().getSetter(),
          "[%sSetNotSet] If any setter methods use the setFoo convention then all must",
          autoWhat());
      return false;
    }
    for (String property : rewrittenPropertyTypes.keySet()) {
      TypeMirror propertyType = rewrittenPropertyTypes.get(property);
      boolean hasSetter = propertyNameToSetter.containsKey(property);
      PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
      boolean hasBuilder = propertyBuilder != null;
      if (hasBuilder) {
        // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
        // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
        // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
        // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
        // method that accepts a Bar argument.
        boolean canMakeBarBuilder =
            (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null);
        boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
        if (needToMakeBarBuilder && !canMakeBarBuilder) {
          errorReporter.reportError(
              propertyBuilder.getPropertyBuilderMethod(),
              "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no"
                  + " way to make that type from %2$s: %2$s does not have a non-static"
                  + " toBuilder() method that returns %1$s, and %1$s does not have a method"
                  + " addAll or putAll that accepts an argument of type %2$s",
              propertyBuilder.getBuilderTypeMirror(),
              propertyType);
        }
      } else if (!hasSetter && !propertiesWithDefaults.contains(property)) {
        // We have neither barBuilder() nor setBar(Bar), so we should complain.
        String setterName = settersPrefixed ? prefixWithSet(property) : property;
        errorReporter.reportError(
            builderType,
            "[%sBuilderMissingMethod] Expected a method with this signature: %s"
                + " %s(%s), or a %sBuilder() method",
            autoWhat(),
            builderType.asType(),
            setterName,
            propertyType,
            property);
      }
    }
    return errorReporter.errorCount() == startErrorCount;
  }

  /** Classifies a method and update the state of this object based on what is found. */
  private void classifyMethod(ExecutableElement method) {
    switch (method.getParameters().size()) {
      case 0:
        classifyMethodNoArgs(method);
        break;
      case 1:
        classifyMethodOneArg(method);
        break;
      default:
        errorReporter.reportError(
            method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat());
    }
  }

  /**
   * Classifies a method given that it has no arguments. Currently a method with no arguments can be
   * a {@code build()} method, meaning that its return type must be the {@code @AutoValue} class; it
   * can be a getter, with the same signature as one of the property getters in the
   * {@code @AutoValue} class; or it can be a property builder, like {@code
   * ImmutableList.Builder<String> foosBuilder()} for the property defined by {@code
   * ImmutableList<String> foos()} or {@code getFoos()}.
   */
  private void classifyMethodNoArgs(ExecutableElement method) {
    Optional<String> getterProperty = propertyForBuilderGetter(method);
    if (getterProperty.isPresent()) {
      classifyGetter(method, getterProperty.get());
      return;
    }

    String methodName = method.getSimpleName().toString();
    TypeMirror returnType = builderMethodReturnType(method);

    if (methodName.endsWith("Builder")) {
      String prefix = methodName.substring(0, methodName.length() - "Builder".length());
      String property =
          rewrittenPropertyTypes.containsKey(prefix)
              ? prefix
              : rewrittenPropertyTypes.keySet().stream()
                  .filter(p -> PropertyNames.decapitalizeNormally(p).equals(prefix))
                  .findFirst()
                  .orElse(null);
      if (property != null) {
        PropertyBuilderClassifier propertyBuilderClassifier =
            new PropertyBuilderClassifier(
                errorReporter,
                typeUtils,
                elementUtils,
                this,
                this::propertyIsNullable,
                rewrittenPropertyTypes,
                eclipseHack,
                nullables);
        Optional<PropertyBuilder> propertyBuilder =
            propertyBuilderClassifier.makePropertyBuilder(method, property);
        if (propertyBuilder.isPresent()) {
          propertyNameToPropertyBuilder.put(property, propertyBuilder.get());
        }
        return;
      }
    }

    if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) {
      buildMethods.add(method);
    } else {
      errorReporter.reportError(
          method,
          "[%1$sBuilderNoArg] Method without arguments should be a build method returning"
              + " %2$s, or a getter method with the same name and type as %3$s,"
              + " or fooBuilder() where %4$s is %3$s",
          // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..."
          autoWhat(),
          builtType,
          getterMustMatch(),
          fooBuilderMustMatch());
    }
  }

  private void classifyGetter(ExecutableElement builderGetter, String propertyName) {
    TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName);
    TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
    String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType);
    if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
      builderGetters.put(
          propertyName,
          new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null));
      return;
    }
    Optionalish optional = Optionalish.createIfOptional(builderGetterType);
    if (optional != null) {
      TypeMirror containedType = optional.getContainedType(typeUtils);
      // If the original method is int getFoo() then we allow Optional<Integer> here.
      // boxedOriginalType is Integer, and containedType is also Integer.
      // We don't need any special code for OptionalInt because containedType will be int then.
      TypeMirror boxedOriginalType =
          originalGetterType.getKind().isPrimitive()
              ? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType)).asType()
              : null;
      if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType)
          || TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) {
        builderGetters.put(
            propertyName,
            new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional));
        return;
      }
    }
    errorReporter.reportError(
        builderGetter,
        "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s"
            + " instead of %3$s or an Optional wrapping of %3$s",
        builtType,
        builderGetterType,
        originalGetterType);
  }

  /**
   * Classifies a method given that it has one argument. A method with one argument can be:
   *
   * <ul>
   *   <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the
   *       {@code AutoValue} class has a property called {@code foo} of type {@code T};
   *   <li>a property builder with one argument, meaning it looks like {@code
   *       ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue}
   *       class has a property called {@code foos} with a type whose builder can be made with an
   *       argument of the given type.
   * </ul>
   */
  private void classifyMethodOneArg(ExecutableElement method) {
    if (classifyPropertyBuilderOneArg(method)) {
      return;
    }
    String methodName = method.getSimpleName().toString();
    ImmutableMap<String, E> propertyElements = propertyElements();
    String propertyName = null;
    E propertyElement = propertyElements.get(methodName);
    Multimap<String, PropertySetter> propertyNameToSetters = null;
    if (propertyElement != null) {
      propertyNameToSetters = propertyNameToUnprefixedSetters;
      propertyName = methodName;
    } else if (methodName.startsWith("set") && methodName.length() > 3) {
      propertyNameToSetters = propertyNameToPrefixedSetters;
      propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3));
      propertyElement = propertyElements.get(propertyName);
      if (propertyElement == null) {
        // If our property is defined by a getter called getOAuth() then it is called "OAuth"
        // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter
        // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter
        // called oAuth() then it is called "oAuth", but you would still expect to be able to set it
        // using setOAuth(x). Hence the second try using a decapitalize method without the quirky
        // two-leading-capitals rule.
        propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3));
        propertyElement = propertyElements.get(propertyName);
      }
    } else {
      // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and
      // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called
      // OAuth(x). Iterating over the properties here is a bit clunky but this case should be
      // unusual.
      propertyNameToSetters = propertyNameToUnprefixedSetters;
      for (Map.Entry<String, E> entry : propertyElements.entrySet()) {
        if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) {
          propertyName = entry.getKey();
          propertyElement = entry.getValue();
          break;
        }
      }
    }
    if (propertyElement == null || propertyNameToSetters == null) {
      // The second disjunct isn't needed but convinces control-flow checkers that
      // propertyNameToSetters can't be null when we call put on it below.
      errorReporter.reportError(
          method,
          "[%sBuilderWhatProp] Method %s does not correspond to %s",
          autoWhat(),
          methodName,
          getterMustMatch());
      checkForFailedJavaBean(method);
      return;
    }
    Optional<Copier> function = getSetterFunction(propertyElement, method);
    if (function.isPresent()) {
      DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
      ExecutableType methodMirror =
          MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method));
      TypeMirror returnType = methodMirror.getReturnType();
      if (typeUtils.isSubtype(builderType.asType(), returnType)
          && !MoreTypes.isTypeOf(Object.class, returnType)) {
        if (nullableAnnotationFor(method, returnType).isPresent()) {
          errorReporter.
              reportWarning(
                  method,
                  "[%sBuilderSetterNullable] Setter methods always return the Builder so @Nullable"
                      + " is not appropriate", autoWhat());
        }
        // We allow the return type to be a supertype (other than Object), to support step builders.
        TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes());
        propertyNameToSetters.put(
            propertyName, new PropertySetter(method, parameterType, function.get()));
      } else {
        errorReporter.reportError(
            method,
            "[%sBuilderRet] Setter methods must return %s or a supertype",
            autoWhat(),
            builderType.asType());
      }
    }
  }

  /**
   * Classifies a method given that it has one argument and is a property builder with a parameter,
   * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}.
   *
   * @param method A method to classify
   * @return true if method has been classified successfully
   */
  private boolean classifyPropertyBuilderOneArg(ExecutableElement method) {
    String methodName = method.getSimpleName().toString();
    if (!methodName.endsWith("Builder")) {
      return false;
    }
    String property = methodName.substring(0, methodName.length() - "Builder".length());
    if (!rewrittenPropertyTypes.containsKey(property)) {
      return false;
    }
    PropertyBuilderClassifier propertyBuilderClassifier =
        new PropertyBuilderClassifier(
            errorReporter,
            typeUtils,
            elementUtils,
            this,
            this::propertyIsNullable,
            rewrittenPropertyTypes,
            eclipseHack,
            nullables);
    Optional<PropertyBuilder> maybePropertyBuilder =
        propertyBuilderClassifier.makePropertyBuilder(method, property);
    maybePropertyBuilder.ifPresent(
        propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder));
    return maybePropertyBuilder.isPresent();
  }

  /**
   * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
   * to the getter's return type, or {@code Optional.empty()} if the conversion isn't possible. An
   * error will have been reported in the latter case. We can convert if they are already the same
   * type, when the returned function will be the identity; or if the setter type can be copied
   * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned
   * function will be something like {@code s -> "Optional.of(" + s + ")"}.
   */
  private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) {
    VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
    boolean nullableParameter =
        nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent();
    String property = propertyElements().inverse().get(propertyElement);
    TypeMirror targetType = rewrittenPropertyTypes.get(property);
    ExecutableType finalSetter =
        MoreTypes.asExecutable(
            typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter));
    TypeMirror parameterType = finalSetter.getParameterTypes().get(0);
    // Two types are assignable to each other if they are the same type, or if one is primitive and
    // the other is the corresponding boxed type. There might be other cases where this is true, but
    // we're likely to want to accept those too.
    if (typeUtils.isAssignable(parameterType, targetType)
        && typeUtils.isAssignable(targetType, parameterType)) {
      if (nullableParameter) {
        boolean nullableProperty =
            nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement))
                .isPresent();
        if (!nullableProperty) {
          errorReporter.reportError(
              setter,
              "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not",
              autoWhat(),
              propertyString(propertyElement));
          return Optional.empty();
        }
      }
      return Optional.of(Copier.IDENTITY);
    }

    // Parameter type is not equal to property type, but might be convertible with copyOf.
    ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter);
    if (!copyOfMethods.isEmpty()) {
      return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType);
    }
    errorReporter.reportError(
        setter,
        "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s",
        autoWhat(),
        parameterType,
        targetType,
        propertyString(propertyElement));
    return Optional.empty();
  }

  /**
   * Returns an {@code Optional} describing how to convert a value from the setter's parameter type
   * to the getter's return type using one of the given methods, or {@code Optional.empty()} if the
   * conversion isn't possible. An error will have been reported in the latter case.
   */
  private Optional<Copier> getConvertingSetterFunction(
      ImmutableList<ExecutableElement> copyOfMethods,
      E propertyElement,
      ExecutableElement setter,
      TypeMirror parameterType) {
    String property = propertyElements().inverse().get(propertyElement);
    DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property));
    for (ExecutableElement copyOfMethod : copyOfMethods) {
      Optional<Copier> function =
          getConvertingSetterFunction(copyOfMethod, targetType, parameterType);
      if (function.isPresent()) {
        return function;
      }
    }
    String targetTypeSimpleName = targetType.asElement().getSimpleName().toString();
    errorReporter.reportError(
        setter,
        "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it"
            + " should be a type that can be passed to %s.%s to produce %s",
        autoWhat(),
        parameterType,
        targetType,
        propertyString(propertyElement),
        targetTypeSimpleName,
        copyOfMethods.get(0).getSimpleName(),
        targetType);
    return Optional.empty();
  }

  /**
   * Returns an {@code Optional} containing a function to use {@code copyOfMethod} to copy the
   * {@code parameterType} to the {@code targetType}, or {@code Optional.empty()} if the method
   * can't be used. For example, we might have a property of type {@code ImmutableSet<T>} and our
   * setter has a parameter of type {@code Set<? extends T>}. Can we use {@code ImmutableSet<E>
   * ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about {@code
   * ImmutableSet<E> ImmutableSet.copyOf(E[])}?
   *
   * <p>The example here is deliberately complicated, in that it has a type parameter of its own,
   * presumably because the {@code @AutoValue} class is {@code Foo<T>}. One subtle point is that the
   * builder will then be {@code Builder<T>} where this {@code T} is a <i>different</i> type
   * variable. However, we've used {@link TypeVariables} to ensure that the {@code T} in {@code
   * ImmutableSet<T>} is actually the one from {@code Builder<T>} instead of the original one from
   * {@code Foo<T>}.}
   *
   * @param copyOfMethod the candidate method to do the copy, {@code
   *     ImmutableSet.copyOf(Collection<? extends E>)} or {@code ImmutableSet.copyOf(E[])} in the
   *     examples.
   * @param targetType the type of the property to be set, {@code ImmutableSet<T>} in the example.
   * @param parameterType the type of the setter parameter, {@code Set<? extends T>} in the example.
   * @return a function that maps a string parameter to a method call using that parameter. For
   *     example it might map {@code foo} to {@code ImmutableList.copyOf(foo)}.
   */
  private Optional<Copier> getConvertingSetterFunction(
      ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType) {
    // We have a parameter type, for example Set<? extends T>, and we want to know if it can be
    // passed to the given copyOf method, which might for example be one of these methods from
    // ImmutableSet:
    //    public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements)
    //    public static <E> ImmutableSet<E> copyOf(E[] elements)
    // Additionally, if it can indeed be passed to the method, we want to know whether the result
    // (here ImmutableSet<? extends T>) is compatible with the property to be set.
    // We can't use Types.asMemberOf to do the substitution for us, because the methods in question
    // are static. So even if our target type is ImmutableSet<String>, if we ask what the type of
    // copyOf is in ImmutableSet<String> it will still tell us <T> Optional<T> (T).
    // Instead, we do the variable substitutions ourselves.
    if (TypeVariables.canAssignStaticMethodResult(
        copyOfMethod, parameterType, targetType, typeUtils)) {
      String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName();
      Function<String, String> callMethod = s -> method + "(" + s + ")";
      // This is a big old hack. We guess that the method can accept a null parameter if it has
      // "Nullable" in the name, which java.util.Optional.ofNullable and
      // com.google.common.base.Optional.fromNullable do.
      Copier copier =
          method.contains("Nullable")
              ? Copier.acceptingNull(callMethod)
              : Copier.notAcceptingNull(callMethod);
      return Optional.of(copier);
    }
    return Optional.empty();
  }

  /**
   * Returns {@code copyOf} methods from the given type. These are static methods with a single
   * parameter, called {@code copyOf} or {@code copyOfSorted} for Guava collection types, and called
   * {@code of} or {@code ofNullable} for {@code Optional}. All of Guava's concrete immutable
   * collection types have at least one such method, but we will also accept other classes with an
   * appropriate {@code copyOf} method, such as {@link java.util.EnumSet}.
   */
  private ImmutableList<ExecutableElement> copyOfMethods(
      TypeMirror targetType, boolean nullableParameter) {
    if (!targetType.getKind().equals(TypeKind.DECLARED)) {
      return ImmutableList.of();
    }
    ImmutableSet<String> copyOfNames;
    Optionalish optionalish = Optionalish.createIfOptional(targetType);
    if (optionalish == null) {
      copyOfNames = ImmutableSet.of("copyOfSorted", "copyOf");
    } else {
      copyOfNames = ImmutableSet.of(nullableParameter ? optionalish.ofNullable() : "of");
    }
    TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType));
    ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder();
    for (String copyOfName : copyOfNames) {
      for (ExecutableElement method :
          ElementFilter.methodsIn(targetTypeElement.getEnclosedElements())) {
        if (method.getSimpleName().contentEquals(copyOfName)
            && method.getParameters().size() == 1
            && method.getModifiers().contains(Modifier.STATIC)) {
          copyOfMethods.add(method);
        }
      }
    }
    return copyOfMethods.build();
  }

  /**
   * Returns the return type of the given method from the builder. This should be the final type of
   * the method when any bound type variables are substituted. Consider this example:
   *
   * <pre>{@code
   * abstract static class ParentBuilder<B extends ParentBuilder> {
   *   B setFoo(String s);
   * }
   * abstract static class ChildBuilder extends ParentBuilder<ChildBuilder> {
   *   ...
   * }
   * }</pre>
   *
   * If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also {@code
   * ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim.
   *
   * <p>If the caller is in a version of Eclipse with <a
   * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this bug</a> then the {@code
   * asMemberOf} call will fail if the method is inherited from an interface. We work around that
   * for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we
   * don't try to do so here because it should be much less likely. You might need to change {@code
   * ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to
   * do that anyway.
   */
  TypeMirror builderMethodReturnType(ExecutableElement builderMethod) {
    DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
    TypeMirror methodMirror;
    try {
      methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod);
    } catch (IllegalArgumentException e) {
      // Presumably we've hit the Eclipse bug cited.
      return builderMethod.getReturnType();
    }
    return MoreTypes.asExecutable(methodMirror).getReturnType();
  }

  private static String prefixWithSet(String propertyName) {
    // This is not internationalizationally correct, but it corresponds to what
    // Introspector.decapitalize does.
    return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
  }

  /**
   * True if the given property is nullable, either because its type has a {@code @Nullable} type
   * annotation, or because its getter method has a {@code @Nullable} method annotation.
   */
  private boolean propertyIsNullable(String property) {
    E propertyElement = propertyElements().get(property);
    return Stream.of(propertyElement, originalPropertyType(propertyElement))
        .flatMap(ac -> ac.getAnnotationMirrors().stream())
        .map(a -> a.getAnnotationType().asElement().getSimpleName())
        .anyMatch(n -> n.contentEquals("Nullable"));
  }

  /**
   * Returns a map from property names to the corresponding source program elements. For AutoValue,
   * these elements are the abstract getter methods in the {@code @AutoValue} class. For
   * AutoBuilder, they are the parameters of the constructor or method that the generated builder
   * will call.
   */
  abstract ImmutableBiMap<String, E> propertyElements();

  /**
   * Returns the property type as it appears on the original source program element. This can be
   * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to
   * type variables in the builder rather than in the original class. Also, {@link
   * #rewrittenPropertyTypes} will not have type annotations even if they were present on the
   * original element, so {@code originalPropertyType} is the right thing to use for those.
   */
  abstract TypeMirror originalPropertyType(E propertyElement);

  /**
   * A string identifying the given property element, which is a method for AutoValue or a parameter
   * for AutoBuilder.
   */
  abstract String propertyString(E propertyElement);

  /**
   * Returns the name of the property that the given no-arg builder method queries, if
   * any. For example, if your {@code @AutoValue} class has a method {@code abstract String
   * getBar()} then an abstract method in its builder with the same signature will query the {@code
   * bar} property.
   */
  abstract Optional<String> propertyForBuilderGetter(ExecutableElement method);

  /**
   * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match
   * anything, and emits a compiler Note if detected. A frequent source of problems is where the
   * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers
   * that they haven't been followed at all, so you might have a property called getFoo where you
   * thought it was called just foo, and you might not understand why your setter called setFoo is
   * rejected (it would have to be called setGetFoo).
   *
   * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The
   * parameter names are unambiguously the same as the property names.
   */
  abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter);

  /**
   * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}.
   */
  abstract String autoWhat();

  /**
   * A string describing what a builder getter must match: a property method for AutoValue, a
   * parameter for AutoBuilder.
   */
  abstract String getterMustMatch();

  /**
   * A string describing what a property builder for property {@code foo} must match, {@code foo()
   * or getFoo()} for AutoValue, {@code foo} for AutoBuilder.
   */
  abstract String fooBuilderMustMatch();
}