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