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