package spoon.test.parent;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import spoon.Launcher;
import spoon.compiler.SpoonResourceHelper;
import spoon.reflect.ast.IntercessionScanner;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtAssignment;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.code.CtVariableRead;
import spoon.reflect.code.CtWhile;
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.declaration.ModifierKind;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtArrayTypeReference;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtParameterReference;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.reflect.visitor.filter.ReferenceTypeFilter;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.UnsettableProperty;
import spoon.test.replace.testclasses.Tacos;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static spoon.testing.utils.ModelUtils.build;
public class ParentTest {
Factory factory;
@Before
public void setup() throws Exception {
Launcher spoon = new Launcher();
spoon.setArgs(new String[] {"--output-type", "nooutput" });
factory = spoon.createFactory();
spoon.createCompiler(
factory,
SpoonResourceHelper
.resources("./src/test/java/spoon/test/parent/Foo.java"))
.build();
}
@Test
public void testParent() throws Exception {
// toString should not throw a parent exception even if parents are not
// set
try {
CtLiteral<Object> literal = factory.Core().createLiteral();
literal.setValue(1);
CtBinaryOperator<?> minus = factory.Core().createBinaryOperator();
minus.setKind(BinaryOperatorKind.MINUS);
minus.setRightHandOperand(literal);
minus.setLeftHandOperand(literal);
} catch (Exception e) {
Assert.fail();
}
}
@Test
public void testParentSet() throws Exception {
CtClass<?> foo = factory.Package().get("spoon.test.parent")
.getType("Foo");
CtMethod<?> fooMethod = foo.getMethodsByName("foo").get(0);
assertEquals("foo", fooMethod.getSimpleName());
CtLocalVariable<?> localVar = (CtLocalVariable<?>) fooMethod.getBody()
.getStatements().get(0);
CtAssignment<?,?> assignment = (CtAssignment<?,?>) fooMethod.getBody()
.getStatements().get(1);
CtLiteral<?> newLit = factory.Code().createLiteral(0);
localVar.setDefaultExpression((CtExpression) newLit);
assertEquals(localVar, newLit.getParent());
CtLiteral<?> newLit2 = factory.Code().createLiteral(1);
assignment.setAssignment((CtExpression) newLit2);
assertEquals(assignment, newLit2.getParent());
}
@Test
public void testParentPackage() throws Exception {
// addType should set Parent
CtClass<?> clazz = factory.Core().createClass();
clazz.setSimpleName("Foo");
CtPackage pack = factory.Core().createPackage();
pack.setSimpleName("bar");
pack.addType(clazz);
assertTrue(pack.getTypes().contains(clazz));
assertEquals(pack, clazz.getParent());
}
@Test
public void testParentOfCtPackageReference() throws Exception {
// contract: a parent at a top level must be the root package and in the code, the element which call getParent().
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.getEnvironment().setNoClasspath(true);
launcher.addInputResource("./src/test/resources/reference-package");
launcher.run();
final CtType<Object> panini = launcher.getFactory().Type().get("Panini");
CtElement topLevelParent = panini.getPackage().getParent();
assertNotNull(topLevelParent);
assertEquals(CtPackage.TOP_LEVEL_PACKAGE_NAME, panini.getPackage().getSimpleName());
CtPackage pack1 = factory.Package().getRootPackage();
// the factory are not the same
assertNotEquals(factory, launcher.getFactory());
// so the root packages are not deeply equals
assertNotEquals(pack1, topLevelParent);
final CtTypeReference<?> burritos = panini.getElements(new ReferenceTypeFilter<CtTypeReference<?>>(CtTypeReference.class) {
@Override
public boolean matches(CtTypeReference<?> reference) {
return "Burritos".equals(reference.getSimpleName()) && super.matches(reference);
}
}).get(0);
assertNotNull(burritos.getPackage().getParent());
assertEquals("com.awesome", burritos.getPackage().getSimpleName());
assertEquals(burritos, burritos.getPackage().getParent());
}
@Test
public void testParentOfCtVariableReference() throws Exception {
// contract: parent of a variable reference is the element which call getVariable().
final Factory factory = build(Tacos.class);
final CtType<Tacos> aTacos = factory.Type().get(Tacos.class);
final CtInvocation inv = aTacos.getMethodsByName("m3").get(0).getElements(new TypeFilter<>(CtInvocation.class)).get(0);
final CtVariableRead<?> variableRead = (CtVariableRead<?>) inv.getArguments().get(0);
final CtParameterReference<?> aParameterReference = (CtParameterReference<?>) variableRead.getVariable();
assertNotNull(aParameterReference.getParent());
assertEquals(variableRead, aParameterReference.getParent());
}
@Test
public void testParentOfCtExecutableReference() throws Exception {
// contract: parent of a executable reference is the element which call getExecutable().
final Factory factory = build(Tacos.class);
final CtType<Tacos> aTacos = factory.Type().get(Tacos.class);
final CtInvocation inv = aTacos.getMethodsByName("m3").get(0).getElements(new TypeFilter<>(CtInvocation.class)).get(0);
final CtExecutableReference oldExecutable = inv.getExecutable();
assertNotNull(oldExecutable.getParent());
assertEquals(inv, oldExecutable.getParent());
}
@Test
public void testParentOfGenericInTypeReference() throws Exception {
// contract: parent of a generic in a type reference is the type reference.
final Factory factory = build(Tacos.class);
final CtTypeReference referenceWithGeneric = Query.getElements(factory, new ReferenceTypeFilter<CtTypeReference>(CtTypeReference.class) {
@Override
public boolean matches(CtTypeReference reference) {
return reference.getActualTypeArguments().size() > 0 && super.matches(reference);
}
}).get(0);
final CtTypeReference<?> generic = referenceWithGeneric.getActualTypeArguments().get(0);
assertNotNull(generic.getParent());
assertEquals(referenceWithGeneric, generic.getParent());
}
@Test
public void testParentOfPrimitiveReference() throws Exception {
// contract: parent of a primitive different isn't different of other type. Its parent is the element which used this type.
final Factory factory = build(Tacos.class);
final CtType<Tacos> aTacos = factory.Type().get(Tacos.class);
final CtMethod<?> aMethod = aTacos.getMethodsByName("m").get(0);
assertNotNull(aMethod.getType().getParent());
assertEquals(factory.Type().INTEGER_PRIMITIVE, aMethod.getType());
assertEquals(aMethod, aMethod.getType().getParent());
}
public static void checkParentContract(CtPackage pack) {
for(CtElement elem: pack.getElements(new TypeFilter<>(CtElement.class))) {
// there is always one parent
Assert.assertNotNull("no parent for "+elem.getClass()+"-"+elem.getPosition(), elem.getParent());
}
// the scanner and the parent are in correspondence
new CtScanner() {
Deque<CtElement> elementStack = new ArrayDeque<CtElement>();
@Override
public void scan(CtElement e) {
if (e==null) { return; }
if (e instanceof CtReference) { return; }
if (!elementStack.isEmpty()) {
assertEquals(elementStack.peek(), e.getParent());
}
elementStack.push(e);
e.accept(this);
elementStack.pop();
};
}.scan(pack);
}
@Test
public void testGetParentWithFilter() throws Exception {
// addType should set Parent
CtClass<Foo> clazz = (CtClass<Foo>) factory.Class().getAll().get(0);
CtMethod<Object> m = clazz.getMethod("m");
// get three = "" in one = two = three = "";
CtExpression statement = ((CtAssignment)((CtAssignment)m.getBody().getStatement(3)).getAssignment()).getAssignment();
CtPackage ctPackage = statement.getParent(new TypeFilter<CtPackage>(CtPackage.class));
assertEquals(Foo.class.getPackage().getName(), ctPackage.getQualifiedName());
CtStatement ctStatement = statement
.getParent(new AbstractFilter<CtStatement>(CtStatement.class) {
@Override
public boolean matches(CtStatement element) {
return element.getParent() instanceof CtStatementList && super.matches(element);
}
});
// the filter has to return one = two = three = ""
assertEquals(m.getBody().getStatement(3), ctStatement);
m = clazz.getMethod("internalClass");
CtStatement ctStatement1 = m.getElements(
new AbstractFilter<CtStatement>(CtStatement.class) {
@Override
public boolean matches(CtStatement element) {
return element instanceof CtLocalVariable && super.matches(element);
}
}).get(0);
// get the top class
ctStatement1.getParent(CtType.class);
CtType parent = ctStatement1
.getParent(new AbstractFilter<CtType>(CtType.class) {
@Override
public boolean matches(CtType element) {
return !element.isAnonymous() && element.isTopLevel() && super.matches(element);
}
});
assertEquals(clazz, parent);
assertNotEquals(ctStatement1.getParent(CtType.class), parent);
// not present element
CtWhile ctWhile = ctStatement1.getParent(new TypeFilter<CtWhile>(CtWhile.class));
assertEquals(null, ctWhile);
CtStatement statementParent = statement
.getParent(new AbstractFilter<CtStatement>(CtStatement.class) {
@Override
public boolean matches(CtStatement element) {
return true;
}
});
// getParent must not return the current element
assertNotEquals(statement, statementParent);
}
@Test
public void testHasParent() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/resources/reference-package/Panini.java");
launcher.getEnvironment().setNoClasspath(true);
launcher.run();
try {
final CtType<Object> aPanini = launcher.getFactory().Type().get("Panini");
assertNotNull(aPanini);
assertFalse(aPanini.hasParent(aPanini.getFactory().Core().createAnnotation()));
assertTrue(aPanini.getMethod("m").hasParent(aPanini));
} catch (NullPointerException e) {
fail();
}
}
@Test
@Ignore // too fragile because of conventions
public void testParentSetInSetter() throws Exception {
// contract: Check that all setters protect their parameter.
final Launcher launcher = new Launcher();
final Factory factory = launcher.getFactory();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.getEnvironment().setNoClasspath(true);
// interfaces.
launcher.addInputResource("./src/main/java/spoon/reflect/code");
launcher.addInputResource("./src/main/java/spoon/reflect/declaration");
launcher.addInputResource("./src/main/java/spoon/reflect/reference");
// implementations.
launcher.addInputResource("./src/main/java/spoon/support/reflect/code");
launcher.addInputResource("./src/main/java/spoon/support/reflect/declaration");
launcher.addInputResource("./src/main/java/spoon/support/reflect/reference");
// Utils.
launcher.addInputResource("./src/test/java/spoon/reflect/ast/");
launcher.buildModel();
// Asserts.
new IntercessionScanner(launcher.getFactory()) {
@Override
protected boolean isToBeProcessed(CtMethod<?> candidate) {
return (candidate.getSimpleName().startsWith("set") //
|| candidate.getSimpleName().startsWith("add")) //
&& candidate.hasModifier(ModifierKind.PUBLIC) //
&& takeSetterForCtElement(candidate) //
&& avoidInterfaces(candidate) //
&& avoidThrowUnsupportedOperationException(candidate);
}
@Override
public void process(CtMethod<?> element) {
if (element.getAnnotation(UnsettableProperty.class) != null) {
// we don't check the contracts for unsettable setters
return;
}
if (element.getSimpleName().startsWith("add")) {
checkAddStrategy(element);
} else {
checkSetStrategy(element);
}
}
private void checkAddStrategy(CtMethod<?> element) {
final CtStatement statement = element.getBody().getStatement(0);
if (!(statement instanceof CtIf)) {
fail("First statement should be an if to check the parameter of the setter." + element.getSignature() + " declared in " + element.getDeclaringType().getQualifiedName());
}
if (!createCheckNull(element.getParameters().get(0)).equals(((CtIf) statement).getCondition())) {
fail("Condition should test if the parameter is null. The condition was " + ((CtIf) statement).getCondition() + "in " + element.getSignature() + " declared in " + element
.getDeclaringType().getQualifiedName());
}
}
private void checkSetStrategy(CtMethod<?> element) {
final CtTypeReference<?> type = element.getParameters().get(0).getType();
if (!COLLECTIONS.contains(type) && !(type instanceof CtArrayTypeReference)) {
CtInvocation<?> setParent = searchSetParent(element.getBody());
if (setParent == null) {
return;
}
try {
if (setParent.getParent(CtIf.class) == null) {
fail("Missing condition in " + element.getSignature() + " declared in the class " + element.getDeclaringType().getQualifiedName());
}
} catch (ParentNotInitializedException e) {
fail("Missing parent condition in " + element.getSignature() + " declared in the class " + element.getDeclaringType().getQualifiedName());
}
}
}
/**
* Creates <code>parameter == null</code>.
*
* @param ctParameter <code>parameter</code>
*/
private CtBinaryOperator<Boolean> createCheckNull(CtParameter<?> ctParameter) {
final CtLiteral nullLiteral = factory.Code().createLiteral(null);
nullLiteral.setType(factory.Type().NULL_TYPE.clone());
final CtBinaryOperator<Boolean> operator = factory.Code().createBinaryOperator( //
factory.Code().createVariableRead(ctParameter.getReference(), true), //
nullLiteral, BinaryOperatorKind.EQ);
operator.setType(factory.Type().BOOLEAN_PRIMITIVE);
return operator;
}
private CtInvocation<?> searchSetParent(CtBlock<?> body) {
final List<CtInvocation<?>> ctInvocations = body.getElements(new TypeFilter<CtInvocation<?>>(CtInvocation.class) {
@Override
public boolean matches(CtInvocation<?> element) {
return "setParent".equals(element.getExecutable().getSimpleName()) && super.matches(element);
}
});
return ctInvocations.size() >0 ? ctInvocations.get(0) : null;
}
}.scan(launcher.getModel().getRootPackage());
}
}