Skip to content

Commit 0626a89

Browse files
committed
Generalized MoreTypes.isTypeOf(), added MoreTypes.isExactTypeOf()`
Previously, `MoreTypes.isTypeOf(Class<?> clazz, TypeMirror type)` checked if the `type` represents the exact same type as the given class. The shortcomings were: 1. The hierarchy of types were ignored. For example, if the `type` represents the type of `ArrayList`, then `isTypeOf(Collection.class, type)` will return false. 2. If the `type` is of `TypeVariable` type, `IllegalArgumentException` is thrown. 3. If the `type` is of `Wildcard` type, `IllegalArgumentException` is thrown. The new generalized implementation addresses these shortcomings. If the previous behavior for checking the exact type is required, `MoreTypes.isExactTypeOf()` can be used.
1 parent 83b16dd commit 0626a89

File tree

2 files changed

+115
-35
lines changed

2 files changed

+115
-35
lines changed

common/src/main/java/com/google/auto/common/MoreTypes.java

+109-29
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.google.common.base.Equivalence;
2828
import com.google.common.base.Optional;
29+
import com.google.common.base.Preconditions;
2930
import com.google.common.collect.ImmutableList;
3031
import com.google.common.collect.ImmutableSet;
3132
import java.util.HashSet;
@@ -88,13 +89,13 @@ public String toString() {
8889
* may be preferred in a number of cases:
8990
*
9091
* <ul>
91-
* <li>If you don't have an instance of {@code Types}.
92-
* <li>If you want a reliable {@code hashCode()} for the types, for example to construct a set
93-
* of types using {@link java.util.HashSet} with {@link Equivalence#wrap(Object)}.
94-
* <li>If you want distinct type variables to be considered equal if they have the same names
95-
* and bounds.
96-
* <li>If you want wildcard types to compare equal if they have the same bounds. {@code
97-
* Types.isSameType} never considers wildcards equal, even when comparing a type to itself.
92+
* <li>If you don't have an instance of {@code Types}.
93+
* <li>If you want a reliable {@code hashCode()} for the types, for example to construct a set
94+
* of types using {@link java.util.HashSet} with {@link Equivalence#wrap(Object)}.
95+
* <li>If you want distinct type variables to be considered equal if they have the same names
96+
* and bounds.
97+
* <li>If you want wildcard types to compare equal if they have the same bounds. {@code
98+
* Types.isSameType} never considers wildcards equal, even when comparing a type to itself.
9899
* </ul>
99100
*/
100101
public static Equivalence<TypeMirror> equivalence() {
@@ -347,10 +348,10 @@ private static boolean equal(
347348

348349
/**
349350
* Returns the type of the innermost enclosing instance, or null if there is none. This is the
350-
* same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than
351-
* NoType for a static type. We need this because of
352-
* <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby
353-
* the Eclipse compiler returns a value for static classes that is not NoType.
351+
* same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than NoType
352+
* for a static type. We need this because of <a
353+
* href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby the Eclipse
354+
* compiler returns a value for static classes that is not NoType.
354355
*/
355356
private static @Nullable TypeMirror enclosingType(DeclaredType t) {
356357
TypeMirror enclosing = t.getEnclosingType();
@@ -836,42 +837,121 @@ public Boolean visitDeclared(DeclaredType type, Void ignored) {
836837
}
837838

838839
/**
839-
* Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw
840-
* type as the given {@link Class} and throws an IllegalArgumentException if the {@link
841-
* TypeMirror} does not represent a type that can be referenced by a {@link Class}
840+
* Returns {@code true} iff the raw type underlying the given {@link Class} represents the raw
841+
* type of the given {@link TypeMirror} and throws an {@link IllegalArgumentException} if the
842+
* {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}.
843+
*
844+
* <p>Note: The representation need not be exact. For example, {@linkplain java.util.ArrayList}
845+
* represents {@linkplain List}.
842846
*/
843847
public static boolean isTypeOf(final Class<?> clazz, TypeMirror type) {
844-
checkNotNull(clazz);
845-
return type.accept(new IsTypeOf(clazz), null);
848+
return type.accept(new isTypeOf(clazz), null);
846849
}
847850

848-
private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> {
851+
private static final class isTypeOf extends SimpleTypeVisitor8<Boolean, Void> {
849852
private final Class<?> clazz;
850853

851-
IsTypeOf(Class<?> clazz) {
852-
this.clazz = clazz;
854+
isTypeOf(Class<?> clazz) {
855+
this.clazz = Preconditions.checkNotNull(clazz);
853856
}
854857

855858
@Override
856-
protected Boolean defaultAction(TypeMirror type, Void ignored) {
859+
protected Boolean defaultAction(TypeMirror type, Void ignore) {
860+
return isExactTypeOf(clazz, type);
861+
}
862+
863+
@Override
864+
public Boolean visitArray(ArrayType array, Void ignore) {
865+
return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
866+
}
867+
868+
@Override
869+
public Boolean visitDeclared(DeclaredType type, Void ignore) {
870+
return isDeclaredTypeOf(clazz, type);
871+
}
872+
873+
@Override
874+
public Boolean visitTypeVariable(TypeVariable type, Void ignore) {
875+
TypeMirror upperBoundType = type.getUpperBound();
876+
if (upperBoundType.getKind() != TypeKind.INTERSECTION) {
877+
return isTypeOf(clazz, upperBoundType);
878+
}
879+
880+
for (TypeMirror UBType : ((IntersectionType) upperBoundType).getBounds()) {
881+
if (isTypeOf(clazz, UBType)) {
882+
return true;
883+
}
884+
}
885+
886+
return false;
887+
}
888+
889+
@Override
890+
public Boolean visitWildcard(WildcardType type, Void ignore) {
891+
TypeMirror upperBoundType = type.getExtendsBound();
892+
return upperBoundType == null || isTypeOf(clazz, upperBoundType);
893+
}
894+
}
895+
896+
private static boolean isDeclaredTypeOf(final Class<?> clazz, DeclaredType declaredType) {
897+
if (isExactTypeOf(clazz, declaredType)) {
898+
return true;
899+
}
900+
901+
TypeElement typeElement = MoreElements.asType(declaredType.asElement());
902+
903+
for (TypeMirror i : typeElement.getInterfaces()) {
904+
if (isDeclaredTypeOf(clazz, MoreTypes.asDeclared(i))) {
905+
return true;
906+
}
907+
}
908+
909+
/* For interfaces (including annotation types),
910+
* and java.lang.Object, TypeElement#getSuperclass() returns
911+
* NoType with the NONE kind.
912+
*/
913+
TypeMirror superClassType = typeElement.getSuperclass();
914+
return (superClassType.getKind() != TypeKind.NONE)
915+
&& isDeclaredTypeOf(clazz, MoreTypes.asDeclared(superClassType));
916+
}
917+
918+
/**
919+
* Returns {@code true} iff the raw type underlying the given {@link TypeMirror} represents the
920+
* same raw type as the given {@link Class} and throws an {@link IllegalArgumentException} if the
921+
* {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}
922+
*/
923+
public static boolean isExactTypeOf(final Class<?> clazz, TypeMirror type) {
924+
return type.accept(new isExactTypeOf(clazz), null);
925+
}
926+
927+
private static final class isExactTypeOf extends SimpleTypeVisitor8<Boolean, Void> {
928+
private final Class<?> clazz;
929+
930+
isExactTypeOf(Class<?> clazz) {
931+
this.clazz = Preconditions.checkNotNull(clazz);
932+
}
933+
934+
@Override
935+
protected Boolean defaultAction(TypeMirror type, Void ignore) {
857936
throw new IllegalArgumentException(type + " cannot be represented as a Class<?>.");
858937
}
859938

860939
@Override
861-
public Boolean visitNoType(NoType noType, Void p) {
862-
if (noType.getKind().equals(TypeKind.VOID)) {
940+
public Boolean visitNoType(NoType noType, Void ignore) {
941+
if (noType.getKind() == TypeKind.VOID) {
863942
return clazz.equals(Void.TYPE);
864943
}
944+
865945
throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>.");
866946
}
867947

868948
@Override
869-
public Boolean visitError(ErrorType errorType, Void p) {
949+
public Boolean visitError(ErrorType errorType, Void ignore) {
870950
return false;
871951
}
872952

873953
@Override
874-
public Boolean visitPrimitive(PrimitiveType type, Void p) {
954+
public Boolean visitPrimitive(PrimitiveType type, Void ignore) {
875955
switch (type.getKind()) {
876956
case BOOLEAN:
877957
return clazz.equals(Boolean.TYPE);
@@ -895,12 +975,12 @@ public Boolean visitPrimitive(PrimitiveType type, Void p) {
895975
}
896976

897977
@Override
898-
public Boolean visitArray(ArrayType array, Void p) {
899-
return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType());
978+
public Boolean visitArray(ArrayType array, Void ignore) {
979+
return clazz.isArray() && isExactTypeOf(clazz.getComponentType(), array.getComponentType());
900980
}
901981

902982
@Override
903-
public Boolean visitDeclared(DeclaredType type, Void ignored) {
983+
public Boolean visitDeclared(DeclaredType type, Void ignore) {
904984
TypeElement typeElement = MoreElements.asType(type.asElement());
905985
return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
906986
}
@@ -946,8 +1026,8 @@ private static boolean isObjectType(DeclaredType type) {
9461026
/**
9471027
* Resolves a {@link VariableElement} parameter to a method or constructor based on the given
9481028
* container, or a member of a class. For parameters to a method or constructor, the variable's
949-
* enclosing element must be a supertype of the container type. For example, given a
950-
* {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
1029+
* enclosing element must be a supertype of the container type. For example, given a {@code
1030+
* container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
9511031
* parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
9521032
*/
9531033
public static TypeMirror asMemberOf(

common/src/test/java/com/google/auto/common/MoreTypesTest.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -502,13 +502,13 @@ public void testIsTypeOf() {
502502
PrimitiveType intType = types.getPrimitiveType(TypeKind.INT);
503503
TypeMirror integerType = types.boxedClass(intType).asType();
504504
WildcardType wildcardType = types.getWildcardType(null, null);
505-
expect.that(MoreTypes.isTypeOf(int.class, intType)).isTrue();
506-
expect.that(MoreTypes.isTypeOf(Integer.class, integerType)).isTrue();
507-
expect.that(MoreTypes.isTypeOf(Integer.class, intType)).isFalse();
508-
expect.that(MoreTypes.isTypeOf(int.class, integerType)).isFalse();
509-
expect.that(MoreTypes.isTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse();
505+
expect.that(MoreTypes.isExactTypeOf(int.class, intType)).isTrue();
506+
expect.that(MoreTypes.isExactTypeOf(Integer.class, integerType)).isTrue();
507+
expect.that(MoreTypes.isExactTypeOf(Integer.class, intType)).isFalse();
508+
expect.that(MoreTypes.isExactTypeOf(int.class, integerType)).isFalse();
509+
expect.that(MoreTypes.isExactTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse();
510510
assertThrows(
511-
IllegalArgumentException.class, () -> MoreTypes.isTypeOf(Integer.class, wildcardType));
511+
IllegalArgumentException.class, () -> MoreTypes.isExactTypeOf(Integer.class, wildcardType));
512512
}
513513

514514
// The type of every field here is such that casting to it provokes an "unchecked" warning.

0 commit comments

Comments
 (0)