• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.auto.value.processor;
17 
18 import static com.google.auto.common.MoreStreams.toImmutableBiMap;
19 import static com.google.auto.common.MoreStreams.toImmutableMap;
20 
21 import com.google.auto.common.MoreTypes;
22 import com.google.common.base.Equivalence;
23 import com.google.common.collect.ImmutableBiMap;
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.collect.ImmutableSet;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.function.Function;
32 import javax.annotation.processing.ProcessingEnvironment;
33 import javax.lang.model.element.Element;
34 import javax.lang.model.element.ExecutableElement;
35 import javax.lang.model.element.TypeElement;
36 import javax.lang.model.element.TypeParameterElement;
37 import javax.lang.model.element.VariableElement;
38 import javax.lang.model.type.TypeMirror;
39 import javax.lang.model.type.TypeVariable;
40 import javax.lang.model.util.Types;
41 
42 class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<VariableElement> {
43   private final Executable executable;
44   private final ImmutableBiMap<VariableElement, String> paramToPropertyName;
45 
BuilderMethodClassifierForAutoBuilder( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, Executable executable, TypeMirror builtType, TypeElement builderType, ImmutableBiMap<VariableElement, String> paramToPropertyName, ImmutableMap<String, TypeMirror> rewrittenPropertyTypes, ImmutableSet<String> propertiesWithDefaults, Nullables nullables)46   private BuilderMethodClassifierForAutoBuilder(
47       ErrorReporter errorReporter,
48       ProcessingEnvironment processingEnv,
49       Executable executable,
50       TypeMirror builtType,
51       TypeElement builderType,
52       ImmutableBiMap<VariableElement, String> paramToPropertyName,
53       ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
54       ImmutableSet<String> propertiesWithDefaults,
55       Nullables nullables) {
56     super(
57         errorReporter,
58         processingEnv,
59         builtType,
60         builderType,
61         rewrittenPropertyTypes,
62         propertiesWithDefaults,
63         nullables);
64     this.executable = executable;
65     this.paramToPropertyName = paramToPropertyName;
66   }
67 
68   /**
69    * Classifies the given methods from a builder type and its ancestors.
70    *
71    * @param methods the abstract methods in {@code builderType} and its ancestors.
72    * @param errorReporter where to report errors.
73    * @param processingEnv the ProcessingEnvironment for annotation processing.
74    * @param executable the constructor or static method that AutoBuilder will call.
75    * @param builtType the type to be built.
76    * @param builderType the builder class or interface within {@code ofClass}.
77    * @param propertiesWithDefaults properties that have a default value, so it is not an error for
78    *     them not to have a setter.
79    * @return an {@code Optional} that contains the results of the classification if it was
80    *     successful or nothing if it was not.
81    */
classify( Iterable<ExecutableElement> methods, ErrorReporter errorReporter, ProcessingEnvironment processingEnv, Executable executable, TypeMirror builtType, TypeElement builderType, ImmutableSet<String> propertiesWithDefaults, Nullables nullables)82   static Optional<BuilderMethodClassifier<VariableElement>> classify(
83       Iterable<ExecutableElement> methods,
84       ErrorReporter errorReporter,
85       ProcessingEnvironment processingEnv,
86       Executable executable,
87       TypeMirror builtType,
88       TypeElement builderType,
89       ImmutableSet<String> propertiesWithDefaults,
90       Nullables nullables) {
91     ImmutableBiMap<VariableElement, String> paramToPropertyName =
92         executable.parameters().stream()
93             .collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString()));
94     ImmutableMap<String, TypeMirror> rewrittenPropertyTypes =
95         rewriteParameterTypes(executable, builderType, errorReporter, processingEnv.getTypeUtils());
96     BuilderMethodClassifier<VariableElement> classifier =
97         new BuilderMethodClassifierForAutoBuilder(
98             errorReporter,
99             processingEnv,
100             executable,
101             builtType,
102             builderType,
103             paramToPropertyName,
104             rewrittenPropertyTypes,
105             propertiesWithDefaults,
106             nullables);
107     if (classifier.classifyMethods(methods, false)) {
108       return Optional.of(classifier);
109     } else {
110       return Optional.empty();
111     }
112   }
113 
114   // Rewrites the parameter types of the executable so they use the type variables of the builder
115   // where appropriate.
116   //
117   // Suppose we have something like this:
118   //
119   // static <E> Set<E> singletonSet(E elem) {...}
120   //
121   // @AutoBuilder(callMethod = "singletonSet")
122   // interface SingletonSetBuilder<E> {
123   //   SingletonSetBuilder<E> setElem(E elem);
124   //   Set<E> build();
125   // }
126   //
127   // We want to check that the type of the setter `setElem` matches the type of the
128   // parameter it is setting. But in fact it doesn't: the type of the setter is
129   // E-of-SingletonSetBuilder while the type of the parameter is E-of-singletonSet. So we
130   // need to rewrite any type variables mentioned in parameters so that they use the corresponding
131   // types from the builder. We want to return a map where "elem" is mapped to
132   // E-of-SingletonSetBuilder, even though the `elem` that we get from the parameters of
133   // singletonSet is going to be E-of-singletonSet. And we also want that to work if the parameter
134   // is something more complicated, like List<? extends E>.
135   //
136   // For the corresponding situation with AutoValue, we have a way of dodging the problem somewhat.
137   // For an @AutoValue class Foo<E> with a builder Builder<E>, we can craft a DeclaredType
138   // Foo<E> where the E comes from Builder<E>, and we can use Types.asMemberOf to determine the
139   // return types of methods (which are what we want to rewrite in that case). But that doesn't
140   // work here because singletonSet is static and Types.asMemberOf would have no effect on it.
141   //
142   // So instead we take the type of each parameter and feed it through a TypeVisitor that rewrites
143   // type variables, rewriting from E-of-singletonSet to E-of-SingletonSetBuilder. Then we can use
144   // Types.isSameType or Types.isAssignable and it will work as we expect.
145   //
146   // In principle a similar situation arises with the return type Set<E> of singletonSet versus
147   // the return type Set<E> of SingletonSetBuilder.build(). But in fact we only use
148   // MoreTypes.equivalence to compare those, and that returns true for distinct type variables if
149   // they have the same name and bounds.
rewriteParameterTypes( Executable executable, TypeElement builderType, ErrorReporter errorReporter, Types typeUtils)150   private static ImmutableMap<String, TypeMirror> rewriteParameterTypes(
151       Executable executable,
152       TypeElement builderType,
153       ErrorReporter errorReporter,
154       Types typeUtils) {
155     ImmutableList<TypeParameterElement> executableTypeParams = executable.typeParameters();
156     List<? extends TypeParameterElement> builderTypeParams = builderType.getTypeParameters();
157     if (!BuilderSpec.sameTypeParameters(executableTypeParams, builderTypeParams)) {
158       errorReporter.abortWithError(
159           builderType,
160           "[AutoBuilderTypeParams] Builder type parameters %s must match type parameters %s of %s",
161           TypeEncoder.typeParametersString(builderTypeParams),
162           TypeEncoder.typeParametersString(executableTypeParams),
163           executable);
164     }
165     if (executableTypeParams.isEmpty()) {
166       // Optimization for a common case. No point in doing all that type visiting if we have no
167       // variables to substitute.
168       return executable.parameters().stream()
169           .collect(toImmutableMap(v -> v.getSimpleName().toString(), Element::asType));
170     }
171     Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables = new LinkedHashMap<>();
172     for (int i = 0; i < executableTypeParams.size(); i++) {
173       TypeVariable from = MoreTypes.asTypeVariable(executableTypeParams.get(i).asType());
174       TypeVariable to = MoreTypes.asTypeVariable(builderTypeParams.get(i).asType());
175       typeVariables.put(MoreTypes.equivalence().wrap(from), to);
176     }
177     Function<TypeVariable, TypeMirror> substitute =
178         v -> typeVariables.get(MoreTypes.equivalence().wrap(v));
179     return executable.parameters().stream()
180         .collect(
181             toImmutableMap(
182                 v -> v.getSimpleName().toString(),
183                 v -> TypeVariables.substituteTypeVariables(v.asType(), substitute, typeUtils)));
184   }
185 
186   @Override
propertyForBuilderGetter(ExecutableElement method)187   Optional<String> propertyForBuilderGetter(ExecutableElement method) {
188     String methodName = method.getSimpleName().toString();
189     if (paramToPropertyName.containsValue(methodName)) {
190       return Optional.of(methodName);
191     }
192     if (AutoValueishProcessor.isPrefixedGetter(method)) {
193       int prefixLength = methodName.startsWith("get") ? 3 : 2; // "get" or "is"
194       String unprefixed = methodName.substring(prefixLength);
195       String propertyName = PropertyNames.decapitalizeLikeJavaBeans(unprefixed);
196       if (paramToPropertyName.containsValue(propertyName)) {
197         return Optional.of(propertyName);
198       }
199       propertyName = PropertyNames.decapitalizeNormally(unprefixed);
200       if (paramToPropertyName.containsValue(propertyName)) {
201         return Optional.of(propertyName);
202       }
203     }
204     return Optional.empty();
205   }
206 
207   @Override
checkForFailedJavaBean(ExecutableElement rejectedSetter)208   void checkForFailedJavaBean(ExecutableElement rejectedSetter) {}
209 
210   @Override
propertyElements()211   ImmutableBiMap<String, VariableElement> propertyElements() {
212     return paramToPropertyName.inverse();
213   }
214 
215   @Override
originalPropertyType(VariableElement propertyElement)216   TypeMirror originalPropertyType(VariableElement propertyElement) {
217     return propertyElement.asType();
218   }
219 
220   @Override
propertyString(VariableElement propertyElement)221   String propertyString(VariableElement propertyElement) {
222     return "parameter \"" + propertyElement.getSimpleName() + "\" of " + executable;
223   }
224 
225   @Override
autoWhat()226   String autoWhat() {
227     return "AutoBuilder";
228   }
229 
230   @Override
getterMustMatch()231   String getterMustMatch() {
232     return "a parameter of " + executable;
233   }
234 
235   @Override
fooBuilderMustMatch()236   String fooBuilderMustMatch() {
237     return "foo";
238   }
239 }
240