package spoon.test.parent;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import spoon.SpoonException;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitable;
import spoon.support.UnsettableProperty;
import spoon.test.SpoonTestHelpers;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static spoon.testing.utils.ModelUtils.createFactory;
// check that all setters of the metamodel call setParent in a correct manner
@RunWith(Parameterized.class)
public class ParentContractTest<T extends CtVisitable> {
private static Factory factory = createFactory();
private static final List<CtType<? extends CtElement>> allInstantiableMetamodelInterfaces = SpoonTestHelpers.getAllInstantiableMetamodelInterfaces();
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> data() throws Exception {
List<Object[]> values = new ArrayList<>();
for (CtType t : allInstantiableMetamodelInterfaces) {
if (!(CtReference.class.isAssignableFrom(t.getActualClass()))) {
values.add(new Object[] { t });
}
}
return values;
}
@Parameterized.Parameter(0)
public CtType<?> toTest;
public static Object createCompatibleObject(CtTypeReference<?> parameterType) {
Class<?> c = parameterType.getActualClass();
for(CtType t : allInstantiableMetamodelInterfaces) {
if (c.isAssignableFrom(t.getActualClass())) {
CtElement argument = factory.Core().create(t.getActualClass());
// an empty package is merged with the existing one
// we have to give it a name
if (argument instanceof CtPackage) {
((CtPackage) argument).setSimpleName(argument.getShortRepresentation());
}
return argument;
}
}
if (Set.class.isAssignableFrom(c)) {
// we create one set with one element
HashSet<Object> objects = new HashSet<>();
objects.add(createCompatibleObject(parameterType.getActualTypeArguments().get(0)));
return objects;
}
if (Collection.class.isAssignableFrom(c)) {
// we create one list with one element
ArrayList<Object> objects = new ArrayList<>();
objects.add(createCompatibleObject(parameterType.getActualTypeArguments().get(0)));
return objects;
}
throw new IllegalArgumentException("cannot instantiate "+parameterType);
}
static int nTotalSetterCalls= 0;
@Test
public void testContract() throws Throwable {
int nSetterCalls= 0;
int nAssertsOnParent = 0;
int nAssertsOnParentInList = 0;
// contract: all setters/adders must set the parent (not necessarily the direct parent, can be upper in the parent tree, for instance when injecting blocks
Object o = factory.Core().create((Class<? extends CtElement>) toTest.getActualClass());
for (CtMethod<?> setter : SpoonTestHelpers.getAllSetters(toTest)) {
Object argument = createCompatibleObject(setter.getParameters().get(0).getType());
try {
// we create a fresh object
CtElement receiver = ((CtElement) o).clone();
// we invoke the setter
Method actualMethod = setter.getReference().getActualMethod();
actualMethod.invoke(receiver, new Object[] { argument });
nSetterCalls++;
nTotalSetterCalls++;
// if it's a settable property
// we check that setParent has been called
// directly the element
if (CtElement.class.isInstance(argument)
&& setter.getAnnotation(UnsettableProperty.class) == null) {
nAssertsOnParent++;
assertTrue(((CtElement)argument).hasParent(receiver));
}
// the element is in a list
if (Collection.class.isInstance(argument)
&& setter.getAnnotation(UnsettableProperty.class) == null) {
nAssertsOnParentInList++;
assertTrue(((CtElement)((Collection)argument).iterator().next()).hasParent(receiver));
}
} catch (AssertionError e) {
Assert.fail("call setParent contract failed for " + setter.toString() + " " + e.toString());
} catch (InvocationTargetException e) {
if (e.getCause() instanceof UnsupportedOperationException) {
// fail-safe contract: we can always call a setter
// this simplifies client code which does not have to write defensive if/then or try/catch
// if the setter does nothing
// this is now documented by @UnsettableProperty
throw e;
} else if (e.getCause() instanceof RuntimeException) {
throw e.getCause();
} else {
throw new SpoonException(e.getCause());
}
}
}
assertTrue(nSetterCalls > 0);
assertTrue(nAssertsOnParent > 0 || nAssertsOnParentInList > 0);
}
}