1 /* 2 * Copyright 2017 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.common.base.Preconditions.checkArgument; 19 import static com.google.common.base.Preconditions.checkState; 20 import static java.util.stream.Collectors.toList; 21 22 import com.google.auto.common.MoreElements; 23 import com.google.auto.common.MoreTypes; 24 import com.google.auto.value.processor.MissingTypes.MissingTypeException; 25 import com.google.common.collect.ImmutableSet; 26 import java.util.List; 27 import java.util.OptionalInt; 28 import java.util.Set; 29 import javax.annotation.processing.ProcessingEnvironment; 30 import javax.lang.model.element.AnnotationMirror; 31 import javax.lang.model.element.TypeElement; 32 import javax.lang.model.element.TypeParameterElement; 33 import javax.lang.model.type.ArrayType; 34 import javax.lang.model.type.DeclaredType; 35 import javax.lang.model.type.ErrorType; 36 import javax.lang.model.type.PrimitiveType; 37 import javax.lang.model.type.TypeKind; 38 import javax.lang.model.type.TypeMirror; 39 import javax.lang.model.type.TypeVariable; 40 import javax.lang.model.type.WildcardType; 41 import javax.lang.model.util.Elements; 42 import javax.lang.model.util.SimpleTypeVisitor8; 43 import javax.lang.model.util.Types; 44 45 /** 46 * Encodes types so they can later be decoded to incorporate imports. 47 * 48 * <p>The idea is that types that appear in generated source code use {@link #encode}, which will 49 * spell out a type like {@code java.util.List<? extends java.lang.Number>}, except that wherever a 50 * class name appears it is replaced by a special token. So the spelling might actually be {@code 51 * `java.util.List`<? extends `java.lang.Number`>}. Then once the entire class has been generated, 52 * {@code #decode} scans for these tokens to determine what classes need to be imported, and 53 * replaces the tokens with the correct spelling given the imports. So here, {@code java.util.List} 54 * would be imported, and the final spelling would be {@code List<? extends Number>} (knowing that 55 * {@code Number} is implicitly imported). The special token {@code `import`} marks where the 56 * imports should be, and {@link #decode} replaces it with the correct list of imports. 57 * 58 * <p>The funky syntax for type annotations on qualified type names requires an adjustment to this 59 * scheme. {@code `«java.util.Map`} stands for {@code java.util.} and {@code `»java.util.Map`} 60 * stands for {@code Map}. If {@code java.util.Map} is imported, then {@code `«java.util.Map`} will 61 * eventually be empty, but if {@code java.util.Map} is not imported (perhaps because there is 62 * another {@code Map} in scope) then {@code `«java.util.Map`} will be {@code java.util.}. The end 63 * result is that the code can contain {@code `«java.util.Map`@`javax.annotation.Nullable` 64 * `»java.util.Map`}. That might decode to {@code @Nullable Map} or to {@code java.util.@Nullable 65 * Map} or even to {@code java.util.@javax.annotation.Nullable Map}. 66 * 67 * @author emcmanus@google.com (Éamonn McManus) 68 */ 69 final class TypeEncoder { TypeEncoder()70 private TypeEncoder() {} // There are no instances of this class. 71 72 private static final EncodingTypeVisitor ENCODING_TYPE_VISITOR = new EncodingTypeVisitor(); 73 private static final RawEncodingTypeVisitor RAW_ENCODING_TYPE_VISITOR = 74 new RawEncodingTypeVisitor(); 75 76 /** 77 * Returns the encoding for the given type, where class names are marked by special tokens. The 78 * encoding for {@code int} will be {@code int}, but the encoding for {@code 79 * java.util.List<java.lang.Integer>} will be {@code `java.util.List`<`java.lang.Integer`>}. 80 */ encode(TypeMirror type)81 static String encode(TypeMirror type) { 82 StringBuilder sb = new StringBuilder(); 83 return type.accept(ENCODING_TYPE_VISITOR, sb).toString(); 84 } 85 86 /** 87 * Like {@link #encode}, except that only the raw type is encoded. So if the given type is {@code 88 * java.util.List<java.lang.Integer>} the result will be {@code `java.util.List`}. 89 */ encodeRaw(TypeMirror type)90 static String encodeRaw(TypeMirror type) { 91 StringBuilder sb = new StringBuilder(); 92 return type.accept(RAW_ENCODING_TYPE_VISITOR, sb).toString(); 93 } 94 95 /** 96 * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder} 97 * covers the details of annotation encoding. 98 */ encodeWithAnnotations(TypeMirror type)99 static String encodeWithAnnotations(TypeMirror type) { 100 return encodeWithAnnotations(type, ImmutableSet.of()); 101 } 102 103 /** 104 * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder} 105 * covers the details of annotation encoding. 106 * 107 * @param excludedAnnotationTypes annotations not to include in the encoding. For example, if 108 * {@code com.example.Nullable} is in this set then the encoding will not include this 109 * {@code @Nullable} annotation. 110 */ encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes)111 static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) { 112 StringBuilder sb = new StringBuilder(); 113 return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString(); 114 } 115 116 /** 117 * Decodes the given string, respelling class names appropriately. The text is scanned for tokens 118 * like {@code `java.util.Locale`} or {@code `«java.util.Locale`} to determine which classes are 119 * referenced. An appropriate set of imports is computed based on the set of those types. If the 120 * special token {@code `import`} appears in {@code text} then it will be replaced by this set of 121 * import statements. Then all of the tokens are replaced by the class names they represent, 122 * spelled appropriately given the import statements. 123 * 124 * @param text the text to be decoded. 125 * @param packageName the package of the generated class. Other classes in the same package do not 126 * need to be imported. 127 * @param baseType a class or interface that the generated class inherits from. Nested classes in 128 * that type do not need to be imported, and if another class has the same name as one of 129 * those nested classes then it will need to be qualified. 130 */ decode( String text, ProcessingEnvironment processingEnv, String packageName, TypeMirror baseType)131 static String decode( 132 String text, ProcessingEnvironment processingEnv, String packageName, TypeMirror baseType) { 133 return decode( 134 text, processingEnv.getElementUtils(), processingEnv.getTypeUtils(), packageName, baseType); 135 } 136 decode( String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType)137 static String decode( 138 String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) { 139 TypeRewriter typeRewriter = new TypeRewriter(text, elementUtils, typeUtils, pkg, baseType); 140 return typeRewriter.rewrite(); 141 } 142 className(DeclaredType declaredType)143 private static String className(DeclaredType declaredType) { 144 return MoreElements.asType(declaredType.asElement()).getQualifiedName().toString(); 145 } 146 147 /** 148 * Returns the formal type parameters of the given type. If we have {@code @AutoValue abstract 149 * class Foo<T extends SomeClass>} then this method will return an encoding of {@code <T extends 150 * SomeClass>} for {@code Foo}. Likewise it will return an encoding of the angle-bracket part of: 151 * <br> 152 * {@code Foo<SomeClass>}<br> 153 * {@code Foo<T extends Number>}<br> 154 * {@code Foo<E extends Enum<E>>}<br> 155 * {@code Foo<K, V extends Comparable<? extends K>>}. 156 * 157 * <p>The encoding is simply that classes in the "extends" part are marked, so the examples will 158 * actually look something like this:<br> 159 * {@code <`bar.baz.SomeClass`>}<br> 160 * {@code <T extends `java.lang.Number`>}<br> 161 * {@code <E extends `java.lang.Enum`<E>>}<br> 162 * {@code <K, V extends `java.lang.Comparable`<? extends K>>}. 163 */ formalTypeParametersString(TypeElement type)164 static String formalTypeParametersString(TypeElement type) { 165 List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); 166 if (typeParameters.isEmpty()) { 167 return ""; 168 } else { 169 StringBuilder sb = new StringBuilder("<"); 170 String sep = ""; 171 for (TypeParameterElement typeParameter : typeParameters) { 172 sb.append(sep); 173 sep = ", "; 174 appendTypeParameterWithBounds(typeParameter, sb); 175 } 176 return sb.append(">").toString(); 177 } 178 } 179 appendTypeParameterWithBounds( TypeParameterElement typeParameter, StringBuilder sb)180 private static void appendTypeParameterWithBounds( 181 TypeParameterElement typeParameter, StringBuilder sb) { 182 appendAnnotations(typeParameter.getAnnotationMirrors(), sb); 183 sb.append(typeParameter.getSimpleName()); 184 String sep = " extends "; 185 for (TypeMirror bound : typeParameter.getBounds()) { 186 if (!isUnannotatedJavaLangObject(bound)) { 187 sb.append(sep); 188 sep = " & "; 189 sb.append(encodeWithAnnotations(bound)); 190 } 191 } 192 } 193 194 // We can omit "extends Object" from a type bound, but not "extends @NullableType Object". isUnannotatedJavaLangObject(TypeMirror type)195 private static boolean isUnannotatedJavaLangObject(TypeMirror type) { 196 return type.getKind().equals(TypeKind.DECLARED) 197 && type.getAnnotationMirrors().isEmpty() 198 && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Object"); 199 } 200 appendAnnotations( List<? extends AnnotationMirror> annotationMirrors, StringBuilder sb)201 private static void appendAnnotations( 202 List<? extends AnnotationMirror> annotationMirrors, StringBuilder sb) { 203 for (AnnotationMirror annotationMirror : annotationMirrors) { 204 sb.append(AnnotationOutput.sourceFormForAnnotation(annotationMirror)).append(" "); 205 } 206 } 207 208 /** 209 * Converts a type into a string, using standard Java syntax, except that every class name is 210 * wrapped in backquotes, like {@code `java.util.List`}. 211 */ 212 private static class EncodingTypeVisitor 213 extends SimpleTypeVisitor8<StringBuilder, StringBuilder> { 214 /** 215 * Equivalent to {@code visit(type, sb)} or {@code type.accept(sb)}, except that it fixes a bug 216 * with javac versions up to JDK 8, whereby if the type is a {@code DeclaredType} then the 217 * visitor is called with a version of the type where any annotations have been lost. We can't 218 * override {@code visit} because it is final. 219 */ visit2(TypeMirror type, StringBuilder sb)220 StringBuilder visit2(TypeMirror type, StringBuilder sb) { 221 if (type.getKind().equals(TypeKind.DECLARED)) { 222 // There's no point in using MoreTypes.asDeclared here, and in fact we can't, because it 223 // uses a visitor, so it would trigger the bug we're working around. 224 return visitDeclared((DeclaredType) type, sb); 225 } else { 226 return visit(type, sb); 227 } 228 } 229 230 @Override defaultAction(TypeMirror type, StringBuilder sb)231 protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) { 232 return sb.append(type); 233 } 234 235 @Override visitArray(ArrayType type, StringBuilder sb)236 public StringBuilder visitArray(ArrayType type, StringBuilder sb) { 237 return visit2(type.getComponentType(), sb).append("[]"); 238 } 239 240 @Override visitDeclared(DeclaredType type, StringBuilder sb)241 public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { 242 appendTypeName(type, sb); 243 appendTypeArguments(type, sb); 244 return sb; 245 } 246 appendTypeName(DeclaredType type, StringBuilder sb)247 void appendTypeName(DeclaredType type, StringBuilder sb) { 248 TypeMirror enclosing = EclipseHack.getEnclosingType(type); 249 if (enclosing.getKind().equals(TypeKind.DECLARED)) { 250 // We might have something like com.example.Outer<Double>.Inner. We need to encode 251 // com.example.Outer<Double> first, producing `com.example.Outer`<`java.lang.Double`>. 252 // Then we can simply add .Inner after that. If Inner has its own type arguments, we'll 253 // add them with appendTypeArguments below. Of course, it's more usual for the outer class 254 // not to have type arguments, but we'll still follow this path if the nested class is an 255 // inner (not static) class. 256 visit2(enclosing, sb); 257 sb.append(".").append(type.asElement().getSimpleName()); 258 } else { 259 sb.append('`').append(className(type)).append('`'); 260 } 261 } 262 appendTypeArguments(DeclaredType type, StringBuilder sb)263 void appendTypeArguments(DeclaredType type, StringBuilder sb) { 264 List<? extends TypeMirror> arguments = type.getTypeArguments(); 265 if (!arguments.isEmpty()) { 266 sb.append("<"); 267 String sep = ""; 268 for (TypeMirror argument : arguments) { 269 sb.append(sep); 270 sep = ", "; 271 visit2(argument, sb); 272 } 273 sb.append(">"); 274 } 275 } 276 277 @Override visitWildcard(WildcardType type, StringBuilder sb)278 public StringBuilder visitWildcard(WildcardType type, StringBuilder sb) { 279 sb.append("?"); 280 TypeMirror extendsBound = type.getExtendsBound(); 281 TypeMirror superBound = type.getSuperBound(); 282 if (superBound != null) { 283 sb.append(" super "); 284 visit2(superBound, sb); 285 } else if (extendsBound != null) { 286 sb.append(" extends "); 287 visit2(extendsBound, sb); 288 } 289 return sb; 290 } 291 292 @Override visitError(ErrorType t, StringBuilder p)293 public StringBuilder visitError(ErrorType t, StringBuilder p) { 294 throw new MissingTypeException(t); 295 } 296 } 297 298 /** Like {@link EncodingTypeVisitor} except that type parameters are omitted from the result. */ 299 private static class RawEncodingTypeVisitor extends EncodingTypeVisitor { 300 @Override appendTypeArguments(DeclaredType type, StringBuilder sb)301 void appendTypeArguments(DeclaredType type, StringBuilder sb) {} 302 } 303 304 /** 305 * Like {@link EncodingTypeVisitor} except that annotations on the visited type are also included 306 * in the resultant string. Class names in those annotations are also encoded using the {@code 307 * `java.util.List`} form. 308 */ 309 private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor { 310 private final Set<TypeMirror> excludedAnnotationTypes; 311 AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes)312 AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) { 313 this.excludedAnnotationTypes = excludedAnnotationTypes; 314 } 315 appendAnnotationsWithExclusions( List<? extends AnnotationMirror> annotations, StringBuilder sb)316 private void appendAnnotationsWithExclusions( 317 List<? extends AnnotationMirror> annotations, StringBuilder sb) { 318 // Optimization for the very common cases where there are no annotations or there are no 319 // exclusions. 320 if (annotations.isEmpty() || excludedAnnotationTypes.isEmpty()) { 321 appendAnnotations(annotations, sb); 322 return; 323 } 324 List<AnnotationMirror> includedAnnotations = 325 annotations.stream() 326 .filter(a -> !excludedAnnotationTypes.contains(a.getAnnotationType())) 327 .collect(toList()); 328 appendAnnotations(includedAnnotations, sb); 329 } 330 331 @Override visitPrimitive(PrimitiveType type, StringBuilder sb)332 public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) { 333 appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb); 334 // We can't just append type.toString(), because that will also have the annotation, but 335 // without encoding. 336 return sb.append(type.getKind().toString().toLowerCase()); 337 } 338 339 @Override visitTypeVariable(TypeVariable type, StringBuilder sb)340 public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) { 341 appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb); 342 return sb.append(type.asElement().getSimpleName()); 343 } 344 345 /** 346 * {@inheritDoc} The result respects the Java syntax, whereby {@code Foo @Bar []} is an 347 * annotation on the array type itself, while {@code @Bar Foo[]} would be an annotation on the 348 * component type. 349 */ 350 @Override visitArray(ArrayType type, StringBuilder sb)351 public StringBuilder visitArray(ArrayType type, StringBuilder sb) { 352 visit2(type.getComponentType(), sb); 353 List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors(); 354 if (!annotationMirrors.isEmpty()) { 355 sb.append(" "); 356 appendAnnotationsWithExclusions(annotationMirrors, sb); 357 } 358 return sb.append("[]"); 359 } 360 361 @Override visitDeclared(DeclaredType type, StringBuilder sb)362 public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) { 363 List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors(); 364 if (annotationMirrors.isEmpty()) { 365 super.visitDeclared(type, sb); 366 } else { 367 TypeMirror enclosing = EclipseHack.getEnclosingType(type); 368 if (enclosing.getKind().equals(TypeKind.DECLARED)) { 369 // We have something like com.example.Outer<Double>.@Annot Inner. 370 // We'll recursively encode com.example.Outer<Double> first, 371 // which if it is also annotated might result in a mouthful like 372 // `«com.example.Outer`@`org.annots.Nullable``»com.example.Outer`<`java.lang.Double`> . 373 // That annotation will have been added by a recursive call to this method. 374 // Then we'll add the annotation on the .Inner class, which we know is there because 375 // annotationMirrors is not empty. That means we'll append .@`org.annots.Annot` Inner . 376 visit2(enclosing, sb); 377 sb.append("."); 378 appendAnnotationsWithExclusions(annotationMirrors, sb); 379 sb.append(type.asElement().getSimpleName()); 380 } else { 381 // This isn't an inner class, so we have the simpler (but still complicated) case of 382 // needing to place the annotation correctly in cases like java.util.@Nullable Map . 383 // See the class doc comment for an explanation of « and » here. 384 String className = className(type); 385 sb.append("`«").append(className).append("`"); 386 appendAnnotationsWithExclusions(annotationMirrors, sb); 387 sb.append("`»").append(className).append("`"); 388 } 389 appendTypeArguments(type, sb); 390 } 391 return sb; 392 } 393 } 394 395 private static class TypeRewriter { 396 private final String text; 397 private final int textLength; 398 private final JavaScanner scanner; 399 private final Elements elementUtils; 400 private final Types typeUtils; 401 private final String packageName; 402 private final TypeMirror baseType; 403 TypeRewriter( String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType)404 TypeRewriter( 405 String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) { 406 this.text = text; 407 this.textLength = text.length(); 408 this.scanner = new JavaScanner(text); 409 this.elementUtils = elementUtils; 410 this.typeUtils = typeUtils; 411 this.packageName = pkg; 412 this.baseType = baseType; 413 } 414 rewrite()415 String rewrite() { 416 // Scan the text to determine what classes are referenced. 417 Set<TypeMirror> referencedClasses = findReferencedClasses(); 418 // Make a type simplifier based on these referenced types. 419 TypeSimplifier typeSimplifier = 420 new TypeSimplifier(elementUtils, typeUtils, packageName, referencedClasses, baseType); 421 422 StringBuilder output = new StringBuilder(); 423 int copyStart; 424 425 // Replace the `import` token with the import statements, if it is present. 426 OptionalInt importMarker = findImportMarker(); 427 if (importMarker.isPresent()) { 428 output.append(text, 0, importMarker.getAsInt()); 429 for (String toImport : typeSimplifier.typesToImport()) { 430 output.append("import ").append(toImport).append(";\n"); 431 } 432 copyStart = scanner.tokenEnd(importMarker.getAsInt()); 433 } else { 434 copyStart = 0; 435 } 436 437 // Replace each of the classname tokens with the appropriate spelling of the classname. 438 int token; 439 for (token = copyStart; token < textLength; token = scanner.tokenEnd(token)) { 440 if (text.charAt(token) == '`') { 441 output.append(text, copyStart, token); 442 decode(output, typeSimplifier, token); 443 copyStart = scanner.tokenEnd(token); 444 } 445 } 446 output.append(text, copyStart, textLength); 447 return output.toString(); 448 } 449 findReferencedClasses()450 private Set<TypeMirror> findReferencedClasses() { 451 Set<TypeMirror> classes = new TypeMirrorSet(); 452 for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) { 453 if (text.charAt(token) == '`' && !text.startsWith("`import`", token)) { 454 String className = classNameAt(token); 455 classes.add(classForName(className)); 456 } 457 } 458 return classes; 459 } 460 classForName(String className)461 private DeclaredType classForName(String className) { 462 TypeElement typeElement = elementUtils.getTypeElement(className); 463 checkState(typeElement != null, "Could not find referenced class %s", className); 464 return MoreTypes.asDeclared(typeElement.asType()); 465 } 466 decode(StringBuilder output, TypeSimplifier typeSimplifier, int token)467 private void decode(StringBuilder output, TypeSimplifier typeSimplifier, int token) { 468 String className = classNameAt(token); 469 DeclaredType type = classForName(className); 470 String simplified = typeSimplifier.simplifiedClassName(type); 471 int dot; 472 switch (text.charAt(token + 1)) { 473 case '«': 474 // If this is `«java.util.Map` then we want "java.util." here. 475 // That's because this is the first part of something like "java.util.@Nullable Map" 476 // or "java.util.Map.@Nullable Entry". 477 // If there's no dot, then we want nothing here, for "@Nullable Map". 478 dot = simplified.lastIndexOf('.'); 479 output.append(simplified.substring(0, dot + 1)); // correct even if dot == -1 480 break; 481 case '»': 482 dot = simplified.lastIndexOf('.'); 483 output.append(simplified.substring(dot + 1)); // correct even if dot == -1 484 break; 485 default: 486 output.append(simplified); 487 break; 488 } 489 } 490 findImportMarker()491 private OptionalInt findImportMarker() { 492 for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) { 493 if (text.startsWith("`import`", token)) { 494 return OptionalInt.of(token); 495 } 496 } 497 return OptionalInt.empty(); 498 } 499 classNameAt(int token)500 private String classNameAt(int token) { 501 checkArgument(text.charAt(token) == '`'); 502 int end = scanner.tokenEnd(token) - 1; // points to the closing ` 503 int t = token + 1; 504 char c = text.charAt(t); 505 if (c == '«' || c == '»') { 506 t++; 507 } 508 return text.substring(t, end); 509 } 510 } 511 } 512