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