use of spoon.reflect.visitor.chain.ScanningMode in project spoon by INRIA.
the class SubInheritanceHierarchyResolver method forEachSubTypeInPackage.
/**
* Calls `outputConsumer.apply(subType)` for each sub type of the targetSuperTypes that are found in `inputPackage`.
* Each sub type is returned only once.
* It makes sense to call this method again for example after new super types are added
* by {@link #addSuperType(CtTypeInformation)}.
*
* If this method is called again with same input and configuration, nothing in sent to outputConsumer
* @param outputConsumer the consumer for found sub types
*/
public <T extends CtType<?>> void forEachSubTypeInPackage(final CtConsumer<T> outputConsumer) {
/*
* Set of qualified names of all visited types, independent on whether they are sub types or not.
*/
final Set<String> allVisitedTypeNames = new HashSet<>();
/*
* the queue of types whose super inheritance hierarchy we are just visiting.
* They are potential sub types of an `targetSuperTypes`
*/
final Deque<CtTypeReference<?>> currentSubTypes = new ArrayDeque<>();
// algorithm
// 1) query step: scan input package for sub classes and sub interfaces
final CtQuery q = inputPackage.map(new CtScannerFunction());
// 2) query step: visit only required CtTypes
if (includingInterfaces) {
// the client is interested in sub inheritance hierarchy of interfaces too. Check interfaces, classes, enums, Annotations, but not CtTypeParameters.
q.select(typeFilter);
} else {
// the client is not interested in sub inheritance hierarchy of interfaces. Check only classes and enums.
q.select(classFilter);
}
/*
* 3) query step: for each found CtType, visit it's super inheritance hierarchy and search there for a type which is equal to one of targetSuperTypes.
* If found then all sub types in hierarchy (variable `currentSubTypes`) are sub types of targetSuperTypes. So return them
*/
q.map(new SuperInheritanceHierarchyFunction().includingInterfaces(hasSuperInterface).failOnClassNotFound(failOnClassNotFound).setListener(new CtScannerListener() {
@Override
public ScanningMode enter(CtElement element) {
final CtTypeReference<?> typeRef = (CtTypeReference<?>) element;
String qName = typeRef.getQualifiedName();
if (targetSuperTypes.contains(qName)) {
/*
* FOUND! we are in super inheritance hierarchy, which extends from an searched super type(s).
* All `currentSubTypes` are sub types of searched super type
*/
while (currentSubTypes.size() > 0) {
final CtTypeReference<?> currentTypeRef = currentSubTypes.pop();
String currentQName = currentTypeRef.getQualifiedName();
/*
* Send them to outputConsumer and add then as targetSuperTypes too, to perform faster with detection of next sub types.
*/
if (!targetSuperTypes.contains(currentQName)) {
targetSuperTypes.add(currentQName);
outputConsumer.accept((T) currentTypeRef.getTypeDeclaration());
}
}
// but continue visiting of siblings (do not terminate query)
return SKIP_ALL;
}
if (allVisitedTypeNames.add(qName) == false) {
/*
* this type was already visited, by another way. So it is not sub type of `targetSuperTypes`.
* Stop visiting it's inheritance hierarchy.
*/
return SKIP_ALL;
}
/*
* This type was not visited yet.
* We still do not know whether this type is a sub type of any target super type(s)
* continue searching in super inheritance hierarchy
*/
currentSubTypes.push(typeRef);
return NORMAL;
}
@Override
public void exit(CtElement element) {
CtTypeInformation type = (CtTypeInformation) element;
if (currentSubTypes.isEmpty() == false) {
// remove current type, which is not a sub type of targetSuperTypes from the currentSubTypes
CtTypeInformation stackType = currentSubTypes.pop();
if (stackType != type) {
// the enter/exit was not called consistently. There is a bug in SuperInheritanceHierarchyFunction
throw new SpoonException("CtScannerListener#exit was not called after enter.");
}
}
}
})).forEach(new CtConsumer<CtType<?>>() {
@Override
public void accept(CtType<?> type) {
// we do not care about types visited by query `q`.
// the result of whole mapping function was already produced by `sendResult` call
// but we have to consume all these results to let query running
}
});
}
use of spoon.reflect.visitor.chain.ScanningMode in project spoon by INRIA.
the class EarlyTerminatingScanner method scan.
/*
* we cannot override scan(CtRole role, CtElement element) directly
* because some implementations needs scan(CtElement element), which must be called too
*/
@Override
public void scan(CtElement element) {
if (element == null || isTerminated()) {
return;
}
if (listener == null) {
// the listener is not defined
// visit this element and may be children
doScan(scannedRole, element, ScanningMode.NORMAL);
} else {
// the listener is defined, call it's enter method first
ScanningMode mode = listener.enter(element);
if (mode != ScanningMode.SKIP_ALL) {
// the listener decided to visit this element and may be children
doScan(scannedRole, element, mode);
// then call exit, only if enter returned true
listener.exit(element);
}
// else the listener decided to skip this element and all children. Do not call exit.
}
}
use of spoon.reflect.visitor.chain.ScanningMode in project spoon by INRIA.
the class FilterTest method testCtScannerListener.
@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().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().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 " + ele.getClass(), ele instanceof CtPackage || ele instanceof CtType || ele instanceof CtModule);
// 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);
}
use of spoon.reflect.visitor.chain.ScanningMode in project spoon by INRIA.
the class SuperInheritanceHierarchyFunction method apply.
@Override
public void apply(CtTypeInformation input, CtConsumer<Object> outputConsumer) {
CtTypeReference<?> typeRef;
CtType<?> type;
// detect whether input is a class or something else (e.g. interface)
boolean isClass;
if (input instanceof CtType) {
type = (CtType<?>) input;
typeRef = type.getReference();
} else {
typeRef = (CtTypeReference<?>) input;
try {
type = typeRef.getTypeDeclaration();
} catch (SpoonClassNotFoundException e) {
if (typeRef.getFactory().getEnvironment().getNoClasspath() == false) {
throw e;
}
type = null;
}
}
// if the type is unknown, than we expect it is interface, otherwise we would visit java.lang.Object too, even for interfaces
isClass = type == null ? false : (type instanceof CtClass);
if (isClass == false && includingInterfaces == false) {
// the input is interface, but this scanner should visit only interfaces. Finish
return;
}
ScanningMode mode = enter(typeRef, isClass);
if (mode == SKIP_ALL) {
// listener decided to not visit that input. Finish
return;
}
if (includingSelf) {
sendResult(typeRef, outputConsumer);
if (query.isTerminated()) {
mode = SKIP_CHILDREN;
}
}
if (mode == NORMAL) {
if (isClass == false) {
visitSuperInterfaces(typeRef, outputConsumer);
} else {
// call visitSuperClasses only for input of type class. The contract of visitSuperClasses requires that
visitSuperClasses(typeRef, outputConsumer, includingInterfaces);
}
}
exit(typeRef, isClass);
}
use of spoon.reflect.visitor.chain.ScanningMode in project spoon by INRIA.
the class SuperInheritanceHierarchyFunction method visitSuperClasses.
/**
* calls `outputConsumer.accept(superClass)` for all super classes of superType.
*
* @param superTypeRef the reference to a class. This method is called only for classes. Never for interface
* @param includingInterfaces if true then all superInterfaces of each type are sent to `outputConsumer` too.
*/
protected void visitSuperClasses(CtTypeReference<?> superTypeRef, CtConsumer<Object> outputConsumer, boolean includingInterfaces) {
if (Object.class.getName().equals(superTypeRef.getQualifiedName())) {
// java.lang.Object has no interface or super classes
return;
}
if (includingInterfaces) {
visitSuperInterfaces(superTypeRef, outputConsumer);
if (query.isTerminated()) {
return;
}
}
CtTypeReference<?> superClassRef = superTypeRef.getSuperclass();
if (superClassRef == null) {
// only CtClasses extend object,
// this method is called only for classes (not for interfaces) so we know we can visit java.lang.Object now too
superClassRef = superTypeRef.getFactory().Type().OBJECT;
}
ScanningMode mode = enter(superClassRef, true);
if (mode == SKIP_ALL) {
return;
}
sendResult(superClassRef, outputConsumer);
if (mode == NORMAL && query.isTerminated() == false) {
visitSuperClasses(superClassRef, outputConsumer, includingInterfaces);
}
exit(superClassRef, true);
}
Aggregations