blob: d1bbbab545fa4a61e3775b9a946489ab4d4f8df5 [file] [log] [blame]
/*
* 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);
}
}
}