diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java
index c8be62556b..e935a3d95b 100644
--- a/common/src/main/java/com/google/auto/common/MoreTypes.java
+++ b/common/src/main/java/com/google/auto/common/MoreTypes.java
@@ -26,6 +26,7 @@
import com.google.common.base.Equivalence;
import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
@@ -88,13 +89,13 @@ public String toString() {
* may be preferred in a number of cases:
*
*
equivalence() {
@@ -347,10 +348,10 @@ private static boolean equal(
/**
* 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
- * this bug whereby
- * the Eclipse compiler returns a value for static classes that is not NoType.
+ * same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than NoType
+ * for a static type. We need this because of this bug whereby the Eclipse
+ * compiler returns a value for static classes that is not NoType.
*/
private static @Nullable TypeMirror enclosingType(DeclaredType t) {
TypeMirror enclosing = t.getEnclosingType();
@@ -836,42 +837,121 @@ public Boolean visitDeclared(DeclaredType type, Void ignored) {
}
/**
- * 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}
+ * Returns {@code true} iff the raw type underlying the given {@link Class} represents the raw
+ * type of the given {@link TypeMirror} and throws an {@link IllegalArgumentException} if the
+ * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}.
+ *
+ * Note: The representation need not be exact. For example, {@linkplain java.util.ArrayList}
+ * represents {@linkplain List}.
*/
public static boolean isTypeOf(final Class> clazz, TypeMirror type) {
- checkNotNull(clazz);
- return type.accept(new IsTypeOf(clazz), null);
+ return type.accept(new isTypeOf(clazz), null);
}
- private static final class IsTypeOf extends SimpleTypeVisitor8 {
+ private static final class isTypeOf extends SimpleTypeVisitor8 {
private final Class> clazz;
- IsTypeOf(Class> clazz) {
- this.clazz = clazz;
+ isTypeOf(Class> clazz) {
+ this.clazz = Preconditions.checkNotNull(clazz);
}
@Override
- protected Boolean defaultAction(TypeMirror type, Void ignored) {
+ protected Boolean defaultAction(TypeMirror type, Void ignore) {
+ return isExactTypeOf(clazz, type);
+ }
+
+ @Override
+ public Boolean visitArray(ArrayType array, Void ignore) {
+ return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
+ }
+
+ @Override
+ public Boolean visitDeclared(DeclaredType type, Void ignore) {
+ return isDeclaredTypeOf(clazz, type);
+ }
+
+ @Override
+ public Boolean visitTypeVariable(TypeVariable type, Void ignore) {
+ TypeMirror upperBoundType = type.getUpperBound();
+ if (upperBoundType.getKind() != TypeKind.INTERSECTION) {
+ return isTypeOf(clazz, upperBoundType);
+ }
+
+ for (TypeMirror UBType : ((IntersectionType) upperBoundType).getBounds()) {
+ if (isTypeOf(clazz, UBType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public Boolean visitWildcard(WildcardType type, Void ignore) {
+ TypeMirror upperBoundType = type.getExtendsBound();
+ return upperBoundType == null || isTypeOf(clazz, upperBoundType);
+ }
+ }
+
+ private static boolean isDeclaredTypeOf(final Class> clazz, DeclaredType declaredType) {
+ if (isExactTypeOf(clazz, declaredType)) {
+ return true;
+ }
+
+ TypeElement typeElement = MoreElements.asType(declaredType.asElement());
+
+ for (TypeMirror i : typeElement.getInterfaces()) {
+ if (isDeclaredTypeOf(clazz, MoreTypes.asDeclared(i))) {
+ return true;
+ }
+ }
+
+ /* For interfaces (including annotation types),
+ * and java.lang.Object, TypeElement#getSuperclass() returns
+ * NoType with the NONE kind.
+ */
+ TypeMirror superClassType = typeElement.getSuperclass();
+ return (superClassType.getKind() != TypeKind.NONE)
+ && isDeclaredTypeOf(clazz, MoreTypes.asDeclared(superClassType));
+ }
+
+ /**
+ * Returns {@code true} iff the raw type underlying the given {@link TypeMirror} represents the
+ * same raw type as the given {@link Class} and throws an {@link IllegalArgumentException} if the
+ * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}
+ */
+ public static boolean isExactTypeOf(final Class> clazz, TypeMirror type) {
+ return type.accept(new isExactTypeOf(clazz), null);
+ }
+
+ private static final class isExactTypeOf extends SimpleTypeVisitor8 {
+ private final Class> clazz;
+
+ isExactTypeOf(Class> clazz) {
+ this.clazz = Preconditions.checkNotNull(clazz);
+ }
+
+ @Override
+ protected Boolean defaultAction(TypeMirror type, Void ignore) {
throw new IllegalArgumentException(type + " cannot be represented as a Class>.");
}
@Override
- public Boolean visitNoType(NoType noType, Void p) {
- if (noType.getKind().equals(TypeKind.VOID)) {
+ public Boolean visitNoType(NoType noType, Void ignore) {
+ if (noType.getKind() == TypeKind.VOID) {
return clazz.equals(Void.TYPE);
}
+
throw new IllegalArgumentException(noType + " cannot be represented as a Class>.");
}
@Override
- public Boolean visitError(ErrorType errorType, Void p) {
+ public Boolean visitError(ErrorType errorType, Void ignore) {
return false;
}
@Override
- public Boolean visitPrimitive(PrimitiveType type, Void p) {
+ public Boolean visitPrimitive(PrimitiveType type, Void ignore) {
switch (type.getKind()) {
case BOOLEAN:
return clazz.equals(Boolean.TYPE);
@@ -895,12 +975,12 @@ public Boolean visitPrimitive(PrimitiveType type, Void p) {
}
@Override
- public Boolean visitArray(ArrayType array, Void p) {
- return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
+ public Boolean visitArray(ArrayType array, Void ignore) {
+ return clazz.isArray() && isExactTypeOf(clazz.getComponentType(), array.getComponentType());
}
@Override
- public Boolean visitDeclared(DeclaredType type, Void ignored) {
+ public Boolean visitDeclared(DeclaredType type, Void ignore) {
TypeElement typeElement = MoreElements.asType(type.asElement());
return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
}
@@ -946,8 +1026,8 @@ private static boolean isObjectType(DeclaredType type) {
/**
* 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}, and a variable corresponding to the {@code E e}
+ * enclosing element must be a supertype of the container type. For example, given a {@code
+ * container} of type {@code Set}, 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(
diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
index 614e26265f..f5727d18ea 100644
--- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java
+++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
@@ -41,8 +41,8 @@
import javax.lang.model.util.SimpleTypeVisitor8;
/**
- * A utility class that traverses {@link Element} instances and ensures that all type information
- * is present and resolvable.
+ * A utility class that traverses {@link Element} instances and ensures that all type information is
+ * present and resolvable.
*
* @author Gregory Kick
*/
@@ -213,7 +213,7 @@ private static boolean validateAnnotationValues(
new SimpleAnnotationValueVisitor8() {
@Override
protected Boolean defaultAction(Object o, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(o.getClass(), expectedType);
+ return MoreTypes.isExactTypeOf(o.getClass(), expectedType);
}
@Override
@@ -254,42 +254,42 @@ public Boolean visitType(TypeMirror type, TypeMirror ignored) {
@Override
public Boolean visitBoolean(boolean b, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Boolean.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Boolean.TYPE, expectedType);
}
@Override
public Boolean visitByte(byte b, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Byte.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Byte.TYPE, expectedType);
}
@Override
public Boolean visitChar(char c, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Character.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Character.TYPE, expectedType);
}
@Override
public Boolean visitDouble(double d, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Double.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Double.TYPE, expectedType);
}
@Override
public Boolean visitFloat(float f, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Float.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Float.TYPE, expectedType);
}
@Override
public Boolean visitInt(int i, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Integer.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Integer.TYPE, expectedType);
}
@Override
public Boolean visitLong(long l, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Long.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Long.TYPE, expectedType);
}
@Override
public Boolean visitShort(short s, TypeMirror expectedType) {
- return MoreTypes.isTypeOf(Short.TYPE, expectedType);
+ return MoreTypes.isExactTypeOf(Short.TYPE, expectedType);
}
};
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
index ba8fccebdd..63dc731612 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
@@ -23,8 +23,14 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.testing.compile.CompilationRule;
+import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.RandomAccess;
import java.util.SortedMap;
+import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -148,9 +154,81 @@ public void isTypeOf_arrayType() {
.isTrue();
}
+ @Test
+ // ArrayList is a list because its parent implements List (checking interface and direct ancestry)
+ public void isTypeOf_listLineage() {
+ TypeMirror type =
+ typeUtils.getDeclaredType(
+ getTypeElementFor(ArrayList.class), getTypeElementFor(String.class).asType());
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(ArrayList.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(List.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(String.class, type))
+ .isFalse();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + LinkedList.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(LinkedList.class, type))
+ .isFalse();
+
+ type = typeUtils.getArrayType(type); // ArrayList[]
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(ArrayList[].class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(List[].class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + LinkedList[].class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(LinkedList[].class, type))
+ .isFalse();
+ }
+
+ @Test
+ // NavigableMap implements SortedMap and SortedMap implements Map (checking interface ancestry)
+ public void isTypeOf_mapLineage() {
+ TypeMirror type = getTypeElementFor(SortedMap.class).asType();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(SortedMap.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Map.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(NavigableMap.class, type))
+ .isFalse();
+
+ // Testing ancestor that is not a direct parent
+ type = getTypeElementFor(NavigableMap.class).asType();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Map.class, type))
+ .isTrue();
+ }
+
+ @Test
+ public void isTypeOf_wildcardCapture() {
+ TypeMirror type =
+ typeUtils.getWildcardType(
+ getTypeElementFor(SortedMap.class).asType(), null); // ? extends SortedMap
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(SortedMap.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Map.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(NavigableMap.class, type))
+ .isFalse();
+ }
+
private interface TestType {
@SuppressWarnings("unused")
> T method0();
+
+ @SuppressWarnings("unused")
+ & RandomAccess> void method1(
+ RANDOM_ACCESS_LIST randomAccessList);
}
@Test
@@ -167,13 +245,49 @@ public void isTypeOf_declaredType() {
.isFalse();
}
+ @Test
+ public void isTypeOf_typeParameterCapture() {
+ assertTrue(MoreTypes.isType(getTypeElementFor(TestType.class).asType()));
+
+ // Getting the type parameter of method0
+ ExecutableElement executableElement =
+ MoreElements.asExecutable(getTypeElementFor(TestType.class).getEnclosedElements().get(0));
+ TypeMirror type = Iterables.getOnlyElement(executableElement.getTypeParameters()).asType();
+
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(SortedMap.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(Map.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(NavigableMap.class, type))
+ .isFalse();
+
+ // Getting parameter type of method1 and checking for intersection type
+ executableElement =
+ MoreElements.asExecutable(getTypeElementFor(TestType.class).getEnclosedElements().get(1));
+ type = Iterables.getOnlyElement(executableElement.getParameters()).asType();
+
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(List.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + RandomAccess.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(RandomAccess.class, type))
+ .isTrue();
+ assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList.class.getCanonicalName())
+ .that(MoreTypes.isTypeOf(ArrayList.class, type))
+ .isFalse();
+ }
+
@Test
public void isTypeOf_fail() {
- TypeMirror methodType =
- Iterables.getOnlyElement(getTypeElementFor(TestType.class).getEnclosedElements()).asType();
- assertFalse(MoreTypes.isType(methodType));
+ assertFalse(
+ MoreTypes.isType(getTypeElementFor(TestType.class).getEnclosedElements().get(0).asType()));
+ TypeMirror method1Type =
+ getTypeElementFor(TestType.class).getEnclosedElements().get(1).asType();
try {
- MoreTypes.isTypeOf(List.class, methodType);
+ MoreTypes.isTypeOf(List.class, method1Type);
fail();
} catch (IllegalArgumentException expected) {
}
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
index b8e84e0859..d71b47e930 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
@@ -502,13 +502,13 @@ public void testIsTypeOf() {
PrimitiveType intType = types.getPrimitiveType(TypeKind.INT);
TypeMirror integerType = types.boxedClass(intType).asType();
WildcardType wildcardType = types.getWildcardType(null, null);
- expect.that(MoreTypes.isTypeOf(int.class, intType)).isTrue();
- expect.that(MoreTypes.isTypeOf(Integer.class, integerType)).isTrue();
- expect.that(MoreTypes.isTypeOf(Integer.class, intType)).isFalse();
- expect.that(MoreTypes.isTypeOf(int.class, integerType)).isFalse();
- expect.that(MoreTypes.isTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse();
+ expect.that(MoreTypes.isExactTypeOf(int.class, intType)).isTrue();
+ expect.that(MoreTypes.isExactTypeOf(Integer.class, integerType)).isTrue();
+ expect.that(MoreTypes.isExactTypeOf(Integer.class, intType)).isFalse();
+ expect.that(MoreTypes.isExactTypeOf(int.class, integerType)).isFalse();
+ expect.that(MoreTypes.isExactTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse();
assertThrows(
- IllegalArgumentException.class, () -> MoreTypes.isTypeOf(Integer.class, wildcardType));
+ IllegalArgumentException.class, () -> MoreTypes.isExactTypeOf(Integer.class, wildcardType));
}
// The type of every field here is such that casting to it provokes an "unchecked" warning.