package spoon.test.filters;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.fail;
import static spoon.testing.utils.ModelUtils.build;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.reflect.code.CtCFlowBreak;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtLoop;
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtSwitch;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtInterface;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.chain.CtConsumableFunction;
import spoon.reflect.visitor.chain.CtQueryImpl;
import spoon.reflect.visitor.chain.CtScannerListener;
import spoon.reflect.visitor.chain.QueryFailurePolicy;
import spoon.reflect.visitor.chain.ScanningMode;
import spoon.reflect.visitor.chain.CtConsumer;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.reflect.visitor.filter.AnnotationFilter;
import spoon.reflect.visitor.filter.CompositeFilter;
import spoon.reflect.visitor.filter.CtScannerFunction;
import spoon.reflect.visitor.filter.FieldAccessFilter;
import spoon.reflect.visitor.filter.FilteringOperator;
import spoon.reflect.visitor.filter.InvocationFilter;
import spoon.reflect.visitor.filter.LineFilter;
import spoon.reflect.visitor.filter.NameFilter;
import spoon.reflect.visitor.filter.OverriddenMethodFilter;
import spoon.reflect.visitor.filter.OverriddenMethodQuery;
import spoon.reflect.visitor.filter.OverridingMethodFilter;
import spoon.reflect.visitor.filter.ParentFunction;
import spoon.reflect.visitor.filter.RegexFilter;
import spoon.reflect.visitor.filter.ReturnOrThrowFilter;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.comparator.DeepRepresentationComparator;
import spoon.support.reflect.declaration.CtMethodImpl;
import spoon.test.filters.testclasses.AbstractTostada;
import spoon.test.filters.testclasses.Antojito;
import spoon.test.filters.testclasses.FieldAccessFilterTacos;
import spoon.test.filters.testclasses.ITostada;
import spoon.test.filters.testclasses.SubTostada;
import spoon.test.filters.testclasses.Tacos;
import spoon.test.filters.testclasses.Tostada;
import spoon.testing.utils.ModelUtils;
public class FilterTest {
Factory factory;
@Before
public void setup() throws Exception {
factory = ModelUtils.build(Foo.class);
}
@Test
public void testFilters() throws Exception {
CtClass<?> foo = factory.Package().get("spoon.test.filters").getType("Foo");
assertEquals("Foo", foo.getSimpleName());
List<CtExpression<?>> expressions = foo.getElements(new RegexFilter<CtExpression<?>>(".* = .*"));
assertEquals(2, expressions.size());
}
@Test
public void testReturnOrThrowFilter() throws Exception {
CtClass<?> foo = factory.Package().get("spoon.test.filters").getType("Foo");
assertEquals("Foo", foo.getSimpleName());
List<CtCFlowBreak> expressions = foo.getElements(new ReturnOrThrowFilter());
assertEquals(2, expressions.size());
}
@Test
public void testLineFilter() throws Exception {
CtType<FooLine> foo = ModelUtils.buildClass(FooLine.class);
CtMethod method = foo.getMethod("simple");
List<CtStatement> expressions = method.getElements(new LineFilter());
assertEquals(3, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
method = foo.getMethod("loopBlock");
expressions = method.getElements(new LineFilter());
assertEquals(2, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
assertTrue(expressions.get(1).getParent(new LineFilter()) instanceof CtLoop);
method = foo.getMethod("loopNoBlock");
expressions = method.getElements(new LineFilter());
assertEquals(2, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
assertTrue(expressions.get(1).getParent(new LineFilter()) instanceof CtLoop);
method = foo.getMethod("ifBlock");
expressions = method.getElements(new LineFilter());
assertEquals(2, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
assertTrue(expressions.get(1).getParent(new LineFilter()) instanceof CtIf);
method = foo.getMethod("ifNoBlock");
expressions = method.getElements(new LineFilter());
assertEquals(2, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
assertTrue(expressions.get(1).getParent(new LineFilter()) instanceof CtIf);
method = foo.getMethod("switchBlock");
expressions = method.getElements(new LineFilter());
assertEquals(3, expressions.size());
assertNull(expressions.get(0).getParent(new LineFilter()));
assertTrue(expressions.get(1).getParent(new LineFilter()) instanceof CtSwitch);
assertTrue(expressions.get(2).getParent(new LineFilter()) instanceof CtSwitch);
}
@Test
public void testFieldAccessFilter() throws Exception {
// also specifies VariableAccessFilter since FieldAccessFilter is only a VariableAccessFilter with additional static typing
CtClass<?> foo = factory.Package().get("spoon.test.filters").getType("Foo");
assertEquals("Foo", foo.getSimpleName());
List<CtNamedElement> elements = foo.getElements(new NameFilter<>("i"));
assertEquals(1, elements.size());
CtFieldReference<?> ref = (CtFieldReference<?>)(elements.get(0)).getReference();
List<CtFieldAccess<?>> expressions = foo.getElements(new FieldAccessFilter(ref));
assertEquals(2, expressions.size());
final Factory build = build(FieldAccessFilterTacos.class);
final CtType<FieldAccessFilterTacos> fieldAccessFilterTacos = build.Type().get(FieldAccessFilterTacos.class);
try {
List<CtField> fields = fieldAccessFilterTacos.getElements(new TypeFilter<CtField>(CtField.class));
for (CtField ctField : fields) {
fieldAccessFilterTacos.getElements(new FieldAccessFilter(ctField.getReference()));
}
} catch (NullPointerException e) {
fail("FieldAccessFilter must not throw a NPE.");
}
}
@Test
public void testAnnotationFilter() throws Exception {
CtClass<?> foo = factory.Package().get("spoon.test.filters").getType("Foo");
assertEquals("Foo", foo.getSimpleName());
List<CtElement> expressions = foo.getElements(new AnnotationFilter<>(SuppressWarnings.class));
assertEquals(2, expressions.size());
List<CtMethod> methods = foo.getElements(new AnnotationFilter<>(CtMethod.class, SuppressWarnings.class));
assertEquals(1, methods.size());
}
@SuppressWarnings("rawtypes")
@Test
public void filteredElementsAreOfTheCorrectType() throws Exception {
Factory factory = build("spoon.test", "SampleClass").getFactory();
Class<CtMethod> filterClass = CtMethod.class;
TypeFilter<CtMethod> statementFilter = new TypeFilter<CtMethod>(filterClass);
List<CtMethod> elements = Query.getElements(factory, statementFilter);
for (CtMethod element : elements) {
assertTrue(filterClass.isInstance(element));
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Test
public void intersectionOfTwoFilters() throws Exception {
Factory factory = build("spoon.test", "SampleClass").getFactory();
TypeFilter<CtMethod> statementFilter = new TypeFilter<CtMethod>(CtMethod.class);
TypeFilter<CtMethodImpl> statementImplFilter = new TypeFilter<CtMethodImpl>(CtMethodImpl.class);
CompositeFilter compositeFilter = new CompositeFilter(FilteringOperator.INTERSECTION, statementFilter, statementImplFilter);
List<CtMethod> methodsWithInterfaceSuperclass = Query.getElements(factory, statementFilter);
List<CtMethodImpl> methodWithConcreteClass = Query.getElements(factory, statementImplFilter);
assertEquals(methodsWithInterfaceSuperclass.size(), methodWithConcreteClass.size());
assertEquals(methodsWithInterfaceSuperclass, methodWithConcreteClass);
List intersection = Query.getElements(factory, compositeFilter);
assertEquals(methodsWithInterfaceSuperclass.size(), intersection.size());
assertEquals(methodsWithInterfaceSuperclass, intersection);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Test
public void unionOfTwoFilters() throws Exception {
Factory factory = build("spoon.test", "SampleClass").getFactory();
TypeFilter<CtNewClass> newClassFilter = new TypeFilter<CtNewClass>(CtNewClass.class);
TypeFilter<CtMethod> methodFilter = new TypeFilter<CtMethod>(CtMethod.class);
CompositeFilter compositeFilter = new CompositeFilter(FilteringOperator.UNION, methodFilter, newClassFilter);
List filteredWithCompositeFilter = Query.getElements(factory, compositeFilter);
List<CtMethod> methods = Query.getElements(factory, methodFilter);
List<CtNewClass> newClasses = Query.getElements(factory, newClassFilter);
List<CtElement> union = new ArrayList<CtElement>();
union.addAll(methods);
union.addAll(newClasses);
assertEquals(methods.size() + newClasses.size(), union.size());
assertEquals(union.size(), filteredWithCompositeFilter.size());
assertTrue(filteredWithCompositeFilter.containsAll(union));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Test
public void classCastExceptionIsNotThrown() throws Exception {
Factory factory = build("spoon.test", "SampleClass").getFactory();
NameFilter<CtVariable<?>> nameFilterA = new NameFilter<CtVariable<?>>("j");
NameFilter<CtVariable<?>> nameFilterB = new NameFilter<CtVariable<?>>("k");
CompositeFilter compositeFilter = new CompositeFilter(FilteringOperator.INTERSECTION, nameFilterA, nameFilterB);
List filteredWithCompositeFilter = Query.getElements(factory, compositeFilter);
assertTrue(filteredWithCompositeFilter.isEmpty());
}
@Test
public void testOverridingMethodFromAbstractClass() throws Exception {
// contract: When we declare an abstract method on an abstract class, we must return all overriding
// methods in sub classes and anonymous classes.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<AbstractTostada> aClass = launcher.getFactory().Class().get(AbstractTostada.class);
TreeSet<CtMethod<?>> ts = new TreeSet<CtMethod<?>>(new DeepRepresentationComparator());
List<CtMethod<?>> elements = Query.getElements(launcher.getFactory(), new OverridingMethodFilter(aClass.getMethodsByName("prepare").get(0)));
ts.addAll(elements);
assertEquals(5, elements.size());
final List<CtMethod<?>> overridingMethods = Arrays.asList(ts.toArray(new CtMethod[0]));
assertEquals("spoon.test.filters.testclasses.AbstractTostada$1", overridingMethods.get(3).getParent(CtClass.class).getQualifiedName());
assertEquals(Antojito.class, overridingMethods.get(1).getParent(CtClass.class).getActualClass());
assertEquals(SubTostada.class, overridingMethods.get(2).getParent(CtClass.class).getActualClass());
assertEquals("spoon.test.filters.testclasses.Tostada$1", overridingMethods.get(0).getParent(CtClass.class).getQualifiedName());
assertEquals(Tostada.class, overridingMethods.get(4).getParent(CtClass.class).getActualClass());
}
@Test
public void testOverridingMethodFromSubClassOfAbstractClass() throws Exception {
// contract: When we ask all overriding methods from an overriding method, we must returns all methods
// below and not above (including the declaration).
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<Tostada> aTostada = launcher.getFactory().Class().get(Tostada.class);
TreeSet<CtMethod<?>> ts = new TreeSet<CtMethod<?>>(new DeepRepresentationComparator());
List<CtMethod<?>> elements = Query.getElements(launcher.getFactory(), new OverridingMethodFilter(aTostada.getMethodsByName("prepare").get(0)));
ts.addAll(elements);
final List<CtMethod<?>> overridingMethods = Arrays.asList(ts.toArray(new CtMethod[0]));
assertEquals(3, overridingMethods.size());
assertEquals("spoon.test.filters.testclasses.AbstractTostada$1", overridingMethods.get(2).getParent(CtClass.class).getQualifiedName());
assertEquals(SubTostada.class, overridingMethods.get(1).getParent(CtClass.class).getActualClass());
assertEquals("spoon.test.filters.testclasses.Tostada$1", overridingMethods.get(0).getParent(CtClass.class).getQualifiedName());
final CtClass<SubTostada> aSubTostada = launcher.getFactory().Class().get(SubTostada.class);
assertEquals(0, Query.getElements(launcher.getFactory(), new OverridingMethodFilter(aSubTostada.getMethodsByName("prepare").get(0))).size());
}
@Test
public void testOverridingMethodFromInterface() throws Exception {
// contract: When we declare a method in an interface, we must return all overriding
// methods in sub classes and anonymous classes.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtInterface<ITostada> aITostada = launcher.getFactory().Interface().get(ITostada.class);
TreeSet<CtMethod<?>> ts = new TreeSet<CtMethod<?>>(new DeepRepresentationComparator());
List<CtMethod<?>> elements = Query.getElements(launcher.getFactory(), new OverridingMethodFilter(aITostada.getMethodsByName("make").get(0)));
ts.addAll(elements);
final List<CtMethod<?>> overridingMethods = Arrays.asList(ts.toArray(new CtMethod[0]));
assertEquals(4, overridingMethods.size());
assertEquals(AbstractTostada.class, overridingMethods.get(3).getParent(CtType.class).getParent(CtClass.class).getActualClass());
assertEquals("spoon.test.filters.testclasses.AbstractTostada", overridingMethods.get(1).getParent(CtClass.class).getQualifiedName());
assertEquals(Tostada.class, overridingMethods.get(0).getParent(CtClass.class).getActualClass());
assertEquals(Tacos.class, overridingMethods.get(2).getParent(CtClass.class).getActualClass());
}
@Test
public void testOverridingMethodFromSubClassOfInterface() throws Exception {
// contract: When we ask all overriding methods from an overriding method, we must returns all methods
// below and not above (including the declaration).
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<AbstractTostada> anAbstractTostada = launcher.getFactory().Class().get(AbstractTostada.class);
List<CtMethod<?>> overridingMethods = Query.getElements(launcher.getFactory(), new OverridingMethodFilter(anAbstractTostada.getMethodsByName("make").get(0)));
assertEquals(2, overridingMethods.size());
assertEquals("spoon.test.filters.testclasses.AbstractTostada$1", overridingMethods.get(0).getParent(CtClass.class).getQualifiedName());
assertEquals(Tostada.class, overridingMethods.get(1).getParent(CtClass.class).getActualClass());
final CtClass<Tostada> aTostada = launcher.getFactory().Class().get(Tostada.class);
overridingMethods = Query.getElements(launcher.getFactory(), new OverridingMethodFilter(aTostada.getMethodsByName("make").get(0)));
assertEquals(1, overridingMethods.size());
assertEquals("spoon.test.filters.testclasses.AbstractTostada$1", overridingMethods.get(0).getParent(CtClass.class).getQualifiedName());
}
@Test
public void testOverriddenMethodFromAbstractClass() throws Exception {
// contract: When we declare an abstract method on an abstract class, we must return an empty list
// when we ask all overriden methods from this declaration.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<AbstractTostada> aClass = launcher.getFactory().Class().get(AbstractTostada.class);
assertEquals(0, Query.getElements(launcher.getFactory(), new OverriddenMethodFilter(aClass.getMethodsByName("prepare").get(0))).size());
assertEquals(0, aClass.getMethodsByName("prepare").get(0).map(new OverriddenMethodQuery()).list().size());
}
@Test
public void testOverriddenMethodsFromSubClassOfAbstractClass() throws Exception {
// contract: When we ask all overridden methods from an overriding method, we must returns all methods
// above and not below.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<Tostada> aTostada = launcher.getFactory().Class().get(Tostada.class);
final List<CtMethod<?>> overridenMethods = Query.getElements(launcher.getFactory(), new OverriddenMethodFilter(aTostada.getMethodsByName("prepare").get(0)));
assertEquals(1, overridenMethods.size());
assertEquals(AbstractTostada.class, overridenMethods.get(0).getParent(CtClass.class).getActualClass());
final CtClass<SubTostada> aSubTostada = launcher.getFactory().Class().get(SubTostada.class);
final List<CtMethod<?>> overridenMethodsFromSub = Query.getElements(launcher.getFactory(), new OverriddenMethodFilter(aSubTostada.getMethodsByName("prepare").get(0)));
assertEquals(2, overridenMethodsFromSub.size());
assertEquals(AbstractTostada.class, overridenMethodsFromSub.get(0).getParent(CtClass.class).getActualClass());
assertEquals(Tostada.class, overridenMethodsFromSub.get(1).getParent(CtClass.class).getActualClass());
}
@Test
public void testOverriddenMethodFromInterface() throws Exception {
// contract: When we declare a method in an interface, we must return an empty list
// when we ask all overridden methods from this declaration.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtInterface<ITostada> aITostada = launcher.getFactory().Interface().get(ITostada.class);
OverriddenMethodFilter filter = new OverriddenMethodFilter(aITostada.getMethodsByName("make").get(0));
List<CtMethod<?>> overridingMethods = Query.getElements(launcher.getFactory(), filter);
assertEquals(0, overridingMethods.size());
List<CtMethod> overridingMethods2 = aITostada.getMethodsByName("make").get(0).map(new OverriddenMethodQuery()).list(CtMethod.class);
assertEquals(0, overridingMethods2.size());
}
@Test
public void testOverriddenMethodFromSubClassOfInterface() throws Exception {
// contract: When we ask all overridden methods from an overriding method, we must returns all methods
// above and not below.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<AbstractTostada> anAbstractTostada = launcher.getFactory().Class().get(AbstractTostada.class);
final List<CtMethod<?>> overriddenMethods = Query.getElements(launcher.getFactory(), new OverriddenMethodFilter(anAbstractTostada.getMethodsByName("make").get(0)));
assertEquals(1, overriddenMethods.size());
assertEquals(ITostada.class, overriddenMethods.get(0).getParent(CtInterface.class).getActualClass());
final CtClass<Tostada> aTostada = launcher.getFactory().Class().get(Tostada.class);
OverriddenMethodFilter filter = new OverriddenMethodFilter(aTostada.getMethodsByName("make").get(0));
final List<CtMethod<?>> overriddenMethodsFromSub = Query.getElements(launcher.getFactory(), filter);
assertEquals(2, overriddenMethodsFromSub.size());
assertEquals(AbstractTostada.class, overriddenMethodsFromSub.get(0).getParent(CtType.class).getActualClass());
assertEquals(ITostada.class, overriddenMethodsFromSub.get(1).getParent(CtType.class).getActualClass());
}
@Test
public void testInvocationFilterWithExecutableInLibrary() throws Exception {
// contract: When we have an invocation of an executable declared in a library,
// we can filter it and get the executable of the invocation.
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
final CtClass<Tacos> aTacos = launcher.getFactory().Class().get(Tacos.class);
final CtInvocation<?> invSize = aTacos.getElements(new TypeFilter<CtInvocation<?>>(CtInvocation.class) {
@Override
public boolean matches(CtInvocation<?> element) {
if (element.getExecutable() == null) {
return false;
}
return "size".equals(element.getExecutable().getSimpleName()) && super.matches(element);
}
}).get(0);
final List<CtInvocation<?>> invocations = aTacos.getElements(new InvocationFilter(invSize.getExecutable()));
assertEquals(1, invocations.size());
final CtInvocation<?> expectedInv = invocations.get(0);
assertNotNull(expectedInv);
final CtExecutableReference<?> expectedExecutable = expectedInv.getExecutable();
assertNotNull(expectedExecutable);
assertEquals("size", expectedExecutable.getSimpleName());
assertNull(expectedExecutable.getDeclaration());
final CtExecutable<?> declaration = expectedExecutable.getExecutableDeclaration();
assertNotNull(declaration);
assertEquals("size", declaration.getSimpleName());
}
@Test
public void testReflectionBasedTypeFilter() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
//First collect all classes using tested TypeFilter
List<CtClass<?>> allClasses = launcher.getFactory().Package().getRootPackage().getElements(new TypeFilter<CtClass<?>>(CtClass.class));
assertTrue(allClasses.size()>0);
allClasses.forEach(result->{
assertTrue(result instanceof CtClass);
});
//then do it using Filter whose type is computed by reflection
List<CtClass<?>> allClasses2 = launcher.getFactory().Package().getRootPackage().getElements(new Filter<CtClass<?>>() {
@Override
public boolean matches(CtClass<?> element) {
return true;
}
});
assertArrayEquals(allClasses.toArray(), allClasses2.toArray());
//then do it using Filter implemented by lambda expression
List<CtClass<?>> allClasses3 = launcher.getFactory().Package().getRootPackage().getElements((CtClass<?> element)->true);
assertArrayEquals(allClasses.toArray(), allClasses3.toArray());
//last try AbstractFilter constructor without class parameter
final CtClass<Tacos> aTacos = launcher.getFactory().Class().get(Tacos.class);
final CtInvocation<?> invSize = aTacos.getElements(new AbstractFilter<CtInvocation<?>>(/*no class is needed here*/) {
@Override
public boolean matches(CtInvocation<?> element) {
if (element.getExecutable() == null) {
return false;
}
return "size".equals(element.getExecutable().getSimpleName()) && super.matches(element);
}
}).get(0);
assertNotNull(invSize);
}
@Test
public void testQueryStepScannWithConsumer() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
int counter = 0;
}
Context context = new Context();
CtQuery l_qv = launcher.getFactory().getModel().getRootPackage().filterChildren(new TypeFilter<>(CtClass.class));
assertEquals(0, context.counter);
l_qv.forEach(cls->{
assertTrue(cls instanceof CtClass);
context.counter++;
});
assertTrue(context.counter>0);
}
@Test
public void testQueryBuilderWithFilterChain() throws Exception {
// contract: query methods can be lazy evaluated in a foreach
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
CtMethod<?> method;
int count = 0;
}
Context context = new Context();
// chaining queries
CtQuery q = launcher.getFactory().Package().getRootPackage()
.filterChildren(new TypeFilter<CtMethod<?>>(CtMethod.class))
// using a lazily-evaluated lambda
.map((CtMethod<?> method) -> {context.method = method;return method;})
.map(new OverriddenMethodQuery());
// actual evaluation
q.forEach((CtMethod<?> method) -> {
assertTrue(context.method.getReference().isOverriding(method.getReference()));
assertTrue(context.method.isOverriding(method));
context.count++;
});
// sanity check
assertTrue(context.count>0);
}
@Test
public void testFilterQueryStep() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
//Contract: the filter(Filter) can be used to detect if input of query step should pass to next query step.
List<CtElement> realList = launcher.getFactory().Package().getRootPackage().filterChildren(e->{return true;}).select(new TypeFilter<>(CtClass.class)).list();
List<CtElement> expectedList = launcher.getFactory().Package().getRootPackage().filterChildren(new TypeFilter<>(CtClass.class)).list();
assertArrayEquals(expectedList.toArray(), realList.toArray());
assertTrue(expectedList.size()>0);
}
@Test
public void testFilterChildrenWithoutFilterQueryStep() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
//Contract: the filterChildren(null) without Filter returns same results like filter which returns true for each input.
List<CtElement> list = launcher.getFactory().Package().getRootPackage().filterChildren(null).list();
Iterator<CtElement> iter = list.iterator();
launcher.getFactory().Package().getRootPackage().filterChildren(e->{return true;}).forEach(real->{
//the elements produced by each query are same
CtElement expected = iter.next();
if(real!=expected) {
assertEquals(expected, real);
}
});
assertTrue(list.size()>0);
assertTrue(iter.hasNext()==false);
}
@Test
public void testFunctionQueryStep() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
int count = 0;
}
Context context = new Context();
CtQuery query = launcher.getFactory().Package().getRootPackage().filterChildren((CtClass<?> c)->{return true;}).name("filter CtClass only")
.map((CtClass<?> c)->c.getSuperInterfaces()).name("super interfaces")
.map((CtTypeReference<?> iface)->iface.getTypeDeclaration())
.map((CtType<?> iface)->iface.getAllMethods()).name("allMethods if interface")
.map((CtMethod<?> method)->method.getSimpleName().equals("make"))
.map((CtMethod<?> m)->m.getType())
.map((CtTypeReference<?> t)->t.getTypeDeclaration());
((CtQueryImpl)query).logging(true);
query.forEach((CtInterface<?> c)->{
assertEquals("ITostada", c.getSimpleName());
context.count++;
});
assertTrue(context.count>0);
}
@Test
public void testInvalidQueryStep() throws Exception {
// contract: with default policy an exception is thrown is the input type of a query step
// does not correspond to the output type of the previous step
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
try {
launcher.getFactory().Package().getRootPackage().filterChildren((CtClass<?> c)->{return true;}).name("step1")
.map((CtMethod<?> m)->m).name("invalidStep2")
.map((o)->o).name("step3")
.forEach((CtInterface<?> c)->{
fail();
});
fail();
} catch (SpoonException e) {
assertTrue(e.getMessage().indexOf("Step invalidStep2) spoon.support.reflect.declaration.CtClassImpl cannot be cast to spoon.reflect.declaration.CtMethod")>=0);
}
}
@Test
public void testInvalidQueryStepFailurePolicyIgnore() throws Exception {
// contract: with QueryFailurePolicy.IGNORE, no exception is thrown
// and only valid elements are kept for the next step
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
int count = 0;
}
Context context = new Context();
launcher.getFactory().Package().getRootPackage().filterChildren((CtElement c)->{return true;}).name("step1")
.map((CtMethod<?> m)->m).name("invalidStep2")
.map((o)->o).name("step3")
.failurePolicy(QueryFailurePolicy.IGNORE)
.forEach((CtElement c)->{
assertTrue(c instanceof CtMethod);
context.count++;
});
assertTrue(context.count>0);
}
@Test
public void testElementMapFunction() throws Exception {
// contract: a map(Function) can be followed by a forEach(...) or by a list()
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
cls.map((CtClass<?> c)->c.getParent())
.forEach((CtElement e)->{
assertEquals(cls.getParent(), e);
});
assertEquals(cls.getParent(), cls.map((CtClass<?> c)->c.getParent()).list().get(0));
}
@Test
public void testElementMapFunctionOtherContracts() throws Exception {
// contract: when a function returns an array, all non-null values are sent to the next step
final Launcher launcher = new Launcher();
CtQuery q = launcher.getFactory().Query().createQuery().map((String s)->new String[]{"a", null, s});
List<String> list = q.setInput(null).list();
assertEquals(0, list.size());
list = q.setInput("c").list();
assertEquals(2, list.size());
assertEquals("a", list.get(0));
assertEquals("c", list.get(1));
// contract: the input is stored, and we can evaluate the same query several times
list = q.list(); // using "c" as input
assertEquals(2, list.size());
assertEquals("a", list.get(0));
assertEquals("c", list.get(1));
// contract: when input is null then the query function is not called at all.
CtQuery q2 = launcher.getFactory().Query().createQuery().map((String s)->{ throw new AssertionError();});
assertEquals(0, q2.setInput(null).list().size());
}
@Test
public void testElementMapFunctionNull() throws Exception {
// contract: when a function returns null, it is discarded at the next step
final Launcher launcher = new Launcher();
CtQuery q = launcher.getFactory().Query().createQuery().map((String s)->null);
List<String> list = q.setInput("c").list();
assertEquals(0, list.size());
}
@Test
public void testReuseOfQuery() throws Exception {
// contract: a query created from an existing element can be reused on other inputs
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
CtClass<?> cls2 = launcher.getFactory().Class().get(Tostada.class);
// by default the query starts with "cls" as input
CtQuery q = cls.map((CtClass c) -> c.getSimpleName());
// high-level assert
assertEquals(cls.getSimpleName(), q.list().get(0));
// low-level assert on implementation
assertEquals(1, ((CtQueryImpl)q).getInputs().size());
assertSame(cls, ((CtQueryImpl)q).getInputs().get(0));
// now changing the input of query to cls2
q.setInput(cls2);
//the input is still cls2
assertEquals(cls2.getSimpleName(), q.list().get(0));
assertEquals(1, ((CtQueryImpl)q).getInputs().size());
assertSame(cls2, ((CtQueryImpl)q).getInputs().get(0));
}
@Test
public void testReuseOfBaseQuery() throws Exception {
// contract: an empty query can be used on several inputs
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
CtClass<?> cls2 = launcher.getFactory().Class().get(Tostada.class);
// here is the query
CtQuery q = launcher.getFactory().Query().createQuery().map((CtClass c) -> c.getSimpleName());
// using it on a first input
assertEquals("Tacos", q.setInput(cls).list().get(0));
// using it on a second input
assertEquals("Tostada", q.setInput(cls2).list().get(0));
}
// now testing map(CtConsumableFunction)
@Test
public void testElementMapConsumableFunction() throws Exception {
// contract: a method map(CtConsumableFunction) is provided
// a simple consumer.accept() is equivalent to a single return in a CtFunction
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
// long version
class aFunction implements CtConsumableFunction<CtClass> {
@Override
public void apply(CtClass c, CtConsumer out) {
// equivalent to a single return
out.accept(c.getParent());
}
}
assertEquals(cls.getParent(), cls.map(new aFunction()).list().get(0));
// now the same with Java8 one-liner
assertEquals(cls.getParent(), cls.map((CtClass<?> c, CtConsumer<Object> out)->out.accept(c.getParent())).list().get(0));
}
@Test
public void testQueryInQuery() throws Exception {
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
int count = 0;
}
Context context = new Context();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
// first query
CtQuery allChildPublicClasses = launcher.getFactory().Query().createQuery().filterChildren((CtClass clazz)->clazz.hasModifier(ModifierKind.PUBLIC));
// second query,involving the first query
CtQuery q = launcher.getFactory().Package().getRootPackage().map((CtElement in)->allChildPublicClasses.setInput(in).list());
// now the assertions
q.forEach((CtElement clazz)->{
context.count++;
assertTrue(clazz instanceof CtClass);
assertTrue(((CtClass<?>)clazz).hasModifier(ModifierKind.PUBLIC));
});
assertEquals(6, context.count);
context.count=0; //reset
// again second query, but now with CtConsumableFunction
CtQuery q2 = launcher.getFactory().Package().getRootPackage().map((CtElement in, CtConsumer<Object> out)->allChildPublicClasses.setInput(in).forEach(out));
// now the assertions
q2.forEach((CtElement clazz)->{
context.count++;
assertTrue(clazz instanceof CtClass);
assertTrue(((CtClass<?>)clazz).hasModifier(ModifierKind.PUBLIC));
});
assertEquals(6, context.count);
context.count=0; //reset
// again second query, but with low-level circuitry thanks to cast
CtQuery q3 = launcher.getFactory().Package().getRootPackage().map((in, out)->((CtQueryImpl)allChildPublicClasses).evaluate(in, out));
// now the assertions
q3.forEach((CtElement clazz)->{
context.count++;
assertTrue(clazz instanceof CtClass);
assertTrue(((CtClass<?>)clazz).hasModifier(ModifierKind.PUBLIC));
});
assertEquals(6, context.count);
}
@Test
public void testEmptyQuery() throws Exception {
// contract: unbound or empty query
final Launcher launcher = new Launcher();
//contract: empty query returns no element
assertEquals(0, launcher.getFactory().createQuery().list().size());
assertEquals(0, launcher.getFactory().createQuery(null).list().size());
//contract: empty query returns no element
launcher.getFactory().createQuery().forEach(x->fail());
launcher.getFactory().createQuery(null).forEach(x->fail());
//contract: empty query calls no mapping
assertEquals(0, launcher.getFactory().createQuery().map(x->{fail();return true;}).list().size());
assertEquals(0, launcher.getFactory().createQuery(null).map(x->{fail();return true;}).list().size());
//contract: empty query calls no filterChildren
assertEquals(0, launcher.getFactory().createQuery().filterChildren(x->{fail();return true;}).list().size());
assertEquals(0, launcher.getFactory().createQuery(null).filterChildren(x->{fail();return true;}).list().size());
}
@Test
public void testBoundQuery() throws Exception {
// contract: bound query, without any mapping
final Launcher launcher = new Launcher();
//contract: bound query returns bound element
List<String> list = launcher.getFactory().createQuery("x").list();
assertEquals(1, list.size());
assertEquals("x", list.get(0));
}
@Test
public void testClassCastExceptionOnForEach() throws Exception {
// contract: bound query, without any mapping
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
int count = 0;
}
Context context = new Context();
//contract: if the query produces elements which cannot be cast to forEach consumer, then they are ignored
launcher.getFactory().Package().getRootPackage().filterChildren(f->{return true;}).forEach((CtType t)->{
context.count++;
});
assertTrue(context.count>0);
}
@Test
public void testEarlyTerminatingQuery() throws Exception {
// contract: a method first evaluates query until first element is found and then terminates the query
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
boolean wasTerminated = false;
void failIfTerminated(String place) {
assertTrue("The "+place+" is called after query was terminated.", wasTerminated==false);
}
}
Context context = new Context();
CtMethod firstMethod = launcher.getFactory().Package().getRootPackage().filterChildren(e->{
context.failIfTerminated("Filter#match of filterChildren");
return true;
}).map((CtElement e)->{
context.failIfTerminated("Array returning CtFunction#apply of map");
//send result twice to check that second item is skipped
return new CtElement[]{e,e};
}).map((CtElement e)->{
context.failIfTerminated("List returning CtFunction#apply of map");
//send result twice to check that second item is skipped
return Arrays.asList(new CtElement[]{e,e});
}).map((CtElement e, CtConsumer<Object> out)->{
context.failIfTerminated("CtConsumableFunction#apply of map");
if(e instanceof CtMethod) {
//this call should pass and cause termination of the query
out.accept(e);
context.wasTerminated = true;
//let it call out.accept(e); once more to check that next step is not called after query is terminated
}
out.accept(e);
}).map(e->{
context.failIfTerminated("CtFunction#apply of map after CtConsumableFunction");
return e;
}).first(CtMethod.class);
assertTrue(firstMethod!=null);
assertTrue(context.wasTerminated);
}
@Test
public void testParentFunction() throws Exception {
// contract: a mapping function which returns all parents of CtElement
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);
CtLocalVariable<?> varStrings = cls.filterChildren(new NameFilter<>("strings")).first();
class Context {
CtElement expectedParent;
}
Context context = new Context();
context.expectedParent = varStrings;
varStrings.map(new ParentFunction()).forEach((parent)->{
context.expectedParent = context.expectedParent.getParent();
assertSame(context.expectedParent, parent);
});
//context.expectedParent is last visited element
//Check that last visited element was root package
assertSame(launcher.getFactory().getModel().getRootPackage(), context.expectedParent);
//contract: if includingSelf(false), then parent of input element is first element
assertSame(varStrings.getParent(), varStrings.map(new ParentFunction().includingSelf(false)).first());
//contract: if includingSelf(true), then input element is first element
assertSame(varStrings, varStrings.map(new ParentFunction().includingSelf(true)).first());
}
@Test
public void testCtScannerListener() throws Exception {
// contract: CtScannerFunction can be subclassed and configured by a CtScannerListener
final Launcher launcher = new Launcher();
launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
launcher.run();
class Context {
long nrOfEnter = 0;
long nrOfEnterRetTrue = 0;
long nrOfExit = 0;
long nrOfResults = 0;
}
Context context1 = new Context();
// scan only packages until top level classes. Do not scan class internals
List<CtElement> result1 = launcher.getFactory().getModel().getRootPackage().map(new CtScannerFunction().setListener(new CtScannerListener() {
@Override
public ScanningMode enter(CtElement element) {
context1.nrOfEnter++;
if (element instanceof CtType) {
return ScanningMode.SKIP_CHILDREN;
}
return ScanningMode.NORMAL;
}
@Override
public void exit(CtElement element) {
context1.nrOfExit++;
}
})).list();
//check that test is visiting some nodes
assertTrue(context1.nrOfEnter>0);
assertTrue(result1.size()>0);
//contract: if enter is called and returns SKIP_CHILDREN or NORMAL, then exit must be called too. Exceptions are ignored for now
assertEquals(context1.nrOfEnter, context1.nrOfExit);
Context context2 = new Context();
Iterator iter = result1.iterator();
//scan only from packages till top level classes. Do not scan class internals
launcher.getFactory().getModel().getRootPackage().map(new CtScannerFunction().setListener(new CtScannerListener() {
int inClass = 0;
@Override
public ScanningMode enter(CtElement element) {
context2.nrOfEnter++;
if(inClass>0) {
//we are in class. skip this node and all children
return ScanningMode.SKIP_ALL;
}
if (element instanceof CtType) {
inClass++;
}
context2.nrOfEnterRetTrue++;
return ScanningMode.NORMAL;
}
@Override
public void exit(CtElement element) {
context2.nrOfExit++;
if (element instanceof CtType) {
inClass--;
}
assertTrue(inClass==0 || inClass==1);
}
})).forEach(ele->{
context2.nrOfResults++;
assertTrue(ele instanceof CtPackage || ele instanceof CtType);
//check that first and second query returned same results
assertSame(ele, iter.next());
});
//check that test is visiting some nodes
assertTrue(context2.nrOfEnter>0);
assertTrue(context2.nrOfEnter>context2.nrOfEnterRetTrue);
assertEquals(result1.size(), context2.nrOfResults);
//contract: if enter is called and does not returns SKIP_ALL, then exit must be called too. Exceptions are ignored for now
assertEquals(context2.nrOfEnterRetTrue, context2.nrOfExit);
}
}