1 /* 2 * Copyright 2014 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.GeneratedAnnotations.generatedAnnotation; 19 import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; 20 21 import com.google.auto.common.MoreElements; 22 import com.google.auto.common.SuperficialValidation; 23 import com.google.auto.service.AutoService; 24 import com.google.common.base.Preconditions; 25 import com.google.common.base.Throwables; 26 import com.google.common.collect.ImmutableMap; 27 import com.google.common.collect.ImmutableSet; 28 import com.google.common.primitives.Primitives; 29 import com.google.errorprone.annotations.FormatMethod; 30 import java.io.IOException; 31 import java.io.Writer; 32 import java.util.Collection; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Optional; 37 import java.util.Set; 38 import javax.annotation.processing.AbstractProcessor; 39 import javax.annotation.processing.ProcessingEnvironment; 40 import javax.annotation.processing.Processor; 41 import javax.annotation.processing.RoundEnvironment; 42 import javax.annotation.processing.SupportedAnnotationTypes; 43 import javax.lang.model.SourceVersion; 44 import javax.lang.model.element.AnnotationValue; 45 import javax.lang.model.element.Element; 46 import javax.lang.model.element.ElementKind; 47 import javax.lang.model.element.ExecutableElement; 48 import javax.lang.model.element.Modifier; 49 import javax.lang.model.element.TypeElement; 50 import javax.lang.model.element.VariableElement; 51 import javax.lang.model.type.ArrayType; 52 import javax.lang.model.type.DeclaredType; 53 import javax.lang.model.type.PrimitiveType; 54 import javax.lang.model.type.TypeKind; 55 import javax.lang.model.type.TypeMirror; 56 import javax.lang.model.type.WildcardType; 57 import javax.lang.model.util.ElementFilter; 58 import javax.lang.model.util.Elements; 59 import javax.lang.model.util.Types; 60 import javax.tools.Diagnostic; 61 import javax.tools.JavaFileObject; 62 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 63 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; 64 65 /** 66 * Javac annotation processor (compiler plugin) to generate annotation implementations. User code 67 * never references this class. 68 * 69 * @author emcmanus@google.com (Éamonn McManus) 70 */ 71 @AutoService(Processor.class) 72 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) 73 @SupportedAnnotationTypes(AUTO_ANNOTATION_NAME) 74 public class AutoAnnotationProcessor extends AbstractProcessor { AutoAnnotationProcessor()75 public AutoAnnotationProcessor() {} 76 77 @Override getSupportedSourceVersion()78 public SourceVersion getSupportedSourceVersion() { 79 return SourceVersion.latestSupported(); 80 } 81 82 /** 83 * Issue a compilation error. This method does not throw an exception, since we want to continue 84 * processing and perhaps report other errors. 85 */ 86 @FormatMethod reportError(Element e, String msg, Object... msgParams)87 private void reportError(Element e, String msg, Object... msgParams) { 88 String formattedMessage = String.format(msg, msgParams); 89 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, formattedMessage, e); 90 } 91 92 /** 93 * Issue a compilation error and return an exception that, when thrown, will cause the processing 94 * of this class to be abandoned. This does not prevent the processing of other classes. 95 */ 96 @FormatMethod abortWithError(Element e, String msg, Object... msgParams)97 private AbortProcessingException abortWithError(Element e, String msg, Object... msgParams) { 98 reportError(e, msg, msgParams); 99 return new AbortProcessingException(); 100 } 101 102 private Elements elementUtils; 103 private Types typeUtils; 104 105 @Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)106 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 107 elementUtils = processingEnv.getElementUtils(); 108 typeUtils = processingEnv.getTypeUtils(); 109 boolean claimed = 110 (annotations.size() == 1 111 && annotations 112 .iterator() 113 .next() 114 .getQualifiedName() 115 .contentEquals(AUTO_ANNOTATION_NAME)); 116 if (claimed) { 117 process(roundEnv); 118 return true; 119 } else { 120 return false; 121 } 122 } 123 process(RoundEnvironment roundEnv)124 private void process(RoundEnvironment roundEnv) { 125 TypeElement autoAnnotation = elementUtils.getTypeElement(AUTO_ANNOTATION_NAME); 126 Collection<? extends Element> annotatedElements = 127 roundEnv.getElementsAnnotatedWith(autoAnnotation); 128 List<ExecutableElement> methods = ElementFilter.methodsIn(annotatedElements); 129 if (!SuperficialValidation.validateElements(methods) || methodsAreOverloaded(methods)) { 130 return; 131 } 132 for (ExecutableElement method : methods) { 133 try { 134 processMethod(method); 135 } catch (AbortProcessingException e) { 136 // We abandoned this type, but continue with the next. 137 } catch (RuntimeException e) { 138 String trace = Throwables.getStackTraceAsString(e); 139 reportError(method, "@AutoAnnotation processor threw an exception: %s", trace); 140 throw e; 141 } 142 } 143 } 144 processMethod(ExecutableElement method)145 private void processMethod(ExecutableElement method) { 146 if (!method.getModifiers().contains(Modifier.STATIC)) { 147 throw abortWithError(method, "@AutoAnnotation method must be static"); 148 } 149 150 TypeElement annotationElement = getAnnotationReturnType(method); 151 152 Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method); 153 154 ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement); 155 TypeElement methodClass = (TypeElement) method.getEnclosingElement(); 156 String pkg = TypeSimplifier.packageNameOf(methodClass); 157 158 ImmutableMap<String, AnnotationValue> defaultValues = getDefaultValues(annotationElement); 159 ImmutableMap<String, Member> members = getMembers(method, memberMethods); 160 ImmutableMap<String, Parameter> parameters = getParameters(annotationElement, method, members); 161 validateParameters(annotationElement, method, members, parameters, defaultValues); 162 163 String generatedClassName = generatedClassName(method); 164 165 AutoAnnotationTemplateVars vars = new AutoAnnotationTemplateVars(); 166 vars.annotationFullName = annotationElement.toString(); 167 vars.annotationName = TypeEncoder.encode(annotationElement.asType()); 168 vars.className = generatedClassName; 169 vars.generated = getGeneratedTypeName(); 170 vars.members = members; 171 vars.params = parameters; 172 vars.pkg = pkg; 173 vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections; 174 vars.gwtCompatible = isGwtCompatible(annotationElement); 175 ImmutableMap<String, Integer> invariableHashes = invariableHashes(members, parameters.keySet()); 176 vars.invariableHashSum = 0; 177 for (int h : invariableHashes.values()) { 178 vars.invariableHashSum += h; 179 } 180 vars.invariableHashes = invariableHashes.keySet(); 181 String text = vars.toText(); 182 text = TypeEncoder.decode(text, processingEnv, pkg, annotationElement.asType()); 183 text = Reformatter.fixup(text); 184 String fullName = fullyQualifiedName(pkg, generatedClassName); 185 writeSourceFile(fullName, text, methodClass); 186 } 187 getGeneratedTypeName()188 private String getGeneratedTypeName() { 189 return generatedAnnotation(elementUtils, processingEnv.getSourceVersion()) 190 .map(generatedAnnotation -> TypeEncoder.encode(generatedAnnotation.asType())) 191 .orElse(""); 192 } 193 194 /** 195 * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always 196 * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or 197 * an enum constant does potentially change in different runs of the same program. The hashCode of 198 * an array doesn't change if the hashCodes of its elements don't. Although we could have a 199 * similar rule for nested annotation values, we currently don't. 200 */ invariableHash(AnnotationValue annotationValue)201 private static Optional<Integer> invariableHash(AnnotationValue annotationValue) { 202 Object value = annotationValue.getValue(); 203 if (value instanceof String || Primitives.isWrapperType(value.getClass())) { 204 return Optional.of(value.hashCode()); 205 } else if (value instanceof List<?>) { 206 @SuppressWarnings("unchecked") // by specification 207 List<? extends AnnotationValue> list = (List<? extends AnnotationValue>) value; 208 return invariableHash(list); 209 } else { 210 return Optional.empty(); 211 } 212 } 213 invariableHash( List<? extends AnnotationValue> annotationValues)214 private static Optional<Integer> invariableHash( 215 List<? extends AnnotationValue> annotationValues) { 216 int h = 1; 217 for (AnnotationValue annotationValue : annotationValues) { 218 Optional<Integer> maybeHash = invariableHash(annotationValue); 219 if (!maybeHash.isPresent()) { 220 return Optional.empty(); 221 } 222 h = h * 31 + maybeHash.get(); 223 } 224 return Optional.of(h); 225 } 226 227 /** 228 * Returns a map from the names of members with invariable hashCodes to the values of those 229 * hashCodes. 230 */ invariableHashes( ImmutableMap<String, Member> members, ImmutableSet<String> parameters)231 private static ImmutableMap<String, Integer> invariableHashes( 232 ImmutableMap<String, Member> members, ImmutableSet<String> parameters) { 233 ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder(); 234 for (String element : members.keySet()) { 235 if (!parameters.contains(element)) { 236 Member member = members.get(element); 237 AnnotationValue annotationValue = member.method.getDefaultValue(); 238 Optional<Integer> invariableHash = invariableHash(annotationValue); 239 if (invariableHash.isPresent()) { 240 builder.put(element, (element.hashCode() * 127) ^ invariableHash.get()); 241 } 242 } 243 } 244 return builder.build(); 245 } 246 methodsAreOverloaded(List<ExecutableElement> methods)247 private boolean methodsAreOverloaded(List<ExecutableElement> methods) { 248 boolean overloaded = false; 249 Set<String> classNames = new HashSet<String>(); 250 for (ExecutableElement method : methods) { 251 String qualifiedClassName = 252 fullyQualifiedName( 253 MoreElements.getPackage(method).getQualifiedName().toString(), 254 generatedClassName(method)); 255 if (!classNames.add(qualifiedClassName)) { 256 overloaded = true; 257 reportError(method, "@AutoAnnotation methods cannot be overloaded"); 258 } 259 } 260 return overloaded; 261 } 262 generatedClassName(ExecutableElement method)263 private String generatedClassName(ExecutableElement method) { 264 TypeElement type = (TypeElement) method.getEnclosingElement(); 265 String name = type.getSimpleName().toString(); 266 while (type.getEnclosingElement() instanceof TypeElement) { 267 type = (TypeElement) type.getEnclosingElement(); 268 name = type.getSimpleName() + "_" + name; 269 } 270 return "AutoAnnotation_" + name + "_" + method.getSimpleName(); 271 } 272 getAnnotationReturnType(ExecutableElement method)273 private TypeElement getAnnotationReturnType(ExecutableElement method) { 274 TypeMirror returnTypeMirror = method.getReturnType(); 275 if (returnTypeMirror.getKind() == TypeKind.DECLARED) { 276 Element returnTypeElement = typeUtils.asElement(method.getReturnType()); 277 if (returnTypeElement.getKind() == ElementKind.ANNOTATION_TYPE) { 278 return (TypeElement) returnTypeElement; 279 } 280 } 281 throw abortWithError( 282 method, 283 "Return type of @AutoAnnotation method must be an annotation type, not %s", 284 returnTypeMirror); 285 } 286 getMemberMethods(TypeElement annotationElement)287 private ImmutableMap<String, ExecutableElement> getMemberMethods(TypeElement annotationElement) { 288 ImmutableMap.Builder<String, ExecutableElement> members = ImmutableMap.builder(); 289 for (ExecutableElement member : 290 ElementFilter.methodsIn(annotationElement.getEnclosedElements())) { 291 String name = member.getSimpleName().toString(); 292 members.put(name, member); 293 } 294 return members.build(); 295 } 296 getMembers( Element context, ImmutableMap<String, ExecutableElement> memberMethods)297 private ImmutableMap<String, Member> getMembers( 298 Element context, ImmutableMap<String, ExecutableElement> memberMethods) { 299 ImmutableMap.Builder<String, Member> members = ImmutableMap.builder(); 300 for (Map.Entry<String, ExecutableElement> entry : memberMethods.entrySet()) { 301 ExecutableElement memberMethod = entry.getValue(); 302 String name = memberMethod.getSimpleName().toString(); 303 members.put(name, new Member(processingEnv, context, memberMethod)); 304 } 305 return members.build(); 306 } 307 getDefaultValues(TypeElement annotationElement)308 private ImmutableMap<String, AnnotationValue> getDefaultValues(TypeElement annotationElement) { 309 ImmutableMap.Builder<String, AnnotationValue> defaultValues = ImmutableMap.builder(); 310 for (ExecutableElement member : 311 ElementFilter.methodsIn(annotationElement.getEnclosedElements())) { 312 String name = member.getSimpleName().toString(); 313 AnnotationValue defaultValue = member.getDefaultValue(); 314 if (defaultValue != null) { 315 defaultValues.put(name, defaultValue); 316 } 317 } 318 return defaultValues.build(); 319 } 320 getParameters( TypeElement annotationElement, ExecutableElement method, Map<String, Member> members)321 private ImmutableMap<String, Parameter> getParameters( 322 TypeElement annotationElement, ExecutableElement method, Map<String, Member> members) { 323 ImmutableMap.Builder<String, Parameter> parameters = ImmutableMap.builder(); 324 boolean error = false; 325 for (VariableElement parameter : method.getParameters()) { 326 String name = parameter.getSimpleName().toString(); 327 Member member = members.get(name); 328 if (member == null) { 329 reportError( 330 parameter, 331 "@AutoAnnotation method parameter '%s' must have the same name as a member of %s", 332 name, 333 annotationElement); 334 error = true; 335 } else { 336 TypeMirror parameterType = parameter.asType(); 337 TypeMirror memberType = member.getTypeMirror(); 338 if (compatibleTypes(parameterType, memberType)) { 339 parameters.put(name, new Parameter(parameterType)); 340 } else { 341 reportError( 342 parameter, 343 "@AutoAnnotation method parameter '%s' has type %s but %s.%s has type %s", 344 name, 345 parameterType, 346 annotationElement, 347 name, 348 memberType); 349 error = true; 350 } 351 } 352 } 353 if (error) { 354 throw new AbortProcessingException(); 355 } 356 return parameters.build(); 357 } 358 validateParameters( TypeElement annotationElement, ExecutableElement method, ImmutableMap<String, Member> members, ImmutableMap<String, Parameter> parameters, ImmutableMap<String, AnnotationValue> defaultValues)359 private void validateParameters( 360 TypeElement annotationElement, 361 ExecutableElement method, 362 ImmutableMap<String, Member> members, 363 ImmutableMap<String, Parameter> parameters, 364 ImmutableMap<String, AnnotationValue> defaultValues) { 365 boolean error = false; 366 for (String memberName : members.keySet()) { 367 if (!parameters.containsKey(memberName) && !defaultValues.containsKey(memberName)) { 368 reportError( 369 method, 370 "@AutoAnnotation method needs a parameter with name '%s' and type %s" 371 + " corresponding to %s.%s, which has no default value", 372 memberName, 373 members.get(memberName).getType(), 374 annotationElement, 375 memberName); 376 error = true; 377 } 378 } 379 if (error) { 380 throw new AbortProcessingException(); 381 } 382 } 383 384 /** 385 * Returns true if {@code parameterType} can be used to provide the value of an annotation member 386 * of type {@code memberType}. They must either be the same type, or the member type must be an 387 * array and the parameter type must be a collection of a compatible type. 388 */ compatibleTypes(TypeMirror parameterType, TypeMirror memberType)389 private boolean compatibleTypes(TypeMirror parameterType, TypeMirror memberType) { 390 if (typeUtils.isAssignable(parameterType, memberType)) { 391 // parameterType assignable to memberType, which in the restricted world of annotations 392 // means they are the same type, or maybe memberType is an annotation type and parameterType 393 // is a subtype of that annotation interface (why would you do that?). 394 return true; 395 } 396 // They're not the same, but we could still consider them compatible if for example 397 // parameterType is List<Integer> and memberType is int[]. We accept any type that is assignable 398 // to Collection<Integer> (in this example). 399 if (memberType.getKind() != TypeKind.ARRAY) { 400 return false; 401 } 402 TypeMirror arrayElementType = ((ArrayType) memberType).getComponentType(); 403 TypeMirror wrappedArrayElementType = 404 arrayElementType.getKind().isPrimitive() 405 ? typeUtils.boxedClass((PrimitiveType) arrayElementType).asType() 406 : arrayElementType; 407 TypeElement javaUtilCollection = 408 elementUtils.getTypeElement(Collection.class.getCanonicalName()); 409 DeclaredType collectionOfElement = 410 typeUtils.getDeclaredType(javaUtilCollection, wrappedArrayElementType); 411 return typeUtils.isAssignable(parameterType, collectionOfElement); 412 } 413 414 /** 415 * Returns the wrapper types ({@code Integer.class} etc) that are used in collection parameters 416 * like {@code List<Integer>}. This is needed because we will emit a helper method for each such 417 * type, for example to convert {@code Collection<Integer>} into {@code int[]}. 418 */ wrapperTypesUsedInCollections(ExecutableElement method)419 private Set<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) { 420 TypeElement javaUtilCollection = elementUtils.getTypeElement(Collection.class.getName()); 421 ImmutableSet.Builder<Class<?>> usedInCollections = ImmutableSet.builder(); 422 for (Class<?> wrapper : Primitives.allWrapperTypes()) { 423 DeclaredType collectionOfWrapper = 424 typeUtils.getDeclaredType(javaUtilCollection, getTypeMirror(wrapper)); 425 for (VariableElement parameter : method.getParameters()) { 426 if (typeUtils.isAssignable(parameter.asType(), collectionOfWrapper)) { 427 usedInCollections.add(wrapper); 428 break; 429 } 430 } 431 } 432 return usedInCollections.build(); 433 } 434 getTypeMirror(Class<?> c)435 private TypeMirror getTypeMirror(Class<?> c) { 436 return elementUtils.getTypeElement(c.getName()).asType(); 437 } 438 isGwtCompatible(TypeElement annotationElement)439 private static boolean isGwtCompatible(TypeElement annotationElement) { 440 return annotationElement 441 .getAnnotationMirrors() 442 .stream() 443 .map(mirror -> mirror.getAnnotationType().asElement()) 444 .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible")); 445 } 446 fullyQualifiedName(String pkg, String cls)447 private static String fullyQualifiedName(String pkg, String cls) { 448 return pkg.isEmpty() ? cls : pkg + "." + cls; 449 } 450 writeSourceFile(String className, String text, TypeElement originatingType)451 private void writeSourceFile(String className, String text, TypeElement originatingType) { 452 try { 453 JavaFileObject sourceFile = 454 processingEnv.getFiler().createSourceFile(className, originatingType); 455 try (Writer writer = sourceFile.openWriter()) { 456 writer.write(text); 457 } 458 } catch (IOException e) { 459 // This should really be an error, but we make it a warning in the hope of resisting Eclipse 460 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599. If that bug manifests, we may get 461 // invoked more than once for the same file, so ignoring the ability to overwrite it is the 462 // right thing to do. If we are unable to write for some other reason, we should get a compile 463 // error later because user code will have a reference to the code we were supposed to 464 // generate (new AutoValue_Foo() or whatever) and that reference will be undefined. 465 processingEnv 466 .getMessager() 467 .printMessage( 468 Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e); 469 } 470 } 471 472 public static class Member { 473 private final ProcessingEnvironment processingEnv; 474 private final Element context; 475 private final ExecutableElement method; 476 Member(ProcessingEnvironment processingEnv, Element context, ExecutableElement method)477 Member(ProcessingEnvironment processingEnv, Element context, ExecutableElement method) { 478 this.processingEnv = processingEnv; 479 this.context = context; 480 this.method = method; 481 } 482 483 @Override toString()484 public String toString() { 485 return method.getSimpleName().toString(); 486 } 487 getType()488 public String getType() { 489 return TypeEncoder.encode(getTypeMirror()); 490 } 491 getComponentType()492 public String getComponentType() { 493 Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY); 494 ArrayType arrayType = (ArrayType) getTypeMirror(); 495 return TypeEncoder.encode(arrayType.getComponentType()); 496 } 497 getTypeMirror()498 public TypeMirror getTypeMirror() { 499 return method.getReturnType(); 500 } 501 getKind()502 public TypeKind getKind() { 503 return getTypeMirror().getKind(); 504 } 505 506 // Used as part of the hashCode() computation. 507 // See https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Annotation.html#hashCode-- getNameHash()508 public int getNameHash() { 509 return 127 * toString().hashCode(); 510 } 511 isArrayOfClassWithBounds()512 public boolean isArrayOfClassWithBounds() { 513 if (getTypeMirror().getKind() != TypeKind.ARRAY) { 514 return false; 515 } 516 TypeMirror componentType = ((ArrayType) getTypeMirror()).getComponentType(); 517 if (componentType.getKind() != TypeKind.DECLARED) { 518 return false; 519 } 520 DeclaredType declared = (DeclaredType) componentType; 521 if (!((TypeElement) processingEnv.getTypeUtils().asElement(componentType)) 522 .getQualifiedName() 523 .contentEquals("java.lang.Class")) { 524 return false; 525 } 526 if (declared.getTypeArguments().size() != 1) { 527 return false; 528 } 529 TypeMirror parameter = declared.getTypeArguments().get(0); 530 if (parameter.getKind() != TypeKind.WILDCARD) { 531 return true; // for Class<Foo> 532 } 533 WildcardType wildcard = (WildcardType) parameter; 534 // In theory, we should check if getExtendsBound() != Object, since '?' is equivalent to 535 // '? extends Object', but, experimentally, neither javac or ecj will sets getExtendsBound() 536 // to 'Object', so there isn't a point in checking. 537 return wildcard.getSuperBound() != null || wildcard.getExtendsBound() != null; 538 } 539 getDefaultValue()540 public String getDefaultValue() { 541 AnnotationValue defaultValue = method.getDefaultValue(); 542 if (defaultValue == null) { 543 return null; 544 } else { 545 return AnnotationOutput.sourceFormForInitializer( 546 defaultValue, processingEnv, method.getSimpleName().toString(), context); 547 } 548 } 549 } 550 551 public static class Parameter { 552 private final String typeName; 553 private final TypeKind kind; 554 Parameter(TypeMirror type)555 Parameter(TypeMirror type) { 556 this.typeName = TypeEncoder.encode(type); 557 this.kind = type.getKind(); 558 } 559 getType()560 public String getType() { 561 return typeName; 562 } 563 getKind()564 public TypeKind getKind() { 565 return kind; 566 } 567 } 568 } 569