| /* |
| * 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"); |
| } |
| } |