All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy