blob: e09680bc9688032e8e7ba565c2e6ad711980b4fd [file] [log] [blame]
/*
* Copyright 2014 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.common;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static javax.lang.model.type.TypeKind.ARRAY;
import static javax.lang.model.type.TypeKind.DECLARED;
import static javax.lang.model.type.TypeKind.EXECUTABLE;
import static javax.lang.model.type.TypeKind.INTERSECTION;
import static javax.lang.model.type.TypeKind.TYPEVAR;
import static javax.lang.model.type.TypeKind.WILDCARD;
import com.google.common.base.Equivalence;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
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;
/**
* Utilities related to {@link TypeMirror} instances.
*
* @author Gregory Kick
* @since 2.0
*/
public final class MoreTypes {
private static final class TypeEquivalence extends Equivalence<TypeMirror> {
private static final TypeEquivalence INSTANCE = new TypeEquivalence();
@Override
protected boolean doEquivalent(TypeMirror a, TypeMirror b) {
return MoreTypes.equal(a, b, ImmutableSet.<ComparedElements>of());
}
@Override
protected int doHash(TypeMirror t) {
return MoreTypes.hash(t, ImmutableSet.<Element>of());
}
}
/**
* Returns an {@link Equivalence} that can be used to compare types. The standard way to compare
* types is {@link javax.lang.model.util.Types#isSameType Types.isSameType}, but this alternative
* may be preferred in a number of cases:
*
* <ul>
* <li>If you don't have an instance of {@code Types}.
* <li>If you want a reliable {@code hashCode()} for the types, for example to construct a set
* of types using {@link java.util.HashSet} with {@link Equivalence#wrap(Object)}.
* <li>If you want distinct type variables to be considered equal if they have the same names
* and bounds.
* <li>If you want wildcard types to compare equal if they have the same bounds. {@code
* Types.isSameType} never considers wildcards equal, even when comparing a type to itself.
* </ul>
*/
public static Equivalence<TypeMirror> equivalence() {
return TypeEquivalence.INSTANCE;
}
// So EQUAL_VISITOR can be a singleton, we maintain visiting state, in particular which types
// have been seen already, in this object.
// The logic for handling recursive types like Comparable<T extends Comparable<T>> is very tricky.
// If we're not careful we'll end up with an infinite recursion. So we record the types that
// we've already seen during the recursion, and if we see the same pair of types again we just
// return true provisionally. But "the same pair of types" is itself poorly-defined. We can't
// just say that it is an equal pair of TypeMirrors, because of course if we knew how to
// determine that then we wouldn't need the complicated type visitor at all. On the other hand,
// we can't say that it is an identical pair of TypeMirrors either, because there's no
// guarantee that the TypeMirrors for the two Ts in Comparable<T extends Comparable<T>> will be
// represented by the same object, and indeed with the Eclipse compiler they aren't. We could
// compare the corresponding Elements, since equality is well-defined there, but that's not enough
// either, because the Element for Set<Object> is the same as the one for Set<String>. So we
// approximate by comparing the Elements and, if there are any type arguments, requiring them to
// be identical. This may not be foolproof either but it is sufficient for all the cases we've
// encountered so far.
private static final class EqualVisitorParam {
TypeMirror type;
Set<ComparedElements> visiting;
}
private static class ComparedElements {
final Element a;
final ImmutableList<TypeMirror> aArguments;
final Element b;
final ImmutableList<TypeMirror> bArguments;
ComparedElements(
Element a,
ImmutableList<TypeMirror> aArguments,
Element b,
ImmutableList<TypeMirror> bArguments) {
this.a = a;
this.aArguments = aArguments;
this.b = b;
this.bArguments = bArguments;
}
@Override
public boolean equals(Object o) {
if (o instanceof ComparedElements) {
ComparedElements that = (ComparedElements) o;
int nArguments = aArguments.size();
if (!this.a.equals(that.a)
|| !this.b.equals(that.b)
|| nArguments != bArguments.size()) {
// The arguments must be the same size, but we check anyway.
return false;
}
for (int i = 0; i < nArguments; i++) {
if (aArguments.get(i) != bArguments.get(i)) {
return false;
}
}
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
return a.hashCode() * 31 + b.hashCode();
}
}
private static final class EqualVisitor extends SimpleTypeVisitor8<Boolean, EqualVisitorParam> {
private static final EqualVisitor INSTANCE = new EqualVisitor();
@Override
protected Boolean defaultAction(TypeMirror a, EqualVisitorParam p) {
return a.getKind().equals(p.type.getKind());
}
@Override
public Boolean visitArray(ArrayType a, EqualVisitorParam p) {
if (p.type.getKind().equals(ARRAY)) {
ArrayType b = (ArrayType) p.type;
return equal(a.getComponentType(), b.getComponentType(), p.visiting);
}
return false;
}
@Override
public Boolean visitDeclared(DeclaredType a, EqualVisitorParam p) {
if (p.type.getKind().equals(DECLARED)) {
DeclaredType b = (DeclaredType) p.type;
Element aElement = a.asElement();
Element bElement = b.asElement();
Set<ComparedElements> newVisiting =
visitingSetPlus(
p.visiting, aElement, a.getTypeArguments(), bElement, b.getTypeArguments());
if (newVisiting.equals(p.visiting)) {
// We're already visiting this pair of elements.
// This can happen for example with Enum in Enum<E extends Enum<E>>. Return a
// provisional true value since if the Elements are not in fact equal the original
// visitor of Enum will discover that. We have to check both Elements being compared
// though to avoid missing the fact that one of the types being compared
// differs at exactly this point.
return true;
}
return aElement.equals(bElement)
&& equal(enclosingType(a), enclosingType(b), newVisiting)
&& equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
}
return false;
}
@Override
@SuppressWarnings("TypeEquals")
public Boolean visitError(ErrorType a, EqualVisitorParam p) {
return a.equals(p.type);
}
@Override
public Boolean visitExecutable(ExecutableType a, EqualVisitorParam p) {
if (p.type.getKind().equals(EXECUTABLE)) {
ExecutableType b = (ExecutableType) p.type;
return equalLists(a.getParameterTypes(), b.getParameterTypes(), p.visiting)
&& equal(a.getReturnType(), b.getReturnType(), p.visiting)
&& equalLists(a.getThrownTypes(), b.getThrownTypes(), p.visiting)
&& equalLists(a.getTypeVariables(), b.getTypeVariables(), p.visiting);
}
return false;
}
@Override
public Boolean visitIntersection(IntersectionType a, EqualVisitorParam p) {
if (p.type.getKind().equals(INTERSECTION)) {
IntersectionType b = (IntersectionType) p.type;
return equalLists(a.getBounds(), b.getBounds(), p.visiting);
}
return false;
}
@Override
public Boolean visitTypeVariable(TypeVariable a, EqualVisitorParam p) {
if (p.type.getKind().equals(TYPEVAR)) {
TypeVariable b = (TypeVariable) p.type;
TypeParameterElement aElement = (TypeParameterElement) a.asElement();
TypeParameterElement bElement = (TypeParameterElement) b.asElement();
Set<ComparedElements> newVisiting = visitingSetPlus(p.visiting, aElement, bElement);
if (newVisiting.equals(p.visiting)) {
// We're already visiting this pair of elements.
// This can happen with our friend Eclipse when looking at <T extends Comparable<T>>.
// It incorrectly reports the upper bound of T as T itself.
return true;
}
// We use aElement.getBounds() instead of a.getUpperBound() to avoid having to deal with
// the different way intersection types (like <T extends Number & Comparable<T>>) are
// represented before and after Java 8. We do have an issue that this code may consider
// that <T extends Foo & Bar> is different from <T extends Bar & Foo>, but it's very
// hard to avoid that, and not likely to be much of a problem in practice.
return equalLists(aElement.getBounds(), bElement.getBounds(), newVisiting)
&& equal(a.getLowerBound(), b.getLowerBound(), newVisiting)
&& a.asElement().getSimpleName().equals(b.asElement().getSimpleName());
}
return false;
}
@Override
public Boolean visitWildcard(WildcardType a, EqualVisitorParam p) {
if (p.type.getKind().equals(WILDCARD)) {
WildcardType b = (WildcardType) p.type;
return equal(a.getExtendsBound(), b.getExtendsBound(), p.visiting)
&& equal(a.getSuperBound(), b.getSuperBound(), p.visiting);
}
return false;
}
@Override
public Boolean visitUnknown(TypeMirror a, EqualVisitorParam p) {
throw new UnsupportedOperationException();
}
private Set<ComparedElements> visitingSetPlus(
Set<ComparedElements> visiting, Element a, Element b) {
ImmutableList<TypeMirror> noArguments = ImmutableList.of();
return visitingSetPlus(visiting, a, noArguments, b, noArguments);
}
private Set<ComparedElements> visitingSetPlus(
Set<ComparedElements> visiting,
Element a,
List<? extends TypeMirror> aArguments,
Element b,
List<? extends TypeMirror> bArguments) {
ComparedElements comparedElements =
new ComparedElements(
a, ImmutableList.<TypeMirror>copyOf(aArguments),
b, ImmutableList.<TypeMirror>copyOf(bArguments));
Set<ComparedElements> newVisiting = new HashSet<ComparedElements>(visiting);
newVisiting.add(comparedElements);
return newVisiting;
}
}
@SuppressWarnings("TypeEquals")
private static boolean equal(TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) {
// TypeMirror.equals is not guaranteed to return true for types that are equal, but we can
// assume that if it does return true then the types are equal. This check also avoids getting
// stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in
// <K extends Comparable<K>> is a distinct but equal K.
// The javac implementation of ExecutableType, at least in some versions, does not take thrown
// exceptions into account in its equals implementation, so avoid this optimization for
// ExecutableType.
if (Objects.equal(a, b) && !(a instanceof ExecutableType)) {
return true;
}
EqualVisitorParam p = new EqualVisitorParam();
p.type = b;
p.visiting = visiting;
return (a == b) || (a != null && b != null && a.accept(EqualVisitor.INSTANCE, p));
}
/**
* Returns the type of the innermost enclosing instance, or null if there is none. This is the
* same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than
* NoType for a static type. We need this because of
* <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby
* the Eclipse compiler returns a value for static classes that is not NoType.
*/
private static TypeMirror enclosingType(DeclaredType t) {
TypeMirror enclosing = t.getEnclosingType();
if (enclosing.getKind().equals(TypeKind.NONE)
|| t.asElement().getModifiers().contains(Modifier.STATIC)) {
return null;
}
return enclosing;
}
private static boolean equalLists(
List<? extends TypeMirror> a, List<? extends TypeMirror> b, Set<ComparedElements> visiting) {
int size = a.size();
if (size != b.size()) {
return false;
}
// Use iterators in case the Lists aren't RandomAccess
Iterator<? extends TypeMirror> aIterator = a.iterator();
Iterator<? extends TypeMirror> bIterator = b.iterator();
while (aIterator.hasNext()) {
if (!bIterator.hasNext()) {
return false;
}
TypeMirror nextMirrorA = aIterator.next();
TypeMirror nextMirrorB = bIterator.next();
if (!equal(nextMirrorA, nextMirrorB, visiting)) {
return false;
}
}
return !aIterator.hasNext();
}
private static final int HASH_SEED = 17;
private static final int HASH_MULTIPLIER = 31;
private static final class HashVisitor extends SimpleTypeVisitor8<Integer, Set<Element>> {
private static final HashVisitor INSTANCE = new HashVisitor();
int hashKind(int seed, TypeMirror t) {
int result = seed * HASH_MULTIPLIER;
result += t.getKind().hashCode();
return result;
}
@Override
protected Integer defaultAction(TypeMirror e, Set<Element> visiting) {
return hashKind(HASH_SEED, e);
}
@Override
public Integer visitArray(ArrayType t, Set<Element> visiting) {
int result = hashKind(HASH_SEED, t);
result *= HASH_MULTIPLIER;
result += t.getComponentType().accept(this, visiting);
return result;
}
@Override
public Integer visitDeclared(DeclaredType t, Set<Element> visiting) {
Element element = t.asElement();
if (visiting.contains(element)) {
return 0;
}
Set<Element> newVisiting = new HashSet<Element>(visiting);
newVisiting.add(element);
int result = hashKind(HASH_SEED, t);
result *= HASH_MULTIPLIER;
result += t.asElement().hashCode();
result *= HASH_MULTIPLIER;
result += t.getEnclosingType().accept(this, newVisiting);
result *= HASH_MULTIPLIER;
result += hashList(t.getTypeArguments(), newVisiting);
return result;
}
@Override
public Integer visitExecutable(ExecutableType t, Set<Element> visiting) {
int result = hashKind(HASH_SEED, t);
result *= HASH_MULTIPLIER;
result += hashList(t.getParameterTypes(), visiting);
result *= HASH_MULTIPLIER;
result += t.getReturnType().accept(this, visiting);
result *= HASH_MULTIPLIER;
result += hashList(t.getThrownTypes(), visiting);
result *= HASH_MULTIPLIER;
result += hashList(t.getTypeVariables(), visiting);
return result;
}
@Override
public Integer visitTypeVariable(TypeVariable t, Set<Element> visiting) {
int result = hashKind(HASH_SEED, t);
result *= HASH_MULTIPLIER;
result += t.getLowerBound().accept(this, visiting);
TypeParameterElement element = (TypeParameterElement) t.asElement();
for (TypeMirror bound : element.getBounds()) {
result *= HASH_MULTIPLIER;
result += bound.accept(this, visiting);
}
return result;
}
@Override
public Integer visitWildcard(WildcardType t, Set<Element> visiting) {
int result = hashKind(HASH_SEED, t);
result *= HASH_MULTIPLIER;
result += (t.getExtendsBound() == null) ? 0 : t.getExtendsBound().accept(this, visiting);
result *= HASH_MULTIPLIER;
result += (t.getSuperBound() == null) ? 0 : t.getSuperBound().accept(this, visiting);
return result;
}
@Override
public Integer visitUnknown(TypeMirror t, Set<Element> visiting) {
throw new UnsupportedOperationException();
}
};
private static int hashList(List<? extends TypeMirror> mirrors, Set<Element> visiting) {
int result = HASH_SEED;
for (TypeMirror mirror : mirrors) {
result *= HASH_MULTIPLIER;
result += hash(mirror, visiting);
}
return result;
}
private static int hash(TypeMirror mirror, Set<Element> visiting) {
return mirror == null ? 0 : mirror.accept(HashVisitor.INSTANCE, visiting);
}
/**
* Returns the set of {@linkplain TypeElement types} that are referenced by the given {@link
* TypeMirror}.
*/
public static ImmutableSet<TypeElement> referencedTypes(TypeMirror type) {
checkNotNull(type);
ImmutableSet.Builder<TypeElement> elements = ImmutableSet.builder();
type.accept(ReferencedTypes.INSTANCE, elements);
return elements.build();
}
private static final class ReferencedTypes
extends SimpleTypeVisitor8<Void, ImmutableSet.Builder<TypeElement>> {
private static final ReferencedTypes INSTANCE = new ReferencedTypes();
@Override
public Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) {
t.getComponentType().accept(this, p);
return null;
}
@Override
public Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) {
p.add(MoreElements.asType(t.asElement()));
for (TypeMirror typeArgument : t.getTypeArguments()) {
typeArgument.accept(this, p);
}
return null;
}
@Override
public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) {
t.getLowerBound().accept(this, p);
t.getUpperBound().accept(this, p);
return null;
}
@Override
public Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) {
TypeMirror extendsBound = t.getExtendsBound();
if (extendsBound != null) {
extendsBound.accept(this, p);
}
TypeMirror superBound = t.getSuperBound();
if (superBound != null) {
superBound.accept(this, p);
}
return null;
}
}
/**
* An alternate implementation of {@link Types#asElement} that does not require a {@link Types}
* instance with the notable difference that it will throw {@link IllegalArgumentException}
* instead of returning null if the {@link TypeMirror} can not be converted to an {@link Element}.
*
* @throws NullPointerException if {@code typeMirror} is {@code null}
* @throws IllegalArgumentException if {@code typeMirror} cannot be converted to an {@link
* Element}
*/
public static Element asElement(TypeMirror typeMirror) {
return typeMirror.accept(AsElementVisitor.INSTANCE, null);
}
private static final class AsElementVisitor extends SimpleTypeVisitor8<Element, Void> {
private static final AsElementVisitor INSTANCE = new AsElementVisitor();
@Override
protected Element defaultAction(TypeMirror e, Void p) {
throw new IllegalArgumentException(e + " cannot be converted to an Element");
}
@Override
public Element visitDeclared(DeclaredType t, Void p) {
return t.asElement();
}
@Override
public Element visitError(ErrorType t, Void p) {
return t.asElement();
}
@Override
public Element visitTypeVariable(TypeVariable t, Void p) {
return t.asElement();
}
};
// TODO(gak): consider removing these two methods as they're pretty trivial now
public static TypeElement asTypeElement(TypeMirror mirror) {
return MoreElements.asType(asElement(mirror));
}
public static ImmutableSet<TypeElement> asTypeElements(Iterable<? extends TypeMirror> mirrors) {
checkNotNull(mirrors);
ImmutableSet.Builder<TypeElement> builder = ImmutableSet.builder();
for (TypeMirror mirror : mirrors) {
builder.add(asTypeElement(mirror));
}
return builder.build();
}
/**
* Returns a {@link ArrayType} if the {@link TypeMirror} represents an array or throws an {@link
* IllegalArgumentException}.
*/
public static ArrayType asArray(TypeMirror maybeArrayType) {
return maybeArrayType.accept(ArrayTypeVisitor.INSTANCE, null);
}
private static final class ArrayTypeVisitor extends CastingTypeVisitor<ArrayType> {
private static final ArrayTypeVisitor INSTANCE = new ArrayTypeVisitor();
ArrayTypeVisitor() {
super("array");
}
@Override
public ArrayType visitArray(ArrayType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link DeclaredType} if the {@link TypeMirror} represents a declared type such as a
* class, interface, union/compound, or enum or throws an {@link IllegalArgumentException}.
*/
public static DeclaredType asDeclared(TypeMirror maybeDeclaredType) {
return maybeDeclaredType.accept(DeclaredTypeVisitor.INSTANCE, null);
}
private static final class DeclaredTypeVisitor extends CastingTypeVisitor<DeclaredType> {
private static final DeclaredTypeVisitor INSTANCE = new DeclaredTypeVisitor();
DeclaredTypeVisitor() {
super("declared type");
}
@Override
public DeclaredType visitDeclared(DeclaredType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
* as may result from missing code, or bad compiles or throws an {@link IllegalArgumentException}.
*/
public static ErrorType asError(TypeMirror maybeErrorType) {
return maybeErrorType.accept(ErrorTypeVisitor.INSTANCE, null);
}
private static final class ErrorTypeVisitor extends CastingTypeVisitor<ErrorType> {
private static final ErrorTypeVisitor INSTANCE = new ErrorTypeVisitor();
ErrorTypeVisitor() {
super("error type");
}
@Override
public ErrorType visitError(ErrorType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
* as a method, constructor, or initializer or throws an {@link IllegalArgumentException}.
*/
public static ExecutableType asExecutable(TypeMirror maybeExecutableType) {
return maybeExecutableType.accept(ExecutableTypeVisitor.INSTANCE, null);
}
private static final class ExecutableTypeVisitor extends CastingTypeVisitor<ExecutableType> {
private static final ExecutableTypeVisitor INSTANCE = new ExecutableTypeVisitor();
ExecutableTypeVisitor() {
super("executable type");
}
@Override
public ExecutableType visitExecutable(ExecutableType type, Void ignore) {
return type;
}
}
/**
* Returns an {@link IntersectionType} if the {@link TypeMirror} represents an intersection-type
* or throws an {@link IllegalArgumentException}.
*/
public static IntersectionType asIntersection(TypeMirror maybeIntersectionType) {
return maybeIntersectionType.accept(IntersectionTypeVisitor.INSTANCE, null);
}
private static final class IntersectionTypeVisitor extends CastingTypeVisitor<IntersectionType> {
private static final IntersectionTypeVisitor INSTANCE = new IntersectionTypeVisitor();
IntersectionTypeVisitor() {
super("intersection type");
}
@Override
public IntersectionType visitIntersection(IntersectionType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link NoType} if the {@link TypeMirror} represents an non-type such as void, or
* package, etc. or throws an {@link IllegalArgumentException}.
*/
public static NoType asNoType(TypeMirror maybeNoType) {
return maybeNoType.accept(NoTypeVisitor.INSTANCE, null);
}
private static final class NoTypeVisitor extends CastingTypeVisitor<NoType> {
private static final NoTypeVisitor INSTANCE = new NoTypeVisitor();
NoTypeVisitor() {
super("non-type");
}
@Override
public NoType visitNoType(NoType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link NullType} if the {@link TypeMirror} represents the null type or throws an
* {@link IllegalArgumentException}.
*/
public static NullType asNullType(TypeMirror maybeNullType) {
return maybeNullType.accept(NullTypeVisitor.INSTANCE, null);
}
private static final class NullTypeVisitor extends CastingTypeVisitor<NullType> {
private static final NullTypeVisitor INSTANCE = new NullTypeVisitor();
NullTypeVisitor() {
super("null");
}
@Override
public NullType visitNull(NullType type, Void ignore) {
return type;
}
}
/**
* Returns a {@link PrimitiveType} if the {@link TypeMirror} represents a primitive type or throws
* an {@link IllegalArgumentException}.
*/
public static PrimitiveType asPrimitiveType(TypeMirror maybePrimitiveType) {
return maybePrimitiveType.accept(PrimitiveTypeVisitor.INSTANCE, null);
}
private static final class PrimitiveTypeVisitor extends CastingTypeVisitor<PrimitiveType> {
private static final PrimitiveTypeVisitor INSTANCE = new PrimitiveTypeVisitor();
PrimitiveTypeVisitor() {
super("primitive type");
}
@Override
public PrimitiveType visitPrimitive(PrimitiveType type, Void ignore) {
return type;
}
}
//
// visitUnionType would go here, but isn't relevant for annotation processors
//
/**
* Returns a {@link TypeVariable} if the {@link TypeMirror} represents a type variable or throws
* an {@link IllegalArgumentException}.
*/
public static TypeVariable asTypeVariable(TypeMirror maybeTypeVariable) {
return maybeTypeVariable.accept(TypeVariableVisitor.INSTANCE, null);
}
private static final class TypeVariableVisitor extends CastingTypeVisitor<TypeVariable> {
private static final TypeVariableVisitor INSTANCE = new TypeVariableVisitor();
TypeVariableVisitor() {
super("type variable");
}
@Override
public TypeVariable visitTypeVariable(TypeVariable type, Void ignore) {
return type;
}
}
/**
* Returns a {@link WildcardType} if the {@link TypeMirror} represents a wildcard type or throws
* an {@link IllegalArgumentException}.
*/
public static WildcardType asWildcard(TypeMirror maybeWildcardType) {
return maybeWildcardType.accept(WildcardTypeVisitor.INSTANCE, null);
}
private static final class WildcardTypeVisitor extends CastingTypeVisitor<WildcardType> {
private static final WildcardTypeVisitor INSTANCE = new WildcardTypeVisitor();
WildcardTypeVisitor() {
super("wildcard type");
}
@Override
public WildcardType visitWildcard(WildcardType type, Void ignore) {
return type;
}
}
/**
* Returns true if the raw type underlying the given {@link TypeMirror} represents a type that can
* be referenced by a {@link Class}. If this returns true, then {@link #isTypeOf} is guaranteed to
* not throw.
*/
public static boolean isType(TypeMirror type) {
return type.accept(IsTypeVisitor.INSTANCE, null);
}
private static final class IsTypeVisitor extends SimpleTypeVisitor8<Boolean, Void> {
private static final IsTypeVisitor INSTANCE = new IsTypeVisitor();
@Override
protected Boolean defaultAction(TypeMirror type, Void ignored) {
return false;
}
@Override
public Boolean visitNoType(NoType noType, Void p) {
return noType.getKind().equals(TypeKind.VOID);
}
@Override
public Boolean visitPrimitive(PrimitiveType type, Void p) {
return true;
}
@Override
public Boolean visitArray(ArrayType array, Void p) {
return true;
}
@Override
public Boolean visitDeclared(DeclaredType type, Void ignored) {
return MoreElements.isType(type.asElement());
}
}
/**
* Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw
* type as the given {@link Class} and throws an IllegalArgumentException if the {@link
* TypeMirror} does not represent a type that can be referenced by a {@link Class}
*/
public static boolean isTypeOf(final Class<?> clazz, TypeMirror type) {
checkNotNull(clazz);
return type.accept(new IsTypeOf(clazz), null);
}
private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> {
private final Class<?> clazz;
IsTypeOf(Class<?> clazz) {
this.clazz = clazz;
}
@Override
protected Boolean defaultAction(TypeMirror type, Void ignored) {
throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
}
@Override
public Boolean visitNoType(NoType noType, Void p) {
if (noType.getKind().equals(TypeKind.VOID)) {
return clazz.equals(Void.TYPE);
}
throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>.");
}
@Override
public Boolean visitPrimitive(PrimitiveType type, Void p) {
switch (type.getKind()) {
case BOOLEAN:
return clazz.equals(Boolean.TYPE);
case BYTE:
return clazz.equals(Byte.TYPE);
case CHAR:
return clazz.equals(Character.TYPE);
case DOUBLE:
return clazz.equals(Double.TYPE);
case FLOAT:
return clazz.equals(Float.TYPE);
case INT:
return clazz.equals(Integer.TYPE);
case LONG:
return clazz.equals(Long.TYPE);
case SHORT:
return clazz.equals(Short.TYPE);
default:
throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
}
}
@Override
public Boolean visitArray(ArrayType array, Void p) {
return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
}
@Override
public Boolean visitDeclared(DeclaredType type, Void ignored) {
TypeElement typeElement = MoreElements.asType(type.asElement());
return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
}
}
/**
* Returns the superclass of {@code type}, with any type parameters bound by {@code type}, or
* {@link Optional#absent()} if {@code type} is an interface or {@link Object} or its superclass
* is {@link Object}.
*/
// TODO(user): Remove unused parameter Elements?
public static Optional<DeclaredType> nonObjectSuperclass(Types types, Elements elements,
DeclaredType type) {
checkNotNull(types);
checkNotNull(elements); // This is no longer used, but here to avoid changing the API.
checkNotNull(type);
TypeMirror superclassType = asTypeElement(type).getSuperclass();
if (!isType(superclassType)) { // type is Object or an interface
return Optional.absent();
}
DeclaredType superclass = asDeclared(superclassType);
if (isObjectType(superclass)) {
return Optional.absent();
}
if (superclass.getTypeArguments().isEmpty()) {
return Optional.of(superclass);
}
// In the case where the super class has type parameters, TypeElement#getSuperclass gives
// SuperClass<T> rather than SuperClass<Foo>, so use Types#directSupertypes instead. The javadoc
// for Types#directSupertypes guarantees that a super class, if it exists, comes before any
// interfaces. Thus, we can just get the first element in the list.
return Optional.of(asDeclared(types.directSupertypes(type).get(0)));
}
private static boolean isObjectType(DeclaredType type) {
return asTypeElement(type).getQualifiedName().contentEquals("java.lang.Object");
}
/**
* Resolves a {@link VariableElement} parameter to a method or constructor based on the given
* container, or a member of a class. For parameters to a method or constructor, the variable's
* enclosing element must be a supertype of the container type. For example, given a
* {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
* parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
*/
public static TypeMirror asMemberOf(Types types, DeclaredType container,
VariableElement variable) {
if (variable.getKind().equals(ElementKind.PARAMETER)) {
ExecutableElement methodOrConstructor =
MoreElements.asExecutable(variable.getEnclosingElement());
ExecutableType resolvedMethodOrConstructor =
MoreTypes.asExecutable(types.asMemberOf(container, methodOrConstructor));
List<? extends VariableElement> parameters = methodOrConstructor.getParameters();
List<? extends TypeMirror> parameterTypes = resolvedMethodOrConstructor.getParameterTypes();
checkState(parameters.size() == parameterTypes.size());
for (int i = 0; i < parameters.size(); i++) {
// We need to capture the parameter type of the variable we're concerned about,
// for later printing. This is the only way to do it since we can't use
// types.asMemberOf on variables of methods.
if (parameters.get(i).equals(variable)) {
return parameterTypes.get(i);
}
}
throw new IllegalStateException("Could not find variable: " + variable);
} else {
return types.asMemberOf(container, variable);
}
}
private abstract static class CastingTypeVisitor<T> extends SimpleTypeVisitor8<T, Void> {
private final String label;
CastingTypeVisitor(String label) {
this.label = label;
}
@Override
protected T defaultAction(TypeMirror e, Void v) {
throw new IllegalArgumentException(e + " does not represent a " + label);
}
}
/**
* Returns true if casting {@code Object} to the given type will elicit an unchecked warning from
* the compiler. Only type variables and parameterized types such as {@code List<String>} produce
* such warnings. There will be no warning if the type's only type parameters are simple
* wildcards, as in {@code Map<?, ?>}.
*/
public static boolean isConversionFromObjectUnchecked(TypeMirror type) {
return new CastingUncheckedVisitor().visit(type, null);
}
/**
* Visitor that tells whether a type is erased, in the sense of {@link #castIsUnchecked}. Each
* visitX method returns true if its input parameter is true or if the type being visited is
* erased.
*/
private static class CastingUncheckedVisitor extends SimpleTypeVisitor8<Boolean, Void> {
CastingUncheckedVisitor() {
super(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(CastingUncheckedVisitor::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().equals(TypeKind.WILDCARD)) {
WildcardType wildcard = asWildcard(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;
}
TypeElement typeElement = asTypeElement(type);
return typeElement.getQualifiedName().contentEquals("java.lang.Object");
}
}
private MoreTypes() {}
}