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