• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2019 Google, Inc.
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 
17 package com.google.common.truth.refactorings;
18 
19 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
20 import static com.google.common.base.Objects.equal;
21 import static com.google.common.collect.ImmutableList.toImmutableList;
22 import static com.google.common.collect.ImmutableSet.toImmutableSet;
23 import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
24 import static com.google.common.collect.Iterables.getOnlyElement;
25 import static com.google.common.collect.MoreCollectors.onlyElement;
26 import static com.google.common.collect.Streams.stream;
27 import static com.google.common.truth.refactorings.CorrespondenceSubclassToFactoryCall.MemberType.COMPARE_METHOD;
28 import static com.google.common.truth.refactorings.CorrespondenceSubclassToFactoryCall.MemberType.CONSTRUCTOR;
29 import static com.google.common.truth.refactorings.CorrespondenceSubclassToFactoryCall.MemberType.TO_STRING_METHOD;
30 import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
31 import static com.google.errorprone.fixes.SuggestedFixes.compilesWithFix;
32 import static com.google.errorprone.matchers.Description.NO_MATCH;
33 import static com.google.errorprone.util.ASTHelpers.getDeclaredSymbol;
34 import static com.google.errorprone.util.ASTHelpers.getSymbol;
35 import static com.sun.source.tree.Tree.Kind.EXPRESSION_STATEMENT;
36 import static com.sun.source.tree.Tree.Kind.IDENTIFIER;
37 import static com.sun.source.tree.Tree.Kind.MEMBER_SELECT;
38 import static com.sun.source.tree.Tree.Kind.METHOD_INVOCATION;
39 import static com.sun.source.tree.Tree.Kind.NEW_CLASS;
40 import static com.sun.source.tree.Tree.Kind.NULL_LITERAL;
41 import static com.sun.source.tree.Tree.Kind.RETURN;
42 import static java.lang.String.format;
43 import static javax.lang.model.element.ElementKind.METHOD;
44 import static javax.lang.model.element.Modifier.PRIVATE;
45 import static javax.lang.model.element.Modifier.PROTECTED;
46 import static javax.lang.model.element.Modifier.PUBLIC;
47 
48 import com.google.auto.value.AutoValue;
49 import com.google.common.base.CaseFormat;
50 import com.google.common.collect.HashMultimap;
51 import com.google.common.collect.ImmutableList;
52 import com.google.common.collect.ImmutableSet;
53 import com.google.common.collect.ImmutableSetMultimap;
54 import com.google.common.collect.SetMultimap;
55 import com.google.errorprone.BugPattern;
56 import com.google.errorprone.VisitorState;
57 import com.google.errorprone.bugpatterns.BugChecker;
58 import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
59 import com.google.errorprone.fixes.SuggestedFix;
60 import com.google.errorprone.matchers.Description;
61 import com.google.errorprone.suppliers.Supplier;
62 import com.sun.source.tree.ClassTree;
63 import com.sun.source.tree.CompilationUnitTree;
64 import com.sun.source.tree.ExpressionStatementTree;
65 import com.sun.source.tree.ExpressionTree;
66 import com.sun.source.tree.IdentifierTree;
67 import com.sun.source.tree.MemberSelectTree;
68 import com.sun.source.tree.MethodInvocationTree;
69 import com.sun.source.tree.MethodTree;
70 import com.sun.source.tree.NewClassTree;
71 import com.sun.source.tree.ReturnTree;
72 import com.sun.source.tree.StatementTree;
73 import com.sun.source.tree.Tree;
74 import com.sun.source.util.TreeScanner;
75 import com.sun.tools.javac.code.Symbol;
76 import com.sun.tools.javac.code.Symbol.MethodSymbol;
77 import com.sun.tools.javac.code.Symbol.TypeSymbol;
78 import com.sun.tools.javac.code.Type;
79 import com.sun.tools.javac.tree.JCTree;
80 import java.util.HashSet;
81 import java.util.List;
82 import java.util.Optional;
83 import java.util.Set;
84 import javax.lang.model.element.Modifier;
85 import org.checkerframework.checker.nullness.qual.Nullable;
86 
87 /**
88  * Refactors some subclasses of {@code Correspondence} to instead call {@code Correspondence.from}.
89  * The exact change generated for a given correspondence depends on the details of how it is defined
90  * and used.
91  */
92 @BugPattern(
93     name = "CorrespondenceSubclassToFactoryCall",
94     summary = "Use the factory methods on Correspondence instead of defining a subclass.",
95     severity = SUGGESTION)
96 public final class CorrespondenceSubclassToFactoryCall extends BugChecker
97     implements ClassTreeMatcher {
98 
99   private static final String CORRESPONDENCE_CLASS = "com.google.common.truth.Correspondence";
100 
101   private static final Supplier<Type> COM_GOOGLE_COMMON_TRUTH_CORRESPONDENCE =
102       VisitorState.memoize(state -> state.getTypeFromString(CORRESPONDENCE_CLASS));
103 
104   @Override
matchClass(ClassTree tree, VisitorState state)105   public Description matchClass(ClassTree tree, VisitorState state) {
106     if (!isCorrespondence(tree.getExtendsClause(), state)) {
107       return NO_MATCH;
108     }
109 
110     List<CorrespondenceCode> replacements = computePossibleReplacements(tree, state);
111 
112     Tree parent = state.getPath().getParentPath().getLeaf();
113     if (parent.getKind() == NEW_CLASS) {
114       // Anonymous class. Replace the whole `new Correspondence` with a Correspondence.from call.
115       for (CorrespondenceCode replacement : replacements) {
116         SuggestedFix.Builder fix = SuggestedFix.builder();
117 
118         fix.replace(parent, replacement.callSite());
119 
120         Tree methodOrField = findChildOfStrictlyEnclosing(state, ClassTree.class);
121         /*
122          * If a class declares multiple anonymous Correspondences, we might end up creating multiple
123          * compare() methods in the same scope. We might get away with it, depending on the types
124          * involved, or we might need to manually rename some.
125          */
126         fix.postfixWith(methodOrField, replacement.supportingMethodDefinition());
127 
128         if (compilesWithFix(fix.build(), state)) {
129           return describeMatch(parent, fix.build());
130         }
131       }
132       return NO_MATCH;
133     }
134 
135     Symbol classSymbol = getDeclaredSymbol(tree);
136     /*
137      * Named class. Create a Correspondence.from call, but then decide where to put it:
138      *
139      * The "safest" thing to do is to replace the body of the class with...
140      *
141      * static final Correspondence INSTANCE = Correspondence.from(...);
142      *
143      * ...and then make all callers refer to that. (As long as the class isn't top-level, we can
144      * even delete the class entirely, putting the constant in its place.)
145      *
146      * The other option is to inline that into all use sites. That's a great option if there's a
147      * single use site in a constant or helper method, in which case we produce code like...
148      *
149      * private static Correspondence makeCorrespondence() {
150      *   return Correspondence.from(...);
151      * }
152      *
153      * But the danger of inlining is twofold:
154      *
155      * 1. If there are multiple callers, we'd duplicate the definition of the Correspondence.
156      *
157      * 2. Even if there's only one caller, we might inline a large chunk of code into the middle of
158      * a comparingElementsUsing call.
159      *
160      * So we use the "safe" option unless (a) there's exactly one call and (b) it's not inside a
161      * comparingElementsUsing call.
162      */
163     SetMultimap<ParentType, NewClassTree> calls = findCalls(classSymbol, state);
164     /*
165      * We also sometime see users use the named type for fields and return types, like...
166      *
167      * static final MyCorrespondence INSTANCE = new MyCorrespondence();
168      *
169      * We need to replace that with the generic Correspondence type, like...
170      *
171      * static final Correspondence<String, Integer> INSTANCE = ...;
172      */
173     Set<Tree> typeReferences = findTypeReferences(classSymbol, state);
174 
175     if (calls.size() == 1 && getOnlyElement(calls.keys()) == ParentType.OTHER) {
176       // Inline it.
177       Tree call = getOnlyElement(calls.values());
178       for (CorrespondenceCode replacement : replacements) {
179         SuggestedFix.Builder fix = SuggestedFix.builder();
180         replaceTypeReferences(fix, typeReferences, state.getSourceForNode(tree.getExtendsClause()));
181         fix.replace(call, replacement.callSite());
182         fix.replace(tree, replacement.supportingMethodDefinition());
183         if (compilesWithFix(fix.build(), state)) {
184           return describeMatch(tree, fix.build());
185         }
186       }
187       return NO_MATCH;
188     }
189 
190     // Declare a constant, and make use sites refer to that.
191 
192     /*
193      * If we can't find any callers, then they're probably in other files, so we're going to be
194      * stuck updating them manually. To make the manual updates as simple as possible, we'll declare
195      * a constant named INSTANCE inside the Correspondence subclass (though it won't be a
196      * Correspondence subclass after our changes, just a holder class).
197      */
198     if (calls.isEmpty()) {
199       for (CorrespondenceCode replacement : replacements) {
200         SuggestedFix.Builder fix = SuggestedFix.builder();
201         replaceTypeReferences(fix, typeReferences, state.getSourceForNode(tree.getExtendsClause()));
202 
203         JCTree extendsClause = (JCTree) tree.getExtendsClause();
204         // Replace everything from `extends` to the end of the class, keeping only "class Foo."
205         int startOfExtends =
206             state
207                 .getSourceCode()
208                 .subSequence(0, extendsClause.getStartPosition())
209                 .toString()
210                 .lastIndexOf("extends");
211         fix.replace(
212             startOfExtends,
213             state.getEndPosition(tree),
214             format(
215                 "{ %s static final %s INSTANCE = %s; %s }",
216                 visibilityModifierOnConstructor(tree),
217                 state.getSourceForNode(extendsClause),
218                 replacement.callSite(),
219                 replacement.supportingMethodDefinition()));
220 
221         for (Tree call : calls.values()) {
222           fix.replace(call, tree.getSimpleName() + ".INSTANCE");
223         }
224 
225         if (compilesWithFix(fix.build(), state)) {
226           return describeMatch(tree, fix.build());
227         }
228       }
229       return NO_MATCH;
230     }
231 
232     /*
233      * We found callers. Let's optimistically assume that we found them all, in which case we might
234      * as well replace the whole class with a constant.
235      */
236     for (CorrespondenceCode replacement : replacements) {
237       SuggestedFix.Builder fix = SuggestedFix.builder();
238       replaceTypeReferences(fix, typeReferences, state.getSourceForNode(tree.getExtendsClause()));
239 
240       String name = CaseFormat.UPPER_CAMEL.to(UPPER_UNDERSCORE, tree.getSimpleName().toString());
241 
242       // TODO(cpovirk): We're unlikely to get away with declaring everything `static`.
243       fix.replace(
244           tree,
245           format(
246               "%s static final %s %s = %s; %s",
247               visibilityModifierOnConstructor(tree),
248               state.getSourceForNode(tree.getExtendsClause()),
249               name,
250               replacement.callSite(),
251               replacement.supportingMethodDefinition()));
252 
253       for (Tree call : calls.values()) {
254         fix.replace(call, name);
255       }
256 
257       if (compilesWithFix(fix.build(), state)) {
258         return describeMatch(tree, fix.build());
259       }
260     }
261     return NO_MATCH;
262   }
263 
replaceTypeReferences( SuggestedFix.Builder fix, Set<Tree> typeReferences, String newType)264   private static void replaceTypeReferences(
265       SuggestedFix.Builder fix, Set<Tree> typeReferences, String newType) {
266     for (Tree reference : typeReferences) {
267       fix.replace(reference, newType);
268     }
269   }
270 
visibilityModifierOnConstructor(ClassTree tree)271   private String visibilityModifierOnConstructor(ClassTree tree) {
272     MethodTree constructor =
273         tree.getMembers().stream()
274             .filter(t -> t instanceof MethodTree)
275             .map(t -> (MethodTree) t)
276             .filter(t -> t.getName().contentEquals("<init>"))
277             .findAny()
278             .get();
279     // We don't include the ModifiersTree directly in case it contains any annotations.
280     return constructor.getModifiers().getFlags().stream()
281         .filter(m -> m == PUBLIC || m == PROTECTED || m == PRIVATE)
282         .findAny()
283         .map(Modifier::toString)
284         .orElse("");
285   }
286 
287   /** Returns all calls to the constructor for the given {@code classSymbol}, organized by {@linkplain ParentType whether they happen inside a call to {@code comparingElementsUsing}. */
findCalls( Symbol classSymbol, VisitorState state)288   private static SetMultimap<ParentType, NewClassTree> findCalls(
289       Symbol classSymbol, VisitorState state) {
290     SetMultimap<ParentType, NewClassTree> calls = HashMultimap.create();
291     new TreeScanner<Void, Void>() {
292       private ParentType parentType = ParentType.OTHER;
293 
294       @Override
295       public @Nullable Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
296         boolean isComparingElementsUsing =
297             Optional.of(node.getMethodSelect())
298                 .filter(t -> t.getKind() == MEMBER_SELECT)
299                 .map(t -> (MemberSelectTree) t)
300                 .filter(t -> t.getIdentifier().contentEquals("comparingElementsUsing"))
301                 .isPresent();
302         if (isComparingElementsUsing) {
303           ParentType oldParentType = parentType;
304           parentType = ParentType.COMPARING_ELEMENTS_USING;
305           super.visitMethodInvocation(node, unused);
306           parentType = oldParentType;
307         } else {
308           super.visitMethodInvocation(node, unused);
309         }
310         return null;
311       }
312 
313       @Override
314       public @Nullable Void visitNewClass(NewClassTree node, Void unused) {
315         if (getSymbol(node.getIdentifier()).equals(classSymbol)) {
316           calls.put(parentType, node);
317         }
318         return super.visitNewClass(node, unused);
319       }
320     }.scan(state.findEnclosing(CompilationUnitTree.class), null);
321     return calls;
322   }
323 
324   /**
325    * Finds all references to the name of the given type, excluding (a) when the name is defined
326    * (e.g, {@code public class MyCorrespondence}) and (b) calls to its constructor (e.g., {@code new
327    * MyCorrespondence()}). The goal is to find every reference that we aren't already modifying so
328    * that we can rewrite, e.g., fields of the given type.
329    */
findTypeReferences(Symbol classSymbol, VisitorState state)330   private static Set<Tree> findTypeReferences(Symbol classSymbol, VisitorState state) {
331     Set<Tree> references = new HashSet<>();
332     new TreeScanner<Void, Void>() {
333       @Override
334       public @Nullable Void scan(Tree node, Void unused) {
335         if (equal(getSymbol(node), classSymbol)
336             && getDeclaredSymbol(node) == null // Don't touch the ClassTree that we're replacing.
337         ) {
338           references.add(node);
339         }
340         return super.scan(node, unused);
341       }
342 
343       @Override
344       public @Nullable Void visitNewClass(NewClassTree node, Void aVoid) {
345         scan(node.getEnclosingExpression(), null);
346         // Do NOT scan node.getIdentifier().
347         scan(node.getTypeArguments(), null);
348         scan(node.getArguments(), null);
349         scan(node.getClassBody(), null);
350         return null;
351       }
352     }.scan(state.findEnclosing(CompilationUnitTree.class), null);
353     return references;
354   }
355 
356   /**
357    * Whether the instantiation of a correspondence happens directly inside a call to {@code
358    * comparingElementsUsing} or not. For example, {@code comparingElementsUsing(new
359    * MyCorrespondence())} and {@code comparingElementsUsing(new Correspondence() { ... })} are both
360    * considered to have type {@link #COMPARING_ELEMENTS_USING}, but {@code private static final
361    * MyCorrespondence CORRESPONDENCE = new MyCorrespondence()} does not.
362    */
363   enum ParentType {
364     COMPARING_ELEMENTS_USING,
365     OTHER,
366   }
367 
368   /**
369    * If the given correspondence implementation is "simple enough," returns one or more possible
370    * replacements for its definition and instantiation sites.
371    */
computePossibleReplacements( ClassTree classTree, VisitorState state)372   private static ImmutableList<CorrespondenceCode> computePossibleReplacements(
373       ClassTree classTree, VisitorState state) {
374     ImmutableSetMultimap<MemberType, Tree> members =
375         classTree.getMembers().stream()
376             .collect(toImmutableSetMultimap(m -> MemberType.from(m, state), m -> m));
377     if (members.containsKey(MemberType.OTHER)
378         || members.get(CONSTRUCTOR).size() != 1
379         || members.get(COMPARE_METHOD).size() != 1
380         || members.get(TO_STRING_METHOD).size() != 1) {
381       return ImmutableList.of();
382     }
383     MethodTree constructor = (MethodTree) getOnlyElement(members.get(CONSTRUCTOR));
384     MethodTree compareMethod = (MethodTree) getOnlyElement(members.get(COMPARE_METHOD));
385     MethodTree toStringMethod = (MethodTree) getOnlyElement(members.get(TO_STRING_METHOD));
386 
387     if (!constructorCallsOnlySuper(constructor)) {
388       return ImmutableList.of();
389     }
390 
391     ImmutableList<BinaryPredicateCode> binaryPredicates =
392         makeBinaryPredicates(classTree, compareMethod, state);
393 
394     ExpressionTree toStringReturns = returnExpression(toStringMethod);
395     if (toStringReturns == null) {
396       return ImmutableList.of();
397     }
398     /*
399      * Replace bad toString() implementations that merely `return null`, since the factories make
400      * that an immediate error.
401      */
402     String description =
403         toStringReturns.getKind() == NULL_LITERAL
404             ? "\"corresponds to\""
405             : state.getSourceForNode(toStringReturns);
406     return binaryPredicates.stream()
407         .map(p -> CorrespondenceCode.create(p, description))
408         .collect(toImmutableList());
409   }
410 
411   /** Returns one or more possible replacements for the given correspondence's {@code compare} method's definition and for code to pass to {@code Correspondence.from) to construct a correspondence that uses the replacement. */
makeBinaryPredicates( ClassTree classTree, MethodTree compareMethod, VisitorState state)412   private static ImmutableList<BinaryPredicateCode> makeBinaryPredicates(
413       ClassTree classTree, MethodTree compareMethod, VisitorState state) {
414     Tree comparison = maybeMakeLambdaBody(compareMethod, state);
415     if (comparison == null) {
416       ClassTree enclosing = findStrictlyEnclosing(state, ClassTree.class);
417       CharSequence newCompareMethodOwner;
418       String newCompareMethodName;
419       if (enclosing == null) {
420         newCompareMethodName = "compare";
421         newCompareMethodOwner = classTree.getSimpleName();
422       } else {
423         newCompareMethodName =
424             "compare" + classTree.getSimpleName().toString().replaceFirst("Correspondence$", "");
425         newCompareMethodOwner = enclosing.getSimpleName();
426       }
427       // TODO(cpovirk): We're unlikely to get away with declaring everything `static`.
428       String supportingMethodDefinition =
429           format(
430               "private static boolean %s(%s, %s) %s",
431               newCompareMethodName,
432               state.getSourceForNode(compareMethod.getParameters().get(0)),
433               state.getSourceForNode(compareMethod.getParameters().get(1)),
434               state.getSourceForNode(compareMethod.getBody()));
435       return ImmutableList.of(
436           BinaryPredicateCode.create(
437               newCompareMethodOwner + "::" + newCompareMethodName, supportingMethodDefinition));
438     }
439     // First try without types, then try with.
440     return ImmutableList.of(
441         BinaryPredicateCode.fromParamsAndExpression(
442             compareMethod.getParameters().get(0).getName(),
443             compareMethod.getParameters().get(1).getName(),
444             state.getSourceForNode(comparison)),
445         BinaryPredicateCode.fromParamsAndExpression(
446             state.getSourceForNode(compareMethod.getParameters().get(0)),
447             state.getSourceForNode(compareMethod.getParameters().get(1)),
448             state.getSourceForNode(comparison)));
449   }
450 
451   /**
452    * Converts the given method into a lambda, either expression or block, if "appropriate." For
453    * details about the various cases, see implementation comments.
454    */
maybeMakeLambdaBody(MethodTree compareMethod, VisitorState state)455   private static @Nullable Tree maybeMakeLambdaBody(MethodTree compareMethod, VisitorState state) {
456     ExpressionTree comparison = returnExpression(compareMethod);
457     if (comparison != null) {
458       // compare() is defined as simply `return something;`. Create a lambda.
459       return comparison;
460     }
461 
462     /*
463      * compare() has multiple statements. Let's keep it as a method (though we might change its
464      * modifiers, name, and location) and construct the Correspondence with a method reference...
465      *
466      * ...unless it relies on parameters from the enclosing method, in which case extracting a
467      * method isn't going to work because it won't be able to access those parameters.
468      */
469     MethodTree enclosingMethod = state.findEnclosing(MethodTree.class);
470     if (enclosingMethod == null) {
471       // No enclosing method, so we're presumably not closing over anything. Safe to extract method.
472       return null;
473     }
474 
475     ImmutableSet<Symbol> paramsOfEnclosingMethod =
476         enclosingMethod.getParameters().stream()
477             .map(p -> getDeclaredSymbol(p))
478             .collect(toImmutableSet());
479     boolean[] referenceFound = new boolean[1];
480     new TreeScanner<Void, Void>() {
481       @Override
482       public @Nullable Void scan(Tree node, Void aVoid) {
483         if (paramsOfEnclosingMethod.contains(getSymbol(node))) {
484           referenceFound[0] = true;
485         }
486         return super.scan(node, aVoid);
487       }
488     }.scan(state.getPath().getLeaf(), null);
489 
490     if (!referenceFound[0]) {
491       // No references to anything from the enclosing method. Probably safe to extract a method.
492       return null;
493     }
494 
495     /*
496      * compare() both:
497      *
498      * - uses a parameter from the enclosing method and
499      *
500      * - has multiple statements
501      *
502      * So we create a block lambda.
503      */
504     return compareMethod.getBody();
505   }
506 
507   /** Like {@link VisitorState#findEnclosing} but doesn't consider the leaf to enclose itself. */
findStrictlyEnclosing( VisitorState state, Class<T> clazz)508   private static <T extends Tree> @Nullable T findStrictlyEnclosing(
509       VisitorState state, Class<T> clazz) {
510     return stream(state.getPath().getParentPath())
511         .filter(clazz::isInstance)
512         .map(clazz::cast)
513         .findAny()
514         .orElse(null);
515   }
516 
517   /**
518    * Like {@link #findStrictlyEnclosing} but returns not the found element but its child along the
519    * path. For example, if called with {@code ClassTree}, it might return a {@code MethodTree}
520    * inside the class.
521    */
findChildOfStrictlyEnclosing( VisitorState state, Class<? extends Tree> clazz)522   private static @Nullable Tree findChildOfStrictlyEnclosing(
523       VisitorState state, Class<? extends Tree> clazz) {
524     Tree previous = state.getPath().getLeaf();
525     for (Tree t : state.getPath().getParentPath()) {
526       if (clazz.isInstance(t)) {
527         return previous;
528       }
529       previous = t;
530     }
531     return null;
532   }
533 
534   /**
535    * A {@code Correspondence.from} call to replace the instantiation site of a {@code
536    * Correspondence}. Often the call is self-contained (if it's a lambda), but sometimes it's a
537    * method reference, in which case it's accompanied by a separate method definition.
538    */
539   @AutoValue
540   abstract static class CorrespondenceCode {
create(BinaryPredicateCode binaryPredicate, String description)541     static CorrespondenceCode create(BinaryPredicateCode binaryPredicate, String description) {
542       return new AutoValue_CorrespondenceSubclassToFactoryCall_CorrespondenceCode(
543           binaryPredicate, description);
544     }
545 
binaryPredicate()546     abstract BinaryPredicateCode binaryPredicate();
547 
description()548     abstract String description();
549 
callSite()550     final String callSite() {
551       return format("Correspondence.from(%s, %s)", binaryPredicate().usage(), description());
552     }
553 
supportingMethodDefinition()554     final String supportingMethodDefinition() {
555       return binaryPredicate().supportingMethodDefinition().orElse("");
556     }
557   }
558 
559   /**
560    * Code that can be inserted as the first argument of a {@code Correspondence.from} call. Often
561    * it's a lambda, but sometimes it's a method reference, in which case it's accompanied by a
562    * separate method definition.
563    */
564   @AutoValue
565   abstract static class BinaryPredicateCode {
fromParamsAndExpression( CharSequence param0, CharSequence param1, String expression)566     static BinaryPredicateCode fromParamsAndExpression(
567         CharSequence param0, CharSequence param1, String expression) {
568       return create(String.format("(%s, %s) -> %s", param0, param1, expression), null);
569     }
570 
create(String usage, @Nullable String supportingMethodDefinition)571     static BinaryPredicateCode create(String usage, @Nullable String supportingMethodDefinition) {
572       return new AutoValue_CorrespondenceSubclassToFactoryCall_BinaryPredicateCode(
573           usage, Optional.ofNullable(supportingMethodDefinition));
574     }
575 
usage()576     abstract String usage();
577 
supportingMethodDefinition()578     abstract Optional<String> supportingMethodDefinition();
579   }
580 
returnExpression(MethodTree method)581   private static @Nullable ExpressionTree returnExpression(MethodTree method) {
582     List<? extends StatementTree> statements = method.getBody().getStatements();
583     if (statements.size() != 1) {
584       return null;
585     }
586     StatementTree statement = getOnlyElement(statements);
587     if (statement.getKind() != RETURN) {
588       return null;
589     }
590     return ((ReturnTree) statement).getExpression();
591   }
592 
constructorCallsOnlySuper(MethodTree constructor)593   private static boolean constructorCallsOnlySuper(MethodTree constructor) {
594     List<? extends StatementTree> statements = constructor.getBody().getStatements();
595     if (statements.size() != 1) {
596       return false;
597     }
598     StatementTree statement = getOnlyElement(statements);
599     if (statement.getKind() != EXPRESSION_STATEMENT) {
600       return false;
601     }
602     ExpressionTree expression = ((ExpressionStatementTree) statement).getExpression();
603     if (expression.getKind() != METHOD_INVOCATION) {
604       return false;
605     }
606     ExpressionTree methodSelect = ((MethodInvocationTree) expression).getMethodSelect();
607     if (methodSelect.getKind() != IDENTIFIER
608         || !((IdentifierTree) methodSelect).getName().contentEquals("super")) {
609       return false;
610     }
611     return true;
612   }
613 
614   /**
615    * Whether a member is of one of the standard {@link Correspondence} methods that we know how to
616    * migrate and, if so, which one.
617    */
618   enum MemberType {
619     CONSTRUCTOR,
620     COMPARE_METHOD,
621     TO_STRING_METHOD,
622     OTHER,
623     ;
624 
from(Tree tree, VisitorState state)625     static MemberType from(Tree tree, VisitorState state) {
626       Symbol symbol = getDeclaredSymbol(tree);
627       if (!(symbol instanceof MethodSymbol)) {
628         return OTHER;
629       }
630       MethodSymbol methodSymbol = (MethodSymbol) symbol;
631 
632       if (methodSymbol.getSimpleName().contentEquals("<init>")) {
633         return CONSTRUCTOR;
634       } else if (overrides(methodSymbol, CORRESPONDENCE_CLASS, "compare", state)) {
635         return COMPARE_METHOD;
636       } else if (overrides(methodSymbol, "java.lang.Object", "toString", state)) {
637         return TO_STRING_METHOD;
638       } else {
639         return OTHER;
640       }
641     }
642   }
643 
overrides( MethodSymbol potentialOverrider, String clazz, String method, VisitorState state)644   private static boolean overrides(
645       MethodSymbol potentialOverrider, String clazz, String method, VisitorState state) {
646     Symbol overridable =
647         state.getTypeFromString(clazz).tsym.getEnclosedElements().stream()
648             .filter(s -> s.getKind() == METHOD)
649             .filter(m -> m.getSimpleName().contentEquals(method))
650             .collect(onlyElement());
651     return potentialOverrider.getSimpleName().contentEquals(method)
652         && potentialOverrider.overrides(
653             overridable, (TypeSymbol) overridable.owner, state.getTypes(), true);
654   }
655 
isCorrespondence(Tree supertypeTree, VisitorState state)656   private static boolean isCorrespondence(Tree supertypeTree, VisitorState state) {
657     Type correspondenceType = COM_GOOGLE_COMMON_TRUTH_CORRESPONDENCE.get(state);
658     if (correspondenceType == null) {
659       return false;
660     }
661     return supertypeTree != null
662         && state.getTypes().isSameType(getSymbol(supertypeTree).type, correspondenceType);
663   }
664 }
665