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