Skip to content

Instantly share code, notes, and snippets.

@filiphr
Forked from cykl/TypeResolverTest.java
Last active July 5, 2017 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save filiphr/bda7002922ab2a9e1bbb7011fb4a166c to your computer and use it in GitHub Desktop.
Save filiphr/bda7002922ab2a9e1bbb7011fb4a166c to your computer and use it in GitHub Desktop.
Code sample to figure out how to check whether the return type of two methods are equals (including generics). See https://github.com/joel-costigliola/assertj-core/issues/1005
package cma.sandox;
import com.google.common.reflect.TypeResolver;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
public class TypeResolverTest implements WithAssertions {
static class Api {
public static <T> Asssert<T> m(List<? extends T> in) {
return null;
}
public static <T> Asssert<T> mSame(List<? extends T> in) {
return null;
}
public static <T> Asssert<? extends T> mExtends(List<? extends T> in) {
return null;
}
public static <T> Asssert<? super T> mSuper(List<? extends T> in) {
return null;
}
}
@Test
public void T_and_T_are_equals() {
Type m = resolveGenericReturnType(Api.class, "m");
Type mSame = resolveGenericReturnType(Api.class, "mSame");
assertThat(m).isEqualTo(mSame);
}
@Test
public void T_and_QUESTION_MARK_extends_T_are_not_equals() {
assertThat(resolveGenericReturnType(Api.class, "m"))
.isNotEqualTo(resolveGenericReturnType(Api.class, "mExtends"));
}
@Test
public void T_and_QUESTION_MARK_super_T_are_not_equals() {
assertThat(resolveGenericReturnType(Api.class, "m"))
.isNotEqualTo(resolveGenericReturnType(Api.class, "mSuper"));
}
private static Type resolveGenericReturnType(Class<?> cls, String methodName) {
Method method = Arrays.stream(cls.getMethods())
.filter(m -> m.getName().equals(methodName))
.findFirst()
.orElseThrow(() -> new RuntimeException("Method not found: class=" + cls + " name=" + methodName));
return resolveGenericReturnType(method);
}
private static Type resolveGenericReturnType(Method method) {
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
TypeResolver typeResolver = new TypeResolver();
for (Type type : parameterizedType.getActualTypeArguments()) {
typeResolver = typeResolver.where(type, String.class);
}
return typeResolver.resolveType(parameterizedType);
} else {
return genericReturnType;
}
}
}
package cma.sandox;
import com.google.common.reflect.TypeResolver;
import org.assertj.core.api.AbstractListAssert;
import org.assertj.core.api.ObjectAssert;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;
public class TypeResolverTest2 implements WithAssertions {
static class Asssert<T> {
}
static class Api {
public static <T> Asssert<T> m(List<? extends T> in) {
return null;
}
public static <T> Asssert<T> mSame(List<? extends T> in) {
return null;
}
public static <T> Asssert<? extends T> mExtends(List<? extends T> in) {
return null;
}
public static <T> Asssert<? super T> mSuper(List<? extends T> in) {
return null;
}
public static <T> AbstractListAssert<?, List<? extends T>, T, ObjectAssert<T>> complex1(List<? extends T> in) {
return null;
}
public static <T> AbstractListAssert<?, List<? extends T>, T, ObjectAssert<T>> complex2(List<? extends T> in) {
return null;
}
public static <T extends AssertDelegateTarget> T returnsT(T assertion) {
return null;
}
public static <T extends AssertDelegateTarget> T returnsT2(T assertion) {
return null;
}
public static <K, V> MapAssert<K, V> doubleTypeVariables(Map<K, V> actual) {
return null;
}
public static <K, V> MapAssert<K, V> doubleTypeVariables2(Map<K, V> actual) {
return null;
}
public static <ELEMENT> ListAssert<ELEMENT> listAssert(List<? extends ELEMENT> actual) {
return null;
}
public static <T> ListAssert<T> listAssert2(List<? extends T> actual) {
return null;
}
public static <T> T[] genericArray(T[] actual) {
return null;
}
public static <ELEMENT> ELEMENT[] genericArray2(ELEMENT[] actual) {
return null;
}
public static <T> T[][] doubleGenericArray(T[] actual) {
return null;
}
public static <ELEMENT> ELEMENT[][] doubleGenericArray2(ELEMENT[] actual) {
return null;
}
}
@Test
public void T_and_T_are_equals() {
Type m = resolveGenericReturnType(Api.class, "m");
Type mSame = resolveGenericReturnType(Api.class, "mSame");
assertThat(m).isEqualTo(mSame);
}
@Test
public void T_and_QUESTION_MARK_extends_T_are_not_equals() {
assertThat(resolveGenericReturnType(Api.class, "m"))
.isNotEqualTo(resolveGenericReturnType(Api.class, "mExtends"));
}
@Test
public void T_and_QUESTION_MARK_super_T_are_not_equals() {
assertThat(resolveGenericReturnType(Api.class, "m"))
.isNotEqualTo(resolveGenericReturnType(Api.class, "mSuper"));
}
@Test
public void QUESTION_MARK_extends_T_and_QUESTION_MARK_extends_ELEMENT_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "mExtends"))
.isEqualTo(resolveGenericReturnType(Api.class, "mExtendsElement"));
}
@Test
public void QUESTION_MARK_super_T_and_QUESTION_MARK_super_ELEMENT_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "mSuper"))
.isEqualTo(resolveGenericReturnType(Api.class, "mSuperElement"));
}
@Test
public void complex() {
Type m = resolveGenericReturnType(Api.class, "complex1");
Type mSame = resolveGenericReturnType(Api.class, "complex2");
assertThat(m).isEqualTo(mSame);
}
@Test
public void T_extends_something_returns_T_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "returnsT"))
.isEqualTo(resolveGenericReturnType(Api.class, "returnsT2"));
}
@Test
public void list_asserts_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "listAssert"))
.isEqualTo(resolveGenericReturnType(Api.class, "listAssert2"));
}
@Test
public void K_and_V_and_K_and_V_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "doubleTypeVariables"))
.isEqualTo(resolveGenericReturnType(Api.class, "doubleTypeVariables2"));
}
@Test
public void generic_array_T_and_generic_array_ELEMENT_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "genericArray"))
.isEqualTo(resolveGenericReturnType(Api.class, "genericArray2"));
}
@Test
public void double_generic_array_T_and_double_generic_array_ELEMENT_are_equal() {
assertThat(resolveGenericReturnType(Api.class, "doubleGenericArray"))
.isEqualTo(resolveGenericReturnType(Api.class, "doubleGenericArray2"));
}
private static Type resolveGenericReturnType(Class<?> cls, String methodName) {
Method method = Arrays.stream(cls.getMethods())
.filter(m -> m.getName().equals(methodName))
.findFirst()
.orElseThrow(() -> new RuntimeException("Method not found: class=" + cls + " name=" + methodName));
return TypeCanonizer.canonize(method.getGenericReturnType());
}
static class TypeCanonizer {
/**
* Returns a canonical form of {@code initialType} by replacing all {@link TypeVariable} by {@link Class}
* instances.
*
* <p>
* Such a canonical form allows to compare {@link ParameterizedType}s.
* </p>
*/
public static Type canonize(Type initialType) {
if (!(initialType instanceof ParameterizedType || initialType instanceof GenericArrayType
|| initialType instanceof WildcardType || initialType instanceof TypeVariable)) {
return initialType;
}
ReplacementClassSupplier replacementClassSupplier = new ReplacementClassSupplier();
TypeResolver typeResolver = new TypeResolver();
for (TypeVariable typeVariable : findAllTypeVariables(initialType)) {
typeResolver = typeResolver.where(typeVariable, replacementClassSupplier.get());
}
return typeResolver.resolveType(initialType);
}
/** Returns all {@code type}'s {@link TypeVariable} */
private static Set<TypeVariable> findAllTypeVariables(Type type) {
Set<TypeVariable> typeVariables = new LinkedHashSet<>();
findAllTypeVariables(typeVariables, type);
return typeVariables;
}
/** Adds all {@code type}'s {@link TypeVariable} to {@code typeVariables} */
private static void findAllTypeVariables(Set<TypeVariable> typeVariables, Type... types) {
for (Type type: types) {
if (type instanceof ParameterizedType) {
findAllTypeVariables(typeVariables, ((ParameterizedType) type).getActualTypeArguments());
} else if (type instanceof WildcardType) {
findAllTypeVariables(typeVariables, ((WildcardType) type).getUpperBounds());
findAllTypeVariables(typeVariables, ((WildcardType) type).getLowerBounds());
} else if (type instanceof GenericArrayType) {
findAllTypeVariables(typeVariables, ((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof TypeVariable) {
typeVariables.add((TypeVariable) type);
}
}
}
}
static class ReplacementClassSupplier {
static List<Class> REPLACEMENT_TYPES = Arrays.asList(
String.class, Integer.class, Exception.class, InputStream.class, System.class);
private final Queue<Class> classPool;
ReplacementClassSupplier() {
this(REPLACEMENT_TYPES);
}
ReplacementClassSupplier(Collection<Class> classPool) {
this.classPool = new ArrayDeque<>(classPool);
}
/**
* Returns a class which has not yet been returned
*
* @throws IllegalStateException If the replacement class poll is exhausted
*/
Class get() {
Class clazz = classPool.poll();
if (clazz == null) {
throw new IllegalStateException();
}
return clazz;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment