• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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