aboutsummaryrefslogtreecommitdiff
path: root/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
blob: d1bbbab545fa4a61e3775b9a946489ab4d4f8df5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*
 * Copyright 2017 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.value.processor;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.stream.Collectors.toList;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.processor.MissingTypes.MissingTypeException;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.OptionalInt;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor8;
import javax.lang.model.util.Types;

/**
 * Encodes types so they can later be decoded to incorporate imports.
 *
 * <p>The idea is that types that appear in generated source code use {@link #encode}, which will
 * spell out a type like {@code java.util.List<? extends java.lang.Number>}, except that wherever a
 * class name appears it is replaced by a special token. So the spelling might actually be {@code
 * `java.util.List`<? extends `java.lang.Number`>}. Then once the entire class has been generated,
 * {@code #decode} scans for these tokens to determine what classes need to be imported, and
 * replaces the tokens with the correct spelling given the imports. So here, {@code java.util.List}
 * would be imported, and the final spelling would be {@code List<? extends Number>} (knowing that
 * {@code Number} is implicitly imported). The special token {@code `import`} marks where the
 * imports should be, and {@link #decode} replaces it with the correct list of imports.
 *
 * <p>The funky syntax for type annotations on qualified type names requires an adjustment to this
 * scheme. {@code `«java.util.Map`} stands for {@code java.util.} and {@code `»java.util.Map`}
 * stands for {@code Map}. If {@code java.util.Map} is imported, then {@code `«java.util.Map`} will
 * eventually be empty, but if {@code java.util.Map} is not imported (perhaps because there is
 * another {@code Map} in scope) then {@code `«java.util.Map`} will be {@code java.util.}. The end
 * result is that the code can contain {@code `«java.util.Map`@`javax.annotation.Nullable`
 * `»java.util.Map`}. That might decode to {@code @Nullable Map} or to {@code java.util.@Nullable
 * Map} or even to {@code java.util.@javax.annotation.Nullable Map}.
 *
 * @author emcmanus@google.com (Éamonn McManus)
 */
final class TypeEncoder {
  private TypeEncoder() {} // There are no instances of this class.

  private static final EncodingTypeVisitor ENCODING_TYPE_VISITOR = new EncodingTypeVisitor();
  private static final RawEncodingTypeVisitor RAW_ENCODING_TYPE_VISITOR =
      new RawEncodingTypeVisitor();

  /**
   * Returns the encoding for the given type, where class names are marked by special tokens. The
   * encoding for {@code int} will be {@code int}, but the encoding for {@code
   * java.util.List<java.lang.Integer>} will be {@code `java.util.List`<`java.lang.Integer`>}.
   */
  static String encode(TypeMirror type) {
    StringBuilder sb = new StringBuilder();
    return type.accept(ENCODING_TYPE_VISITOR, sb).toString();
  }

  /**
   * Like {@link #encode}, except that only the raw type is encoded. So if the given type is {@code
   * java.util.List<java.lang.Integer>} the result will be {@code `java.util.List`}.
   */
  static String encodeRaw(TypeMirror type) {
    StringBuilder sb = new StringBuilder();
    return type.accept(RAW_ENCODING_TYPE_VISITOR, sb).toString();
  }

  /**
   * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
   * covers the details of annotation encoding.
   */
  static String encodeWithAnnotations(TypeMirror type) {
    return encodeWithAnnotations(type, ImmutableSet.of());
  }

  /**
   * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
   * covers the details of annotation encoding.
   *
   * @param excludedAnnotationTypes annotations not to include in the encoding. For example, if
   *     {@code com.example.Nullable} is in this set then the encoding will not include this
   *     {@code @Nullable} annotation.
   */
  static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) {
    StringBuilder sb = new StringBuilder();
    return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString();
  }

  /**
   * Decodes the given string, respelling class names appropriately. The text is scanned for tokens
   * like {@code `java.util.Locale`} or {@code `«java.util.Locale`} to determine which classes are
   * referenced. An appropriate set of imports is computed based on the set of those types. If the
   * special token {@code `import`} appears in {@code text} then it will be replaced by this set of
   * import statements. Then all of the tokens are replaced by the class names they represent,
   * spelled appropriately given the import statements.
   *
   * @param text the text to be decoded.
   * @param packageName the package of the generated class. Other classes in the same package do not
   *     need to be imported.
   * @param baseType a class or interface that the generated class inherits from. Nested classes in
   *     that type do not need to be imported, and if another class has the same name as one of
   *     those nested classes then it will need to be qualified.
   */
  static String decode(
      String text, ProcessingEnvironment processingEnv, String packageName, TypeMirror baseType) {
    return decode(
        text, processingEnv.getElementUtils(), processingEnv.getTypeUtils(), packageName, baseType);
  }

  static String decode(
      String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) {
    TypeRewriter typeRewriter = new TypeRewriter(text, elementUtils, typeUtils, pkg, baseType);
    return typeRewriter.rewrite();
  }

  private static String className(DeclaredType declaredType) {
    return MoreElements.asType(declaredType.asElement()).getQualifiedName().toString();
  }

  /**
   * Returns the formal type parameters of the given type. If we have {@code @AutoValue abstract
   * class Foo<T extends SomeClass>} then this method will return an encoding of {@code <T extends
   * SomeClass>} for {@code Foo}. Likewise it will return an encoding of the angle-bracket part of:
   * <br>
   * {@code Foo<SomeClass>}<br>
   * {@code Foo<T extends Number>}<br>
   * {@code Foo<E extends Enum<E>>}<br>
   * {@code Foo<K, V extends Comparable<? extends K>>}.
   *
   * <p>The encoding is simply that classes in the "extends" part are marked, so the examples will
   * actually look something like this:<br>
   * {@code <`bar.baz.SomeClass`>}<br>
   * {@code <T extends `java.lang.Number`>}<br>
   * {@code <E extends `java.lang.Enum`<E>>}<br>
   * {@code <K, V extends `java.lang.Comparable`<? extends K>>}.
   */
  static String formalTypeParametersString(TypeElement type) {
    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
    if (typeParameters.isEmpty()) {
      return "";
    } else {
      StringBuilder sb = new StringBuilder("<");
      String sep = "";
      for (TypeParameterElement typeParameter : typeParameters) {
        sb.append(sep);
        sep = ", ";
        appendTypeParameterWithBounds(typeParameter, sb);
      }
      return sb.append(">").toString();
    }
  }

  private static void appendTypeParameterWithBounds(
      TypeParameterElement typeParameter, StringBuilder sb) {
    appendAnnotations(typeParameter.getAnnotationMirrors(), sb);
    sb.append(typeParameter.getSimpleName());
    String sep = " extends ";
    for (TypeMirror bound : typeParameter.getBounds()) {
      if (!isUnannotatedJavaLangObject(bound)) {
        sb.append(sep);
        sep = " & ";
        sb.append(encodeWithAnnotations(bound));
      }
    }
  }

  // We can omit "extends Object" from a type bound, but not "extends @NullableType Object".
  private static boolean isUnannotatedJavaLangObject(TypeMirror type) {
    return type.getKind().equals(TypeKind.DECLARED)
        && type.getAnnotationMirrors().isEmpty()
        && MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.lang.Object");
  }

  private static void appendAnnotations(
      List<? extends AnnotationMirror> annotationMirrors, StringBuilder sb) {
    for (AnnotationMirror annotationMirror : annotationMirrors) {
      sb.append(AnnotationOutput.sourceFormForAnnotation(annotationMirror)).append(" ");
    }
  }

  /**
   * Converts a type into a string, using standard Java syntax, except that every class name is
   * wrapped in backquotes, like {@code `java.util.List`}.
   */
  private static class EncodingTypeVisitor
      extends SimpleTypeVisitor8<StringBuilder, StringBuilder> {
    /**
     * Equivalent to {@code visit(type, sb)} or {@code type.accept(sb)}, except that it fixes a bug
     * with javac versions up to JDK 8, whereby if the type is a {@code DeclaredType} then the
     * visitor is called with a version of the type where any annotations have been lost. We can't
     * override {@code visit} because it is final.
     */
    StringBuilder visit2(TypeMirror type, StringBuilder sb) {
      if (type.getKind().equals(TypeKind.DECLARED)) {
        // There's no point in using MoreTypes.asDeclared here, and in fact we can't, because it
        // uses a visitor, so it would trigger the bug we're working around.
        return visitDeclared((DeclaredType) type, sb);
      } else {
        return visit(type, sb);
      }
    }

    @Override
    protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) {
      return sb.append(type);
    }

    @Override
    public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
      return visit2(type.getComponentType(), sb).append("[]");
    }

    @Override
    public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
      appendTypeName(type, sb);
      appendTypeArguments(type, sb);
      return sb;
    }

    void appendTypeName(DeclaredType type, StringBuilder sb) {
      TypeMirror enclosing = EclipseHack.getEnclosingType(type);
      if (enclosing.getKind().equals(TypeKind.DECLARED)) {
        // We might have something like com.example.Outer<Double>.Inner. We need to encode
        // com.example.Outer<Double> first, producing `com.example.Outer`<`java.lang.Double`>.
        // Then we can simply add .Inner after that. If Inner has its own type arguments, we'll
        // add them with appendTypeArguments below. Of course, it's more usual for the outer class
        // not to have type arguments, but we'll still follow this path if the nested class is an
        // inner (not static) class.
        visit2(enclosing, sb);
        sb.append(".").append(type.asElement().getSimpleName());
      } else {
        sb.append('`').append(className(type)).append('`');
      }
    }

    void appendTypeArguments(DeclaredType type, StringBuilder sb) {
      List<? extends TypeMirror> arguments = type.getTypeArguments();
      if (!arguments.isEmpty()) {
        sb.append("<");
        String sep = "";
        for (TypeMirror argument : arguments) {
          sb.append(sep);
          sep = ", ";
          visit2(argument, sb);
        }
        sb.append(">");
      }
    }

    @Override
    public StringBuilder visitWildcard(WildcardType type, StringBuilder sb) {
      sb.append("?");
      TypeMirror extendsBound = type.getExtendsBound();
      TypeMirror superBound = type.getSuperBound();
      if (superBound != null) {
        sb.append(" super ");
        visit2(superBound, sb);
      } else if (extendsBound != null) {
        sb.append(" extends ");
        visit2(extendsBound, sb);
      }
      return sb;
    }

    @Override
    public StringBuilder visitError(ErrorType t, StringBuilder p) {
      throw new MissingTypeException(t);
    }
  }

  /** Like {@link EncodingTypeVisitor} except that type parameters are omitted from the result. */
  private static class RawEncodingTypeVisitor extends EncodingTypeVisitor {
    @Override
    void appendTypeArguments(DeclaredType type, StringBuilder sb) {}
  }

  /**
   * Like {@link EncodingTypeVisitor} except that annotations on the visited type are also included
   * in the resultant string. Class names in those annotations are also encoded using the {@code
   * `java.util.List`} form.
   */
  private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor {
    private final Set<TypeMirror> excludedAnnotationTypes;

    AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) {
      this.excludedAnnotationTypes = excludedAnnotationTypes;
    }

    private void appendAnnotationsWithExclusions(
        List<? extends AnnotationMirror> annotations, StringBuilder sb) {
      // Optimization for the very common cases where there are no annotations or there are no
      // exclusions.
      if (annotations.isEmpty() || excludedAnnotationTypes.isEmpty()) {
        appendAnnotations(annotations, sb);
        return;
      }
      List<AnnotationMirror> includedAnnotations =
          annotations.stream()
              .filter(a -> !excludedAnnotationTypes.contains(a.getAnnotationType()))
              .collect(toList());
      appendAnnotations(includedAnnotations, sb);
    }

    @Override
    public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) {
      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
      // We can't just append type.toString(), because that will also have the annotation, but
      // without encoding.
      return sb.append(type.getKind().toString().toLowerCase());
    }

    @Override
    public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) {
      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
      return sb.append(type.asElement().getSimpleName());
    }

    /**
     * {@inheritDoc} The result respects the Java syntax, whereby {@code Foo @Bar []} is an
     * annotation on the array type itself, while {@code @Bar Foo[]} would be an annotation on the
     * component type.
     */
    @Override
    public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
      visit2(type.getComponentType(), sb);
      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
      if (!annotationMirrors.isEmpty()) {
        sb.append(" ");
        appendAnnotationsWithExclusions(annotationMirrors, sb);
      }
      return sb.append("[]");
    }

    @Override
    public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
      if (annotationMirrors.isEmpty()) {
        super.visitDeclared(type, sb);
      } else {
        TypeMirror enclosing = EclipseHack.getEnclosingType(type);
        if (enclosing.getKind().equals(TypeKind.DECLARED)) {
          // We have something like com.example.Outer<Double>.@Annot Inner.
          // We'll recursively encode com.example.Outer<Double> first,
          // which if it is also annotated might result in a mouthful like
          // `«com.example.Outer`@`org.annots.Nullable``»com.example.Outer`<`java.lang.Double`> .
          // That annotation will have been added by a recursive call to this method.
          // Then we'll add the annotation on the .Inner class, which we know is there because
          // annotationMirrors is not empty. That means we'll append .@`org.annots.Annot` Inner .
          visit2(enclosing, sb);
          sb.append(".");
          appendAnnotationsWithExclusions(annotationMirrors, sb);
          sb.append(type.asElement().getSimpleName());
        } else {
          // This isn't an inner class, so we have the simpler (but still complicated) case of
          // needing to place the annotation correctly in cases like java.util.@Nullable Map .
          // See the class doc comment for an explanation of « and » here.
          String className = className(type);
          sb.append("`«").append(className).append("`");
          appendAnnotationsWithExclusions(annotationMirrors, sb);
          sb.append("`»").append(className).append("`");
        }
        appendTypeArguments(type, sb);
      }
      return sb;
    }
  }

  private static class TypeRewriter {
    private final String text;
    private final int textLength;
    private final JavaScanner scanner;
    private final Elements elementUtils;
    private final Types typeUtils;
    private final String packageName;
    private final TypeMirror baseType;

    TypeRewriter(
        String text, Elements elementUtils, Types typeUtils, String pkg, TypeMirror baseType) {
      this.text = text;
      this.textLength = text.length();
      this.scanner = new JavaScanner(text);
      this.elementUtils = elementUtils;
      this.typeUtils = typeUtils;
      this.packageName = pkg;
      this.baseType = baseType;
    }

    String rewrite() {
      // Scan the text to determine what classes are referenced.
      Set<TypeMirror> referencedClasses = findReferencedClasses();
      // Make a type simplifier based on these referenced types.
      TypeSimplifier typeSimplifier =
          new TypeSimplifier(elementUtils, typeUtils, packageName, referencedClasses, baseType);

      StringBuilder output = new StringBuilder();
      int copyStart;

      // Replace the `import` token with the import statements, if it is present.
      OptionalInt importMarker = findImportMarker();
      if (importMarker.isPresent()) {
        output.append(text, 0, importMarker.getAsInt());
        for (String toImport : typeSimplifier.typesToImport()) {
          output.append("import ").append(toImport).append(";\n");
        }
        copyStart = scanner.tokenEnd(importMarker.getAsInt());
      } else {
        copyStart = 0;
      }

      // Replace each of the classname tokens with the appropriate spelling of the classname.
      int token;
      for (token = copyStart; token < textLength; token = scanner.tokenEnd(token)) {
        if (text.charAt(token) == '`') {
          output.append(text, copyStart, token);
          decode(output, typeSimplifier, token);
          copyStart = scanner.tokenEnd(token);
        }
      }
      output.append(text, copyStart, textLength);
      return output.toString();
    }

    private Set<TypeMirror> findReferencedClasses() {
      Set<TypeMirror> classes = new TypeMirrorSet();
      for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) {
        if (text.charAt(token) == '`' && !text.startsWith("`import`", token)) {
          String className = classNameAt(token);
          classes.add(classForName(className));
        }
      }
      return classes;
    }

    private DeclaredType classForName(String className) {
      TypeElement typeElement = elementUtils.getTypeElement(className);
      checkState(typeElement != null, "Could not find referenced class %s", className);
      return MoreTypes.asDeclared(typeElement.asType());
    }

    private void decode(StringBuilder output, TypeSimplifier typeSimplifier, int token) {
      String className = classNameAt(token);
      DeclaredType type = classForName(className);
      String simplified = typeSimplifier.simplifiedClassName(type);
      int dot;
      switch (text.charAt(token + 1)) {
        case '«':
          // If this is `«java.util.Map` then we want "java.util." here.
          // That's because this is the first part of something like "java.util.@Nullable Map"
          // or "java.util.Map.@Nullable Entry".
          // If there's no dot, then we want nothing here, for "@Nullable Map".
          dot = simplified.lastIndexOf('.');
          output.append(simplified.substring(0, dot + 1)); // correct even if dot == -1
          break;
        case '»':
          dot = simplified.lastIndexOf('.');
          output.append(simplified.substring(dot + 1)); // correct even if dot == -1
          break;
        default:
          output.append(simplified);
          break;
      }
    }

    private OptionalInt findImportMarker() {
      for (int token = 0; token < textLength; token = scanner.tokenEnd(token)) {
        if (text.startsWith("`import`", token)) {
          return OptionalInt.of(token);
        }
      }
      return OptionalInt.empty();
    }

    private String classNameAt(int token) {
      checkArgument(text.charAt(token) == '`');
      int end = scanner.tokenEnd(token) - 1; // points to the closing `
      int t = token + 1;
      char c = text.charAt(t);
      if (c == '«' || c == '»') {
        t++;
      }
      return text.substring(t, end);
    }
  }
}