package org.androidtransfuse.adapter;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import org.androidtransfuse.adapter.classes.ASTClassFactory;
import org.junit.Before;
import org.junit.Test;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author John Ericksen
*/
public class ReflectionEquivalenceTest {
private static final ASTType OBJECT_TYPE = new ASTStringType(Object.class.getCanonicalName());
private ASTClassFactory classFactory;
private Set<Class> visited;
@Before
public void setup() {
classFactory = new ASTClassFactory();
visited = new HashSet<Class>();
}
private static class Base {
String value4;
public String getValue4() {
return value4;
}
public void setValue4(String value4) {
this.value4 = value4;
}
}
public static class TestTarget extends Base implements Serializable {
private static final long serialVersionUID = 1198606035702824074L;
String[] value1;
String value2;
int value3;
public TestTarget(String[] value1, String value2, int value3) {
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
public TestTarget(String value2, int value3) {
this(null, value2, value3);
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String[] getValue1() {
return value1;
}
public void setValue1(String[] value1) {
this.value1 = value1;
}
public String getValue2() {
return value2;
}
public void setValue2(String value2) {
this.value2 = value2;
}
public int getValue3() {
return value3;
}
public void setValue3(int value3) {
this.value3 = value3;
}
}
@Test
public void testReflectionEquivalence() {
ASTType testTargetType = classFactory.getType(TestTarget.class);
Class<TestTarget> testTargetClass = TestTarget.class;
equals(testTargetClass, testTargetType);
}
private void equals(Class clazz, ASTType type) {
if(visited.contains(clazz)){
return;
}
visited.add(clazz);
assertEquals(clazz.getName(), !clazz.isInterface(), type.isConcreteClass());
assertEquals(clazz.isInterface(), type.isInterface());
assertEquals(java.lang.reflect.Modifier.isFinal(clazz.getModifiers()), type.isFinal());
assertEquals(java.lang.reflect.Modifier.isStatic(clazz.getModifiers()), type.isStatic());
assertEquals(clazz.isEnum(), type.isEnum());
assertEquals(Modifier.isStatic(clazz.getModifiers()), type.isStatic());
assertEquals(Modifier.isFinal(clazz.getModifiers()), type.isFinal());
assertEquals(clazz.getCanonicalName(), type.getName());
assertEquals(ASTAccessModifier.getModifier(clazz.getModifiers()), type.getAccessModifier());
assertTrue((clazz.getSuperclass() == null || clazz.getSuperclass().equals(Object.class)) ==
((type.getSuperClass() == null || type.getSuperClass().equals(OBJECT_TYPE))));
if(clazz.getSuperclass() != null) {
equals(clazz.getSuperclass(), type.getSuperClass());
}
if(!clazz.isPrimitive() && !clazz.isArray()) {
assertEquals(clazz.getInterfaces().length, type.getInterfaces().size());
for (Class clazzInterface : clazz.getInterfaces()) {
ASTType typeInterface = findInterface(type, clazzInterface);
if(typeInterface == null){
assertTrue("Unable to find interface " + clazzInterface.getCanonicalName(), false);
}
equals(clazzInterface, typeInterface);
}
assertEquals(filter(clazz.getDeclaredMethods()).size(), type.getMethods().size());
for (Method method : filter(clazz.getDeclaredMethods())) {
ASTMethod typeMethod = findMethod(type, method);
if(typeMethod == null){
assertTrue("Unable to find method " + method, false);
}
assertEquals(ASTAccessModifier.getModifier(method.getModifiers()), typeMethod.getAccessModifier());
assertEquals(Modifier.isStatic(method.getModifiers()), typeMethod.isStatic());
assertEquals(Modifier.isFinal(method.getModifiers()), typeMethod.isFinal());
equals(method.getReturnType(), typeMethod.getReturnType());
for(int i = 0; i < method.getParameters().length; i++) {
equals(method.getParameters()[i].getType(), typeMethod.getParameters().get(i).getASTType());
}
}
assertEquals(filter(clazz.getDeclaredFields()).size(), type.getFields().size());
for (Field field : filter(clazz.getDeclaredFields())) {
ASTField typeField = findField(type, field);
if(typeField == null){
assertTrue("Unable to find field " + field, false);
}
assertEquals(ASTAccessModifier.getModifier(field.getModifiers()), typeField.getAccessModifier());
assertEquals(Modifier.isTransient(field.getModifiers()), typeField.isTransient());
assertEquals(Modifier.isStatic(field.getModifiers()), typeField.isStatic());
assertEquals(Modifier.isFinal(field.getModifiers()), typeField.isFinal());
equals(field.getType(), typeField.getASTType());
}
assertEquals(filter(clazz.getDeclaredConstructors()).size(), type.getConstructors().size());
for (Constructor constructor : filter(clazz.getDeclaredConstructors())) {
int parameterOffset = 0;
if(clazz.isEnum()) {
parameterOffset = 2;
}
if(clazz.getDeclaringClass() != null && !Modifier.isStatic(clazz.getModifiers())){
parameterOffset = 1;
}
ASTConstructor typeConstructor = findConstructor(type, constructor, parameterOffset);
if(typeConstructor == null){
assertTrue("Unable to find constructor " + constructor, false);
}
for(int i = parameterOffset; i < constructor.getParameters().length; i++) {
equals(constructor.getParameters()[i].getType(), typeConstructor.getParameters().get(i - parameterOffset).getASTType());
}
}
assertEquals(clazz.getTypeParameters().length, type.getGenericArgumentTypes().size());
for(int i = 0; i < clazz.getTypeParameters().length; i++) {
if(clazz.getTypeParameters()[i] instanceof ParameterizedType) {
Class genericType = (Class)((ParameterizedType)clazz.getTypeParameters()[i]).getRawType();
equals(genericType, type.getGenericArgumentTypes().get(i));
}
}
}
// ImmutableList<ASTType> getGenericParameters();
}
private ASTConstructor findConstructor(ASTType type, Constructor constructor, int sizeOffset) {
for (ASTConstructor typeConstructor : type.getConstructors()) {
if(typeConstructor.getParameters().size() == constructor.getParameters().length - sizeOffset){
boolean found = true;
for(int i = sizeOffset; i < constructor.getParameters().length; i++) {
if(!classFactory.getType(constructor.getParameters()[i].getType()).equals(typeConstructor.getParameters().get(i - sizeOffset).getASTType())){
found = false;
}
}
if(found) {
return typeConstructor;
}
}
}
return null;
}
private List<Method> filter(Method[] declaredMethods) {
return FluentIterable.from(Arrays.asList(declaredMethods))
.filter(new Predicate<Method>() {
@Override
public boolean apply(Method method) {
return !method.isBridge() && !method.isSynthetic();
}
})
.toList();
}
private List<Field> filter(Field[] fields) {
return FluentIterable.from(Arrays.asList(fields))
.filter(new Predicate<Field>() {
@Override
public boolean apply(Field field) {
return !field.isSynthetic();
}
})
.toList();
}
private List<Constructor> filter(Constructor[] constructors) {
return FluentIterable.from(Arrays.asList(constructors))
.filter(new Predicate<Constructor>() {
@Override
public boolean apply(Constructor constructor) {
return !constructor.isSynthetic();
}
})
.toList();
}
private ASTField findField(ASTType type, Field field) {
for (ASTField astField : type.getFields()) {
if(astField.getName().equals(field.getName())){
return astField;
}
}
return null;
}
private ASTMethod findMethod(ASTType type, Method method) {
MethodSignature clazzMethodSignature = buildMethodSignature(method);
for (ASTMethod astMethod : type.getMethods()) {
MethodSignature astMethodSignature = new MethodSignature(astMethod);
if(astMethodSignature.equals(clazzMethodSignature)) {
return astMethod;
}
}
return null;
}
private MethodSignature buildMethodSignature(Method method) {
List<ASTType> paramTypes = FluentIterable.from(Arrays.asList(method.getParameterTypes()))
.transform(new Function<Class<?>, ASTType>() {
@Override
public ASTType apply(@Nullable Class<?> clazz) {
return classFactory.getType(clazz);
}
})
.toList();
return new MethodSignature(method.getName(), paramTypes);
}
private ASTType findInterface(ASTType type, Class clazzInterface) {
for (ASTType typeInterface : type.getInterfaces()) {
if(typeInterface.getName().equals(clazzInterface.getCanonicalName())){
return typeInterface;
}
}
return null;
}
}