blob: 290e59e093f98379c89c662f3640d65b2946fab0 [file] [log] [blame]
/*
* Copyright 2012 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 java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static javax.lang.model.element.Modifier.PRIVATE;
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.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.QualifiedNameable;
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.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor8;
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 emcmanus@google.com (É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 Map<String, Spelling> imports;
/**
* Makes a new simplifier for the given package and set of types.
*
* @param elementUtils the result of {@code ProcessingEnvironment.getElementUtils()} for the
* current annotation processing environment.
* @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).
*/
TypeSimplifier(
Elements elementUtils,
Types typeUtils,
String packageName,
Set<TypeMirror> types,
TypeMirror base) {
Set<TypeMirror> typesPlusBase = new TypeMirrorSet(types);
if (base != null) {
typesPlusBase.add(base);
}
Set<TypeMirror> topLevelTypes = topLevelTypes(typeUtils, typesPlusBase);
Set<TypeMirror> defined = nonPrivateDeclaredTypes(typeUtils, base);
this.imports = findImports(elementUtils, typeUtils, packageName, topLevelTypes, 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<String> typesToImport() {
ImmutableSortedSet.Builder<String> typesToImport = ImmutableSortedSet.naturalOrder();
for (Map.Entry<String, Spelling> entry : imports.entrySet()) {
if (entry.getValue().importIt) {
typesToImport.add(entry.getKey());
}
}
return typesToImport.build();
}
String simplifiedClassName(DeclaredType type) {
TypeElement typeElement = MoreElements.asType(type.asElement());
TypeElement top = topLevelType(typeElement);
// We always want to write a class name starting from the outermost class. For example,
// if the type is java.util.Map.Entry then we will import java.util.Map and write Map.Entry.
String topString = top.getQualifiedName().toString();
if (imports.containsKey(topString)) {
String suffix = typeElement.getQualifiedName().toString().substring(topString.length());
return imports.get(topString).spelling + suffix;
} else {
return typeElement.getQualifiedName().toString();
}
}
// The actual type parameters of the given type.
// If we have @AutoValue abstract class Foo<T extends Something> then the subclass will be
// final class AutoValue_Foo<T extends Something> extends Foo<T>.
// <T extends Something> is the formal type parameter list and
// <T> is the actual type parameter list, which is what this method returns.
static String actualTypeParametersString(TypeElement type) {
List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
if (typeParameters.isEmpty()) {
return "";
} else {
return typeParameters
.stream()
.map(e -> e.getSimpleName().toString())
.collect(joining(", ", "<", ">"));
}
}
/** 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.
*
* <p>This method operates on a {@code Set<TypeMirror>} rather than just a {@code Set<String>}
* 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 codePackageName 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<String, Spelling> findImports(
Elements elementUtils,
Types typeUtils,
String codePackageName,
Set<TypeMirror> referenced,
Set<TypeMirror> defined) {
Map<String, Spelling> imports = new HashMap<>();
Set<TypeMirror> typesInScope = new TypeMirrorSet();
typesInScope.addAll(referenced);
typesInScope.addAll(defined);
Set<String> 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("java.lang")) {
importIt = false;
spelling = javaLangSpelling(elementUtils, codePackageName, typeElement);
} else if (pkg.equals(codePackageName)) {
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;
}
/**
* Handles the tricky case where the class being referred to is in {@code java.lang}, but the
* package of the referring code contains another class of the same name. For example, if the
* current package is {@code foo.bar} and there is a {@code foo.bar.Compiler}, then we will refer
* to {@code java.lang.Compiler} by its full name. The plain name {@code Compiler} would reference
* {@code foo.bar.Compiler} in this case. We need to write {@code java.lang.Compiler} even if the
* other {@code Compiler} class is not being considered here, so the {@link #ambiguousNames} logic
* is not enough. We have to look to see if the class exists.
*/
private static String javaLangSpelling(
Elements elementUtils, String codePackageName, TypeElement typeElement) {
// If this is java.lang.Thread.State or the like, we have to look for a clash with Thread.
TypeElement topLevelType = topLevelType(typeElement);
TypeElement clash =
elementUtils.getTypeElement(codePackageName + "." + topLevelType.getSimpleName());
String fullName = typeElement.getQualifiedName().toString();
return (clash == null) ? fullName.substring("java.lang.".length()) : fullName;
}
/**
* Finds the top-level types for all the declared types (classes and interfaces) in the given
* {@code Set<TypeMirror>}.
*
* <p>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<TypeMirror> topLevelTypes(Types typeUtil, Set<TypeMirror> types) {
return types
.stream()
.map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror)))
.map(typeElement -> topLevelType(typeElement).asType())
.collect(toCollection(TypeMirrorSet::new));
}
/**
* 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<TypeMirror> nonPrivateDeclaredTypes(Types typeUtils, TypeMirror type) {
if (type == null) {
return new TypeMirrorSet();
} else {
Set<TypeMirror> declared = new TypeMirrorSet();
declared.add(type);
List<TypeElement> 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<String> ambiguousNames(Types typeUtils, Set<TypeMirror> types) {
Set<String> ambiguous = new HashSet<>();
Map<String, Name> simpleNamesToQualifiedNames = new HashMap<>();
for (TypeMirror type : types) {
if (type.getKind() == TypeKind.ERROR) {
throw new MissingTypeException(MoreTypes.asError(type));
}
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<TypeMirror>.
* TODO(emcmanus): investigate further, because this might cause problems elsewhere.
*/
Name qualifiedName = ((QualifiedNameable) 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<String>} 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 CASTING_UNCHECKED_VISITOR.visit(type, null);
}
private static final TypeVisitor<Boolean, Void> CASTING_UNCHECKED_VISITOR =
new SimpleTypeVisitor8<Boolean, Void>(false) {
@Override
public Boolean visitUnknown(TypeMirror t, Void p) {
// We don't know whether casting is unchecked for this mysterious type but assume it is,
// so we will insert a possibly unnecessary @SuppressWarnings("unchecked").
return true;
}
@Override
public Boolean visitArray(ArrayType t, Void p) {
return visit(t.getComponentType(), p);
}
@Override
public Boolean visitDeclared(DeclaredType t, Void p) {
return t.getTypeArguments().stream().anyMatch(TypeSimplifier::uncheckedTypeArgument);
}
@Override
public Boolean visitTypeVariable(TypeVariable t, Void p) {
return true;
}
};
// If a type has a type argument, then casting to the type is unchecked, except if the argument
// is <?> or <? extends Object>. The same applies to all type arguments, so casting to Map<?, ?>
// does not produce an unchecked warning for example.
private static boolean uncheckedTypeArgument(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 <? super Foo> 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");
}
}