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.MoreElements.getLocalAndInheritedMethods; 19 import static com.google.auto.common.MoreElements.getPackage; 20 import static com.google.auto.common.MoreStreams.toImmutableList; 21 import static com.google.auto.common.MoreStreams.toImmutableSet; 22 import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION; 23 import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; 24 import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME; 25 import static java.util.stream.Collectors.joining; 26 import static java.util.stream.Collectors.toCollection; 27 import static java.util.stream.Collectors.toMap; 28 import static javax.lang.model.util.ElementFilter.constructorsIn; 29 import static javax.lang.model.util.ElementFilter.methodsIn; 30 31 import com.google.auto.common.AnnotationMirrors; 32 import com.google.auto.common.AnnotationValues; 33 import com.google.auto.common.MoreElements; 34 import com.google.auto.common.MoreTypes; 35 import com.google.auto.common.Visibility; 36 import com.google.auto.service.AutoService; 37 import com.google.auto.value.processor.BuilderSpec.PropertyGetter; 38 import com.google.auto.value.processor.MissingTypes.MissingTypeException; 39 import com.google.common.base.Ascii; 40 import com.google.common.base.VerifyException; 41 import com.google.common.collect.ImmutableList; 42 import com.google.common.collect.ImmutableMap; 43 import com.google.common.collect.ImmutableSet; 44 import com.google.common.collect.Maps; 45 import java.lang.reflect.Field; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.NavigableSet; 49 import java.util.Optional; 50 import java.util.Set; 51 import java.util.TreeSet; 52 import java.util.stream.Stream; 53 import javax.annotation.processing.ProcessingEnvironment; 54 import javax.annotation.processing.Processor; 55 import javax.annotation.processing.SupportedAnnotationTypes; 56 import javax.lang.model.element.AnnotationMirror; 57 import javax.lang.model.element.AnnotationValue; 58 import javax.lang.model.element.Element; 59 import javax.lang.model.element.ElementKind; 60 import javax.lang.model.element.ExecutableElement; 61 import javax.lang.model.element.Modifier; 62 import javax.lang.model.element.PackageElement; 63 import javax.lang.model.element.TypeElement; 64 import javax.lang.model.element.VariableElement; 65 import javax.lang.model.type.TypeKind; 66 import javax.lang.model.type.TypeMirror; 67 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 68 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; 69 70 /** 71 * Javac annotation processor (compiler plugin) for builders; user code never references this class. 72 * 73 * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a> 74 * @author Éamonn McManus 75 */ 76 @AutoService(Processor.class) 77 @SupportedAnnotationTypes(AUTO_BUILDER_NAME) 78 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) 79 public class AutoBuilderProcessor extends AutoValueishProcessor { 80 private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable"; 81 AutoBuilderProcessor()82 public AutoBuilderProcessor() { 83 super(AUTO_BUILDER_NAME, /* appliesToInterfaces= */ true); 84 } 85 86 @Override getSupportedOptions()87 public Set<String> getSupportedOptions() { 88 return ImmutableSet.of(OMIT_IDENTIFIERS_OPTION, ALLOW_OPTION); 89 } 90 91 private TypeMirror javaLangVoid; 92 93 @Override init(ProcessingEnvironment processingEnv)94 public synchronized void init(ProcessingEnvironment processingEnv) { 95 super.init(processingEnv); 96 javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType(); 97 } 98 99 @Override processType(TypeElement autoBuilderType)100 void processType(TypeElement autoBuilderType) { 101 if (processingEnv.getOptions().containsKey(ALLOW_OPTION)) { 102 errorReporter().reportWarning(autoBuilderType, "The -A%s option is obsolete", ALLOW_OPTION); 103 } 104 // The annotation is guaranteed to be present by the contract of Processor#process 105 AnnotationMirror autoBuilderAnnotation = 106 getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get(); 107 TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation); 108 checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass"); 109 String callMethod = findCallMethodValue(autoBuilderAnnotation); 110 ImmutableSet<ExecutableElement> methods = 111 abstractMethodsIn( 112 getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils())); 113 ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods); 114 BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter()); 115 BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType); 116 TypeMirror builtType = builtType(executable); 117 Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier = 118 BuilderMethodClassifierForAutoBuilder.classify( 119 methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType); 120 if (!maybeClassifier.isPresent() || errorReporter().errorCount() > 0) { 121 // We've already output one or more error messages. 122 return; 123 } 124 BuilderMethodClassifier<VariableElement> classifier = maybeClassifier.get(); 125 Map<String, String> propertyToGetterName = 126 Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName); 127 AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars(); 128 vars.props = propertySet(autoBuilderType, executable, propertyToGetterName); 129 builder.defineVars(vars, classifier); 130 vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); 131 String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_"); 132 vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName); 133 vars.builtType = TypeEncoder.encode(builtType); 134 vars.build = build(executable); 135 vars.types = typeUtils(); 136 vars.toBuilderConstructor = false; 137 vars.toBuilderMethods = ImmutableList.of(); 138 defineSharedVarsForType(autoBuilderType, ImmutableSet.of(), vars); 139 String text = vars.toText(); 140 text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType()); 141 text = Reformatter.fixup(text); 142 writeSourceFile(generatedClassName, text, autoBuilderType); 143 } 144 propertySet( TypeElement autoBuilderType, ExecutableElement executable, Map<String, String> propertyToGetterName)145 private ImmutableSet<Property> propertySet( 146 TypeElement autoBuilderType, 147 ExecutableElement executable, 148 Map<String, String> propertyToGetterName) { 149 boolean autoAnnotation = 150 MoreElements.getAnnotationMirror(executable, AUTO_ANNOTATION_NAME).isPresent(); 151 ImmutableMap<String, String> builderInitializers = 152 autoAnnotation 153 ? autoAnnotationInitializers(autoBuilderType, executable) 154 : ImmutableMap.of(); 155 // Fix any parameter names that are reserved words in Java. Java source code can't have 156 // such parameter names, but Kotlin code might, for example. 157 Map<VariableElement, String> identifiers = 158 executable.getParameters().stream() 159 .collect(toMap(v -> v, v -> v.getSimpleName().toString())); 160 fixReservedIdentifiers(identifiers); 161 return executable.getParameters().stream() 162 .map( 163 v -> { 164 String name = v.getSimpleName().toString(); 165 return newProperty( 166 v, 167 identifiers.get(v), 168 propertyToGetterName.get(name), 169 Optional.ofNullable(builderInitializers.get(name))); 170 }) 171 .collect(toImmutableSet()); 172 } 173 newProperty( VariableElement var, String identifier, String getterName, Optional<String> builderInitializer)174 private Property newProperty( 175 VariableElement var, 176 String identifier, 177 String getterName, 178 Optional<String> builderInitializer) { 179 String name = var.getSimpleName().toString(); 180 TypeMirror type = var.asType(); 181 Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType()); 182 return new Property( 183 name, 184 identifier, 185 TypeEncoder.encode(type), 186 type, 187 nullableAnnotation, 188 getterName, 189 builderInitializer); 190 } 191 autoAnnotationInitializers( TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod)192 private ImmutableMap<String, String> autoAnnotationInitializers( 193 TypeElement autoBuilderType, ExecutableElement autoAnnotationMethod) { 194 // We expect the return type of an @AutoAnnotation method to be an annotation type. If it isn't, 195 // AutoAnnotation will presumably complain, so we don't need to complain further. 196 TypeMirror returnType = autoAnnotationMethod.getReturnType(); 197 if (!returnType.getKind().equals(TypeKind.DECLARED)) { 198 return ImmutableMap.of(); 199 } 200 // This might not actually be an annotation (if the code is wrong), but if that's the case we 201 // just won't see any contained ExecutableElement where getDefaultValue() returns something. 202 TypeElement annotation = MoreTypes.asTypeElement(returnType); 203 ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); 204 for (ExecutableElement method : methodsIn(annotation.getEnclosedElements())) { 205 AnnotationValue defaultValue = method.getDefaultValue(); 206 if (defaultValue != null) { 207 String memberName = method.getSimpleName().toString(); 208 builder.put( 209 memberName, 210 AnnotationOutput.sourceFormForInitializer( 211 defaultValue, processingEnv, memberName, autoBuilderType)); 212 } 213 } 214 return builder.build(); 215 } 216 findExecutable( TypeElement ofClass, String callMethod, TypeElement autoBuilderType, ImmutableSet<ExecutableElement> methods)217 private ExecutableElement findExecutable( 218 TypeElement ofClass, 219 String callMethod, 220 TypeElement autoBuilderType, 221 ImmutableSet<ExecutableElement> methods) { 222 List<ExecutableElement> executables = 223 findRelevantExecutables(ofClass, callMethod, autoBuilderType); 224 String description = 225 callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\""; 226 switch (executables.size()) { 227 case 0: 228 throw errorReporter() 229 .abortWithError( 230 autoBuilderType, 231 "[AutoBuilderNoVisible] No visible %s for %s", 232 description, 233 ofClass); 234 case 1: 235 return executables.get(0); 236 default: 237 return matchingExecutable(autoBuilderType, executables, methods, description); 238 } 239 } 240 findRelevantExecutables( TypeElement ofClass, String callMethod, TypeElement autoBuilderType)241 private ImmutableList<ExecutableElement> findRelevantExecutables( 242 TypeElement ofClass, String callMethod, TypeElement autoBuilderType) { 243 List<? extends Element> elements = ofClass.getEnclosedElements(); 244 Stream<ExecutableElement> relevantExecutables = 245 callMethod.isEmpty() 246 ? constructorsIn(elements).stream() 247 : methodsIn(elements).stream() 248 .filter(m -> m.getSimpleName().contentEquals(callMethod)) 249 .filter(m -> m.getModifiers().contains(Modifier.STATIC)); 250 return relevantExecutables 251 .filter(c -> visibleFrom(c, getPackage(autoBuilderType))) 252 .collect(toImmutableList()); 253 } 254 matchingExecutable( TypeElement autoBuilderType, List<ExecutableElement> executables, ImmutableSet<ExecutableElement> methods, String description)255 private ExecutableElement matchingExecutable( 256 TypeElement autoBuilderType, 257 List<ExecutableElement> executables, 258 ImmutableSet<ExecutableElement> methods, 259 String description) { 260 // There's more than one visible executable (constructor or method). We try to find the one that 261 // corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're 262 // basically just looking for an executable where all the parameter names correspond to setters 263 // or property builders in the interface. We might find out after choosing one that it is wrong 264 // for whatever reason (types don't match, spurious methods, etc). But it is likely that if the 265 // names are all accounted for in the methods, and if there's no other matching executable with 266 // more parameters, then this is indeed the one we want. If we later get errors when we try to 267 // analyze the interface in detail, those are probably legitimate errors and not because we 268 // picked the wrong executable. 269 ImmutableList<ExecutableElement> matches = 270 executables.stream().filter(x -> executableMatches(x, methods)).collect(toImmutableList()); 271 switch (matches.size()) { 272 case 0: 273 throw errorReporter() 274 .abortWithError( 275 autoBuilderType, 276 "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of" 277 + " any %s:\n%s", 278 description, 279 executableListString(executables)); 280 case 1: 281 return matches.get(0); 282 default: 283 // More than one match, let's see if we can find the best one. 284 } 285 int max = matches.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt(); 286 ImmutableList<ExecutableElement> maxMatches = 287 matches.stream().filter(c -> c.getParameters().size() == max).collect(toImmutableList()); 288 if (maxMatches.size() > 1) { 289 throw errorReporter() 290 .abortWithError( 291 autoBuilderType, 292 "[AutoBuilderAmbiguous] Property names correspond to more than one %s:\n%s", 293 description, 294 executableListString(maxMatches)); 295 } 296 return maxMatches.get(0); 297 } 298 executableListString(List<ExecutableElement> executables)299 private String executableListString(List<ExecutableElement> executables) { 300 return executables.stream() 301 .map(AutoBuilderProcessor::executableString) 302 .collect(joining("\n ", " ", "")); 303 } 304 executableString(ExecutableElement executable)305 static String executableString(ExecutableElement executable) { 306 Element nameSource = 307 executable.getKind() == ElementKind.CONSTRUCTOR 308 ? executable.getEnclosingElement() 309 : executable; 310 return nameSource.getSimpleName() 311 + executable.getParameters().stream() 312 .map(v -> v.asType() + " " + v.getSimpleName()) 313 .collect(joining(", ", "(", ")")); 314 } 315 executableMatches( ExecutableElement executable, ImmutableSet<ExecutableElement> methods)316 private boolean executableMatches( 317 ExecutableElement executable, ImmutableSet<ExecutableElement> methods) { 318 // Start with the complete set of parameter names and remove them one by one as we find 319 // corresponding methods. We ignore case, under the assumption that it is unlikely that a case 320 // difference is going to allow a candidate to match when another one is better. 321 // A parameter named foo could be matched by methods like this: 322 // X foo(Y) 323 // X setFoo(Y) 324 // X fooBuilder() 325 // X fooBuilder(Y) 326 // There are further constraints, including on the types X and Y, that will later be imposed by 327 // BuilderMethodClassifier, but here we just require that there be at least one method with 328 // one of these shapes for foo. 329 NavigableSet<String> parameterNames = 330 executable.getParameters().stream() 331 .map(v -> v.getSimpleName().toString()) 332 .collect(toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))); 333 for (ExecutableElement method : methods) { 334 String name = method.getSimpleName().toString(); 335 if (name.endsWith("Builder")) { 336 String property = name.substring(0, name.length() - "Builder".length()); 337 parameterNames.remove(property); 338 } 339 if (method.getParameters().size() == 1) { 340 parameterNames.remove(name); 341 if (name.startsWith("set")) { 342 parameterNames.remove(name.substring(3)); 343 } 344 } 345 if (parameterNames.isEmpty()) { 346 return true; 347 } 348 } 349 return false; 350 } 351 visibleFrom(Element element, PackageElement fromPackage)352 private boolean visibleFrom(Element element, PackageElement fromPackage) { 353 Visibility visibility = Visibility.effectiveVisibilityOfElement(element); 354 switch (visibility) { 355 case PUBLIC: 356 return true; 357 case PROTECTED: 358 // We care about whether the constructor is visible from the generated class. The generated 359 // class is never going to be a subclass of the class containing the constructor, so 360 // protected and default access are equivalent. 361 case DEFAULT: 362 return getPackage(element).equals(fromPackage); 363 default: 364 return false; 365 } 366 } 367 builtType(ExecutableElement executable)368 private TypeMirror builtType(ExecutableElement executable) { 369 switch (executable.getKind()) { 370 case CONSTRUCTOR: 371 return executable.getEnclosingElement().asType(); 372 case METHOD: 373 return executable.getReturnType(); 374 default: 375 throw new VerifyException("Unexpected executable kind " + executable.getKind()); 376 } 377 } 378 build(ExecutableElement executable)379 private String build(ExecutableElement executable) { 380 TypeElement enclosing = MoreElements.asType(executable.getEnclosingElement()); 381 String type = TypeEncoder.encodeRaw(enclosing.asType()); 382 switch (executable.getKind()) { 383 case CONSTRUCTOR: 384 boolean generic = !enclosing.getTypeParameters().isEmpty(); 385 String typeParams = generic ? "<>" : ""; 386 return "new " + type + typeParams; 387 case METHOD: 388 return type + "." + executable.getSimpleName(); 389 default: 390 throw new VerifyException("Unexpected executable kind " + executable.getKind()); 391 } 392 } 393 394 private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord(); 395 elementKindRecord()396 private static ElementKind elementKindRecord() { 397 try { 398 Field record = ElementKind.class.getField("RECORD"); 399 return (ElementKind) record.get(null); 400 } catch (ReflectiveOperationException e) { 401 // OK: we must be on a JDK version that predates this. 402 return null; 403 } 404 } 405 getOfClass( TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation)406 private TypeElement getOfClass( 407 TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation) { 408 TypeElement ofClassValue = findOfClassValue(autoBuilderAnnotation); 409 boolean isDefault = typeUtils().isSameType(ofClassValue.asType(), javaLangVoid); 410 if (!isDefault) { 411 return ofClassValue; 412 } 413 Element enclosing = autoBuilderType.getEnclosingElement(); 414 ElementKind enclosingKind = enclosing.getKind(); 415 if (enclosing.getKind() != ElementKind.CLASS && enclosingKind != ELEMENT_KIND_RECORD) { 416 errorReporter() 417 .abortWithError( 418 autoBuilderType, 419 "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it" 420 + " must be nested inside the class to be built; actually nested inside %s %s.", 421 Ascii.toLowerCase(enclosingKind.name()), 422 enclosing); 423 } 424 return MoreElements.asType(enclosing); 425 } 426 findOfClassValue(AnnotationMirror autoBuilderAnnotation)427 private TypeElement findOfClassValue(AnnotationMirror autoBuilderAnnotation) { 428 AnnotationValue ofClassValue = 429 AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "ofClass"); 430 Object value = ofClassValue.getValue(); 431 if (value instanceof TypeMirror) { 432 TypeMirror ofClassType = (TypeMirror) value; 433 switch (ofClassType.getKind()) { 434 case DECLARED: 435 return MoreTypes.asTypeElement(ofClassType); 436 case ERROR: 437 throw new MissingTypeException(MoreTypes.asError(ofClassType)); 438 default: 439 break; 440 } 441 } 442 throw new MissingTypeException(null); 443 } 444 findCallMethodValue(AnnotationMirror autoBuilderAnnotation)445 private String findCallMethodValue(AnnotationMirror autoBuilderAnnotation) { 446 AnnotationValue callMethodValue = 447 AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "callMethod"); 448 return AnnotationValues.getString(callMethodValue); 449 } 450 451 @Override nullableAnnotationForMethod(ExecutableElement propertyMethod)452 Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) { 453 // TODO(b/183005059): implement 454 return Optional.empty(); 455 } 456 } 457