com.google.auto.value.processor.TypeSimplifier Maven / Gradle / Ivy
/*
* Copyright (C) 2012 Google, Inc.
*
* 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 javax.lang.model.element.Modifier.PRIVATE;
import com.google.auto.common.MoreElements;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSortedSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
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.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.Types;
/**
* Takes a set of types and a package and determines which of those types can be imported, and how
* to spell any of the types in the set given those imports.
*
* @author [email protected] (Éamonn McManus)
*/
final class TypeSimplifier {
/**
* The spelling that should be used to refer to a given class, and an indication of whether it
* should be imported.
*/
private static class Spelling {
final String spelling;
final boolean importIt;
Spelling(String spelling, boolean importIt) {
this.spelling = spelling;
this.importIt = importIt;
}
}
private final Types typeUtils;
private final Map imports;
/**
* Makes a new simplifier for the given package and set of types.
*
* @param typeUtils the result of {@code ProcessingEnvironment.getTypeUtils()} for the current
* annotation processing environment.
* @param packageName the name of the package from which classes will be referenced. Classes that
* are in the same package do not need to be imported.
* @param types the types that will be referenced.
* @param base a base class that the class containing the references will extend. This is needed
* because nested classes in that class or one of its ancestors are in scope in the generated
* subclass, so a reference to another class with the same name as one of them is ambiguous.
*
* @throws MissingTypeException if one of the input types contains an error (typically,
* is undefined). This may be something like {@code UndefinedClass}, or something more subtle
* like {@code Set>}.
*/
TypeSimplifier(Types typeUtils, String packageName, Set types, TypeMirror base) {
this.typeUtils = typeUtils;
Set typesPlusBase = new TypeMirrorSet(types);
if (base != null) {
typesPlusBase.add(base);
}
Set referenced = referencedClassTypes(typeUtils, typesPlusBase);
Set defined = nonPrivateDeclaredTypes(typeUtils, base);
this.imports = findImports(typeUtils, packageName, referenced, defined);
}
/**
* Returns the set of types to import. We import every type that is neither in java.lang nor in
* the package containing the AutoValue class, provided that the result refers to the type
* unambiguously. For example, if there is a property of type java.util.Map.Entry then we will
* import java.util.Map.Entry and refer to the property as Entry. We could also import just
* java.util.Map in this case and refer to Map.Entry, but currently we never do that.
*/
ImmutableSortedSet typesToImport() {
ImmutableSortedSet.Builder typesToImport = ImmutableSortedSet.naturalOrder();
for (Map.Entry entry : imports.entrySet()) {
if (entry.getValue().importIt) {
typesToImport.add(entry.getKey());
}
}
return typesToImport.build();
}
/**
* Returns a string that can be used to refer to the given type given the imports defined by
* {@link #typesToImport}.
*/
String simplify(TypeMirror type) {
return type.accept(toStringTypeVisitor, new StringBuilder()).toString();
}
/**
* Returns a string that can be used to refer to the given raw type given the imports defined by
* {@link #typesToImport}. The difference between this and {@link #simplify} is that the string
* returned here will not include type parameters.
*/
String simplifyRaw(TypeMirror type) {
return type.accept(toStringRawTypeVisitor, new StringBuilder()).toString();
}
// The formal type parameters of the given type.
// If we have @AutoValue abstract class Foo then this method will
// return for Foo. Likewise it will return the angle-bracket part of:
// Foo
// Foo
// Foo>
// Foo>
// Type variables need special treatment because we only want to include their bounds when they
// are declared, not when they are referenced. We don't want to include the bounds of the second E
// in > or of the second K in >. That's
// why we put the "extends" handling here and not in ToStringTypeVisitor.
String formalTypeParametersString(TypeElement type) {
List typeParameters = type.getTypeParameters();
if (typeParameters.isEmpty()) {
return "";
} else {
StringBuilder sb = new StringBuilder("<");
String sep = "";
for (TypeParameterElement typeParameter : typeParameters) {
sb.append(sep);
sep = ", ";
appendTypeParameterWithBounds(sb, typeParameter);
}
return sb.append(">").toString();
}
}
// The actual type parameters of the given type.
// If we have @AutoValue abstract class Foo then the subclass will be
// final class AutoValue_Foo extends Foo.
// is the formal type parameter list and
// is the actual type parameter list, which is what this method returns.
static String actualTypeParametersString(TypeElement type) {
List typeParameters = type.getTypeParameters();
if (typeParameters.isEmpty()) {
return "";
} else {
return "<"
+ FluentIterable.from(typeParameters)
.transform(SimpleNameFunction.INSTANCE)
.join(Joiner.on(", "))
+ ">";
}
}
private void appendTypeParameterWithBounds(StringBuilder sb, TypeParameterElement typeParameter) {
sb.append(typeParameter.getSimpleName());
String sep = " extends ";
for (TypeMirror bound : typeParameter.getBounds()) {
if (!bound.toString().equals("java.lang.Object")) {
sb.append(sep);
sep = " & ";
bound.accept(toStringTypeVisitor, sb);
}
}
}
private final ToStringTypeVisitor toStringTypeVisitor = new ToStringTypeVisitor();
private final ToStringTypeVisitor toStringRawTypeVisitor = new ToStringRawTypeVisitor();
/**
* Visitor that produces a string representation of a type for use in generated code.
* The visitor takes into account the imports defined by {@link #typesToImport} and will use
* the short names of those types.
*
* A simpler alternative would be just to use TypeMirror.toString() and regular expressions to
* pick apart the type references and replace fully-qualified types where possible. That depends
* on unspecified behaviour of TypeMirror.toString(), though, and is vulnerable to formatting
* quirks such as the way it omits the space after the comma in
* {@code java.util.Map}.
*/
private class ToStringTypeVisitor extends SimpleTypeVisitor6 {
@Override protected StringBuilder defaultAction(TypeMirror type, StringBuilder sb) {
return sb.append(type);
}
@Override public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
return visit(type.getComponentType(), sb).append("[]");
}
@Override public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
TypeElement typeElement = (TypeElement) typeUtils.asElement(type);
TypeElement top = topLevelType(typeElement);
String topString = top.getQualifiedName().toString();
if (imports.containsKey(topString)) {
String suffix = typeElement.getQualifiedName().toString().substring(topString.length());
sb.append(imports.get(topString).spelling).append(suffix);
} else {
sb.append(typeElement.getQualifiedName());
}
appendTypeArguments(type, sb);
return sb;
}
void appendTypeArguments(DeclaredType type, StringBuilder sb) {
List arguments = type.getTypeArguments();
if (!arguments.isEmpty()) {
sb.append("<");
String sep = "";
for (TypeMirror argument : arguments) {
sb.append(sep);
sep = ", ";
visit(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 ");
visit(superBound, sb);
} else if (extendsBound != null) {
sb.append(" extends ");
visit(extendsBound, sb);
}
return sb;
}
}
private class ToStringRawTypeVisitor extends ToStringTypeVisitor {
@Override
void appendTypeArguments(DeclaredType type, StringBuilder sb) {
}
}
/**
* Returns the name of the given type, including any enclosing types but not the package.
*/
static String classNameOf(TypeElement type) {
String name = type.getQualifiedName().toString();
String pkgName = packageNameOf(type);
return pkgName.isEmpty() ? name : name.substring(pkgName.length() + 1);
}
private static TypeElement topLevelType(TypeElement type) {
while (type.getNestingKind() != NestingKind.TOP_LEVEL) {
type = MoreElements.asType(type.getEnclosingElement());
}
return type;
}
/**
* Returns the name of the package that the given type is in. If the type is in the default
* (unnamed) package then the name is the empty string.
*/
static String packageNameOf(TypeElement type) {
return MoreElements.getPackage(type).getQualifiedName().toString();
}
static String simpleNameOf(String s) {
if (s.contains(".")) {
return s.substring(s.lastIndexOf('.') + 1);
} else {
return s;
}
}
/**
* Given a set of referenced types, works out which of them should be imported and what the
* resulting spelling of each one is.
*
* This method operates on a {@code Set} rather than just a {@code Set}
* because it is not strictly possible to determine what part of a fully-qualified type name is
* the package and what part is the top-level class. For example, {@code java.util.Map.Entry} is
* a class called {@code Map.Entry} in a package called {@code java.util} assuming Java
* conventions are being followed, but it could theoretically also be a class called {@code Entry}
* in a package called {@code java.util.Map}. Since we are operating as part of the compiler, our
* goal should be complete correctness, and the only way to achieve that is to operate on the real
* representations of types.
*
* @param packageName The name of the package where the class containing these references is
* defined. Other classes within the same package do not need to be imported.
* @param referenced The complete set of declared types (classes and interfaces) that will be
* referenced in the generated code.
* @param defined The complete set of declared types (classes and interfaces) that are defined
* within the scope of the generated class (i.e. nested somewhere in its superclass chain,
* or in its interface set)
* @return a map where the keys are fully-qualified types and the corresponding values indicate
* whether the type should be imported, and how the type should be spelled in the source code.
*/
private static Map findImports(
Types typeUtils, String packageName, Set referenced, Set defined) {
Map imports = new HashMap();
Set typesInScope = new TypeMirrorSet();
typesInScope.addAll(referenced);
typesInScope.addAll(defined);
Set ambiguous = ambiguousNames(typeUtils, typesInScope);
for (TypeMirror type : referenced) {
TypeElement typeElement = (TypeElement) typeUtils.asElement(type);
String fullName = typeElement.getQualifiedName().toString();
String simpleName = typeElement.getSimpleName().toString();
String pkg = packageNameOf(typeElement);
boolean importIt;
String spelling;
if (ambiguous.contains(simpleName)) {
importIt = false;
spelling = fullName;
} else if (pkg.equals(packageName) || pkg.equals("java.lang")) {
importIt = false;
spelling = fullName.substring(pkg.isEmpty() ? 0 : pkg.length() + 1);
} else {
importIt = true;
spelling = simpleName;
}
imports.put(fullName, new Spelling(spelling, importIt));
}
return imports;
}
/**
* Finds all declared types (classes and interfaces) that are referenced in the given
* {@code Set}. This includes classes and interfaces that appear directly in the set,
* but also ones that appear in type parameters and the like. For example, if the set contains
* {@code java.util.List} then both {@code java.util.List} and
* {@code java.lang.Number} will be in the resulting set.
*
* The returned set contains only top-level types. If we reference {@code java.util.Map.Entry}
* then the returned set will contain {@code java.util.Map}. This is because we want to write
* {@code Map.Entry} everywhere rather than {@code Entry}.
*/
private static Set referencedClassTypes(Types typeUtil, Set types) {
Set allReferenced = new TypeMirrorSet();
ReferencedClassTypeVisitor referencedClassVisitor =
new ReferencedClassTypeVisitor(typeUtil, allReferenced);
for (TypeMirror type : types) {
referencedClassVisitor.visit(type);
}
Set topLevelReferenced = new TypeMirrorSet();
for (TypeMirror type : allReferenced) {
TypeElement typeElement = MoreElements.asType(typeUtil.asElement(type));
topLevelReferenced.add(topLevelType(typeElement).asType());
}
return topLevelReferenced;
}
private static class ReferencedClassTypeVisitor extends SimpleTypeVisitor6 {
private final Types typeUtils;
private final Set referencedTypes;
private final Set seenTypes;
ReferencedClassTypeVisitor(Types typeUtils, Set referenced) {
this.typeUtils = typeUtils;
this.referencedTypes = referenced;
this.seenTypes = new TypeMirrorSet();
}
@Override public Void visitArray(ArrayType t, Void p) {
return visit(t.getComponentType(), p);
}
@Override public Void visitDeclared(DeclaredType t, Void p) {
if (seenTypes.add(t)) {
referencedTypes.add(typeUtils.erasure(t));
for (TypeMirror param : t.getTypeArguments()) {
visit(param, p);
}
}
return null;
}
@Override public Void visitTypeVariable(TypeVariable t, Void p) {
// Instead of visiting t.getUpperBound(), we explicitly visit the supertypes of t.
// The reason is that for a variable like , t.getUpperBound() will be
// the intersection type Foo & Bar, with no really simple way to extract Foo and Bar. But
// directSupertypes(t) will be exactly [Foo, Bar]. For plain , directSupertypes(t) will
// be java.lang.Object, and it is harmless for us to record a reference to that since we won't
// try to import it or use it in the output string for .
for (TypeMirror upper : typeUtils.directSupertypes(t)) {
visit(upper, p);
}
return visit(t.getLowerBound(), p);
}
@Override public Void visitWildcard(WildcardType t, Void p) {
for (TypeMirror bound : new TypeMirror[] {t.getSuperBound(), t.getExtendsBound()}) {
if (bound != null) {
visit(bound, p);
}
}
return null;
}
@Override public Void visitError(ErrorType t, Void p) {
throw new MissingTypeException();
}
}
/**
* Finds all types that are declared with non private visibility by the given {@code TypeMirror},
* any class in its superclass chain, or any interface it implements.
*/
private static Set nonPrivateDeclaredTypes(Types typeUtils, TypeMirror type) {
if (type == null) {
return new TypeMirrorSet();
} else {
Set declared = new TypeMirrorSet();
declared.add(type);
List nestedTypes =
ElementFilter.typesIn(typeUtils.asElement(type).getEnclosedElements());
for (TypeElement nestedType : nestedTypes) {
if (!nestedType.getModifiers().contains(PRIVATE)) {
declared.add(nestedType.asType());
}
}
for (TypeMirror supertype : typeUtils.directSupertypes(type)) {
declared.addAll(nonPrivateDeclaredTypes(typeUtils, supertype));
}
return declared;
}
}
private static Set ambiguousNames(Types typeUtils, Set types) {
Set ambiguous = new HashSet();
Map simpleNamesToQualifiedNames = new HashMap();
for (TypeMirror type : types) {
if (type.getKind() == TypeKind.ERROR) {
throw new MissingTypeException();
}
String simpleName = typeUtils.asElement(type).getSimpleName().toString();
/*
* Compare by qualified names, because in Eclipse JDT, if Java 8 type annotations are used,
* the same (unannotated) type may appear multiple times in the Set.
* TODO(emcmanus): investigate further, because this might cause problems elsewhere.
*/
Name qualifiedName = ((TypeElement)typeUtils.asElement(type)).getQualifiedName();
Name previous = simpleNamesToQualifiedNames.put(simpleName, qualifiedName);
if (previous != null && !previous.equals(qualifiedName)) {
ambiguous.add(simpleName);
}
}
return ambiguous;
}
/**
* Returns true if casting to the given type will elicit an unchecked warning from the
* compiler. Only generic types such as {@code List} produce such warnings. There will be
* no warning if the type's only generic parameters are simple wildcards, as in {@code Map}.
*/
static boolean isCastingUnchecked(TypeMirror type) {
return new CastingUncheckedVisitor().visit(type, false);
}
/**
* Visitor that tells whether a type is erased, in the sense of {@link #isCastingUnchecked}. Each
* visitX method returns true if its input parameter is true or if the type being visited is
* erased.
*/
private static class CastingUncheckedVisitor extends SimpleTypeVisitor6 {
@Override protected Boolean defaultAction(TypeMirror e, Boolean p) {
return p;
}
@Override public Boolean visitUnknown(TypeMirror t, Boolean p) {
// We don't know whether casting is unchecked for this mysterious type but assume it is,
// so we will insert a possible-unnecessary @SuppressWarnings("unchecked").
return true;
}
@Override public Boolean visitArray(ArrayType t, Boolean p) {
return visit(t.getComponentType(), p);
}
@Override public Boolean visitDeclared(DeclaredType t, Boolean p) {
return p || FluentIterable.from(t.getTypeArguments()).anyMatch(UNCHECKED_TYPE_ARGUMENT);
}
@Override public Boolean visitTypeVariable(TypeVariable t, Boolean p) {
return true;
}
// If a type has a type argument, then casting to the type is unchecked, except if the argument
// is or . The same applies to all type arguments, so casting to Map
// does not produce an unchecked warning for example.
private static final Predicate UNCHECKED_TYPE_ARGUMENT =
new Predicate() {
@Override public boolean apply(TypeMirror arg) {
if (arg.getKind() == TypeKind.WILDCARD) {
WildcardType wildcard = (WildcardType) arg;
if (wildcard.getExtendsBound() == null || isJavaLangObject(wildcard.getExtendsBound())) {
// This is , unless there's a super bound, in which case it is and
// is erased.
return (wildcard.getSuperBound() != null);
}
}
return true;
}
};
private static boolean isJavaLangObject(TypeMirror type) {
if (type.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType declaredType = (DeclaredType) type;
TypeElement typeElement = (TypeElement) declaredType.asElement();
return typeElement.getQualifiedName().contentEquals("java.lang.Object");
}
};
}