/*
Copyright (c) 2009-2011 Olivier Chafik, All Rights Reserved
This file is part of JNAerator (http://jnaerator.googlecode.com/).
JNAerator is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
JNAerator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with JNAerator. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ochafik.lang.jnaerator.parser;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import static org.junit.Assert.*;
import static com.ochafik.lang.SyntaxUtils.*;
import com.ochafik.beans.BeansUtils;
import com.ochafik.junit.ParameterizedWithDescription;
//import com.ochafik.lang.jnaerator.parser.Expression.FunctionCall;
import com.ochafik.lang.reflect.GettersAndSettersHelper;
import com.ochafik.lang.reflect.GettersAndSettersHelper.GetterAndSetterInfo;
import com.nativelibs4java.jalico.Pair;
@SuppressWarnings("unused")
//@RunWith(Parameterized.class)
@RunWith(Parameterized.class)
public class ObjCppElementsTest {
String description;
Class<? extends Element> type;
/// Gives the set of subclasses that implement a given abstract class or interface
Map<Class<?>, Set<Class<?>>> implementations;
public ObjCppElementsTest(String description, Class<? extends Element> type, Map<Class<?>, Set<Class<?>>> implementations) {
super();
this.description = description;
this.type = type;
this.implementations = implementations;
}
final static Set<String> fieldsExcludedFromGetterSetterChecks = new HashSet<String>();
static {
fieldsExcludedFromGetterSetterChecks.add("previousSibling");
fieldsExcludedFromGetterSetterChecks.add("nextSibling");
fieldsExcludedFromGetterSetterChecks.add("plain");
fieldsExcludedFromGetterSetterChecks.add("class");
fieldsExcludedFromGetterSetterChecks.add("const");
fieldsExcludedFromGetterSetterChecks.add("unsigned");
fieldsExcludedFromGetterSetterChecks.add("gettersAndSetters");
fieldsExcludedFromGetterSetterChecks.add("modifiersStringPrefix");
fieldsExcludedFromGetterSetterChecks.add("id");
fieldsExcludedFromGetterSetterChecks.add("varArgs");
fieldsExcludedFromGetterSetterChecks.add("parents");
fieldsExcludedFromGetterSetterChecks.add("pathInFramework");
fieldsExcludedFromGetterSetterChecks.add("plainStorage");
}
// @Test
// public void checkDerivedClone() {
// try {
// Method m = type.getMethod("clone");
// assertEquals(m + " must have a return type of " + type.getSimpleName(), m.getReturnType(), type);
// } catch (Exception e) {
// assertTrue("Failed to get clone method (bad visibility ?)", false);
// }
// }
@Test
public void checkGettersAndSetters() {
Element element = newElement();
GettersAndSettersHelper helper = element.getGettersAndSetters();//new GettersAndSettersHelper(type);
for (Map.Entry<String, GetterAndSetterInfo> e : helper.gettersAndSetters.entrySet()) {
GetterAndSetterInfo p = e.getValue();
String fieldName = e.getKey();
Type fieldType = helper.getFieldType(fieldName );
String className = element.getClass().getSimpleName();
assertNotNull("Field " + className + "." + fieldName + " does not have a getter !", p.getter);
if (!fieldsExcludedFromGetterSetterChecks.contains(fieldName))
assertNotNull("Field " + className + "." + fieldName + " does not have a setter !", p.setter);
if (p.setter == null)
continue;
helper.assertConsistentPair(p);
// System.err.println("Testing field " + type.getSimpleName() + "." + fieldName);// + ": ");
if (fieldType instanceof Class<?> && Element.class.isAssignableFrom((Class<?>) fieldType)) {
testSetNewInstancesOf(fieldType, implementations, element, fieldName, p.setter, p.getter, null);
//System.err.println();
} else if (fieldType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) fieldType;
Type rawType = paramType.getRawType();
if (rawType instanceof Class<?>) {
Class<?> rawClass = (Class<?>) rawType;
boolean isList = List.class.isAssignableFrom(rawClass);
if (isList || Set.class.isAssignableFrom(rawClass)) {
Collection<Element> container = isList ? new ArrayList<Element>() : new TreeSet<Element>(new Comparator<Element>() {
public int compare(Element o1, Element o2) {
return o1.toString().compareTo(o2.toString());
}
});
Type[] typeArguments = paramType.getActualTypeArguments();
if (typeArguments.length == 1 && typeArguments[0] instanceof Class<?> && Element.class.isAssignableFrom((Class<?>) typeArguments[0])) {
Class<?> elementType = (Class<?>) typeArguments[0];
testSetNewInstancesOf(elementType, implementations, element, fieldName, p.setter, p.getter, container);
}
} else {
//assertTrue("Field '" + fieldName + "' of type " + fieldType + " cannot be tested !" , false);
}
}
} else if (String.class.isAssignableFrom(as(fieldType, Class.class))) {
Element instance = newElement();
BeansUtils.set(instance, fieldName, String.class, "");
Object v = BeansUtils.get(instance, fieldName);
assertEquals("Testing " + fieldName + " :get(set(\"\"))", v, "");
BeansUtils.set(instance, fieldName, String.class, null);
v = BeansUtils.get(instance, fieldName);
assertEquals(v, null);
}
}
}
private void testSetNewInstancesOf(Type fieldType, Map<Class<?>, Set<Class<?>>> implementations2, Element element, String fieldName, Method setter, Method getter, Collection<Element> container) {
Set<Class<?>> fieldTypeImplementations = implementations.get(fieldType);
assertNotNull("Found no implementation of " + fieldType, fieldTypeImplementations);
for (Class<?> fieldTypeImplClass : fieldTypeImplementations) {
testSetNewInstanceOf(fieldTypeImplClass, element, fieldName, setter, getter, container);
}
}
@SuppressWarnings("unchecked")
private void testSetNewInstanceOf(Class<?> fieldTypeImplClass,
Element element, String fieldName, Method setter, Method getter, Collection<Element> container) {
Element arg = null, replace = null;
try {
arg = (Element) fieldTypeImplClass.newInstance();
replace = (Element) fieldTypeImplClass.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
assertFalse("Failed to create new " + fieldTypeImplClass.getName() + " : " + ex + "; cause : " + ex.getCause(), true);
}
// System.err.print("setter ");
/// Setter
Object valueToBeSet;
if (container == null) {
valueToBeSet = arg;
} else {
container.clear();
container.add(arg);
valueToBeSet = container;
}
try {
setter.invoke(element, valueToBeSet);
if (!fieldName.equals("parentElement")) {
assertNotNull("Parent element not set in " + setter, arg.getParentElement());
assertEquals("Parent element not set properly in " + setter,
element, arg.getParentElement());
}
} catch (Exception ex) {
ex.printStackTrace();
assertFalse("Failed to call " + setter + " with a " + fieldTypeImplClass.getName() + " : " + ex + "; cause : " + ex.getCause(), true);
}
if (!fieldName.equals("parentElement")) {
final Set<Integer> visitedIds = new TreeSet<Integer>();
element.accept(new Scanner() {
@Override
protected void visitElement(Element d) {
if (d != null)
visitedIds.add(d.getId());
super.visitElement(d);
}
});
assertTrue("Scanner did not visit child element " + fieldName + " with a " + fieldTypeImplClass.getName() , visitedIds.contains(arg.getId()));
}
if (getter != null) {
// System.err.print("getter ");
/// Getter
try {
Object got = getter.invoke(element);
Element elementGot = container == null ? (Element)got : ((Collection<Element>)got).iterator().next();
assertEquals("Getter of " + fieldName + " does not return same object as setter in " + type.getName(), arg, elementGot);
if (!fieldName.equals("parentElement")) {
if (container != null) {
container.clear();
container.add(replace);
}
arg.replaceBy(replace);
Object got2 = getter.invoke(element);
Element elementGot2 = container == null ? (Element)got2 : ((Collection<Element>)got2).iterator().next();
// if (arg != elementGot2)
// arg =arg; // debug: break here
assertFalse("Replacement of field " + fieldName + " in " + type.getName() + " failed", arg == elementGot2);
assertEquals("Replacement of field " + fieldName + " in " + type.getName() + " failed", replace, elementGot2);
assertNull("Parent element not removed in " + setter, elementGot.getParentElement());
assertNotNull("Parent element not set in " + setter, elementGot2.getParentElement());
assertEquals("Parent element not set properly in " + setter,
element, elementGot2.getParentElement());
}
} catch (Exception ex) {
ex.printStackTrace();
assertFalse("Failed to call " + setter + " with a " + fieldTypeImplClass.getName() + " : " + ex + "; cause : " + ex.getCause(), true);
}
}
}
private Element newElement() {
try {
return type.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
assertFalse("Failed to create new " + type.getName() + " : " + ex + "; cause : " + ex.getCause(), true);
return null;
}
}
@Parameters
public static List<Object[]> getClassesToTest() throws IOException {
Set<Class<?>> elementClasses = new HashSet<Class<?>>();
for (Method method : Scanner.class.getMethods()) {
for (Class<?> argType : method.getParameterTypes())
if (Element.class.isAssignableFrom(argType))
elementClasses.add(argType);
Class<?> returnType = method.getReturnType();
if (Element.class.isAssignableFrom(returnType))
elementClasses.add(returnType);
}
Map<Class<?>, Set<Class<?>>> implementations = new LinkedHashMap<Class<?>, Set<Class<?>>>();
for (Class<?> c : elementClasses) {
if ((c.getModifiers() & Modifier.ABSTRACT) != 0)
continue;
Class<?> s = c;
do {
Set<Class<?>> cs = implementations.get(s);
if (cs == null)
implementations.put(s, cs = new HashSet<Class<?>>());
cs.add(c);
s = s.getSuperclass();
} while (s != null);
}
List<Object[]> ret = new ArrayList<Object[]>();
for (Class<?> c : elementClasses) {
if ((c.getModifiers() & Modifier.ABSTRACT) != 0)
continue;
ret.add(new Object[] {c.getName(), c, implementations});
}
return ret;
}
public static class TestSiblings {
@Test
public void addSibling() {
VariablesDeclaration d = new VariablesDeclaration();
Declarator s1 = new Declarator.DirectDeclarator(), s2 = new Declarator.DirectDeclarator();
d.setDeclarators(Arrays.asList(s1));
s1.insertSibling(s2, false);
List<Declarator> list = d.getDeclarators();
assertEquals("Failed to add after", 2, list.size());
assertSame("Added, but not after", s1, list.get(0));
assertSame(s2, list.get(1));
s2.replaceBy(null);
list = d.getDeclarators();
assertEquals("Failed to remove added element", 1, list.size());
assertSame("Removed bad element", s1, list.get(0));
d.setDeclarators(Arrays.asList(s1));
s1.insertSibling(s2, true);
list = d.getDeclarators();
assertEquals("Failed to add before", 2, list.size());
assertSame("Added, but not before", s2, list.get(0));
assertSame(s1, list.get(1));
s2.replaceBy(null);
list = d.getDeclarators();
assertEquals("Failed to remove added element", 1, list.size());
assertSame("Removed bad element", s1, list.get(0));
}
}
}