diff --git a/src/main/java/spoon/support/visitor/SubInheritanceHierarchyResolver.java b/src/main/java/spoon/support/visitor/SubInheritanceHierarchyResolver.java
new file mode 100644
index 00000000000..6abd297df2f
--- /dev/null
+++ b/src/main/java/spoon/support/visitor/SubInheritanceHierarchyResolver.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program 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 CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.support.visitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+
+import spoon.SpoonException;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtElement;
+import spoon.reflect.declaration.CtEnum;
+import spoon.reflect.declaration.CtPackage;
+import spoon.reflect.declaration.CtType;
+import spoon.reflect.declaration.CtTypeInformation;
+import spoon.reflect.declaration.CtTypeParameter;
+import spoon.reflect.reference.CtTypeReference;
+import spoon.reflect.visitor.Filter;
+import spoon.reflect.visitor.chain.CtConsumer;
+import spoon.reflect.visitor.chain.CtQuery;
+import spoon.reflect.visitor.chain.CtScannerListener;
+import spoon.reflect.visitor.chain.ScanningMode;
+import spoon.reflect.visitor.filter.CtScannerFunction;
+import spoon.reflect.visitor.filter.SuperInheritanceHierarchyFunction;
+import spoon.reflect.visitor.filter.TypeFilter;
+
+import static spoon.reflect.visitor.chain.ScanningMode.NORMAL;
+import static spoon.reflect.visitor.chain.ScanningMode.SKIP_ALL;
+
+/**
+ * Expects a {@link CtPackage} as input
+ * and upon calls to forEachSubTypeInPackage produces all sub classes and sub interfaces, which extends or implements super type(s) provided in constructor and stored as `targetSuperTypes`.
+ *
+ * The repeated processing of this mapping function on the same input returns only newly found sub types.
+ * The instance of {@link SubInheritanceHierarchyResolver} returns found sub types only once.
+ * So repeated call with same input package returns nothing.
+ * Create and use new instance of {@link SubInheritanceHierarchyResolver} if you need to scan the subtype hierarchy again.
+ */
+public class SubInheritanceHierarchyResolver {
+
+ /** where the subtypes will be looked for */
+ private CtPackage inputPackage;
+
+ /** whether interfaces are included in the result */
+ private boolean includingInterfaces = true;
+ /**
+ * Set of qualified names of all super types whose sub types we are searching for.
+ * Each found sub type is added to this set too
+ */
+ private Set targetSuperTypes = new HashSet<>();
+ /**
+ * if true then we have to check if type is a subtype of superClass or superInterfaces too
+ * if false then it is enough to search in superClass hierarchy only (faster)
+ */
+ private boolean hasSuperInterface = false;
+
+ private boolean failOnClassNotFound = false;
+
+ public SubInheritanceHierarchyResolver(CtPackage input) {
+ inputPackage = input;
+ }
+
+ /**
+ * Add another super type to this mapping function.
+ * Using this function you can search parallel in more sub type hierarchies.
+ *
+ * @param superType - the type whose sub types will be returned by this mapping function too.
+ */
+ public SubInheritanceHierarchyResolver addSuperType(CtTypeInformation superType) {
+ targetSuperTypes.add(superType.getQualifiedName());
+ if (hasSuperInterface == false) {
+ hasSuperInterface = superType.isInterface();
+ }
+ return this;
+ }
+
+ /**
+ * @param includingInterfaces if false then interfaces are not visited - only super classes. By default it is true.
+ */
+ public SubInheritanceHierarchyResolver includingInterfaces(boolean includingInterfaces) {
+ this.includingInterfaces = includingInterfaces;
+ return this;
+ }
+
+ /**
+ * @param failOnClassNotFound sets whether processing should throw an exception if class is missing in noClassPath mode
+ */
+ public SubInheritanceHierarchyResolver failOnClassNotFound(boolean failOnClassNotFound) {
+ this.failOnClassNotFound = failOnClassNotFound;
+ return this;
+ }
+
+ /**
+ * 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 void forEachSubTypeInPackage(final CtConsumer outputConsumer) {
+ /*
+ * Set of qualified names of all visited types, independent on whether they are sub types or not.
+ */
+ final Set 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> 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()
+ //if there is any interface between `targetSuperTypes`, then we have to check superInterfaces too
+ .includingInterfaces(hasSuperInterface)
+ .failOnClassNotFound(failOnClassNotFound)
+ /*
+ * listen for types in super inheritance hierarchy
+ * 1) to collect `currentSubTypes`
+ * 2) to check if we have already found a targetSuperType
+ * 3) if found then send `currentSubTypes` to `outputConsumer` and skip visiting of further super types
+ */
+ .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());
+ }
+ }
+ //we do not have to go deeper into super inheritance hierarchy. Skip visiting of further super types
+ //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>() {
+ @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
+ }
+ });
+ }
+
+ /**
+ * accept all {@link CtType} excluding {@link CtTypeParameter}
+ */
+ private static final Filter> typeFilter = new Filter>() {
+ @Override
+ public boolean matches(CtType> type) {
+ if (type instanceof CtTypeParameter) {
+ return false;
+ }
+ return true;
+ }
+ };
+
+ /**
+ * Accept all {@link CtClass}, {@link CtEnum}
+ */
+ private static final Filter> classFilter = new TypeFilter>(CtClass.class);
+}
diff --git a/src/test/java/spoon/test/filters/FilterTest.java b/src/test/java/spoon/test/filters/FilterTest.java
index 544e984d12a..aa20ccd26ed 100644
--- a/src/test/java/spoon/test/filters/FilterTest.java
+++ b/src/test/java/spoon/test/filters/FilterTest.java
@@ -39,6 +39,7 @@
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
+import spoon.reflect.declaration.CtTypeInformation;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
@@ -72,6 +73,7 @@
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.comparator.DeepRepresentationComparator;
import spoon.support.reflect.declaration.CtMethodImpl;
+import spoon.support.visitor.SubInheritanceHierarchyResolver;
import spoon.test.filters.testclasses.AbstractTostada;
import spoon.test.filters.testclasses.Antojito;
import spoon.test.filters.testclasses.FieldAccessFilterTacos;
@@ -1045,4 +1047,61 @@ public void exit(CtElement element) {
//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);
}
+
+ @Test
+ public void testSubInheritanceHierarchyResolver() throws Exception {
+ // contract; SubInheritanceHierarchyResolver supports finding subtypes in an incremental manner
+ final Launcher launcher = new Launcher();
+ launcher.setArgs(new String[] {"--output-type", "nooutput","--level","info" });
+ launcher.addInputResource("./src/test/java/spoon/test/filters/testclasses");
+ launcher.buildModel();
+
+ SubInheritanceHierarchyResolver resolver = new SubInheritanceHierarchyResolver(launcher.getModel().getRootPackage());
+
+ // contract: by default, nothing is sent to the consumer
+ resolver.forEachSubTypeInPackage(new CtConsumer() {
+ @Override
+ public void accept(CtTypeInformation ctTypeInformation) {
+ fail();
+ }
+ });
+
+ // we add a type
+ resolver.addSuperType(launcher.getFactory().Type().createReference(AbstractTostada.class));
+ class Counter { int counter =0;}
+ Counter c = new Counter();
+ resolver.forEachSubTypeInPackage(new CtConsumer() {
+ @Override
+ public void accept(CtTypeInformation ctTypeInformation) {
+ c.counter++;
+ }
+ });
+
+ // there are 5 subtypes of AbstractTostada
+ assertEquals(5, c.counter);
+
+ // we add a type already visited
+ resolver.addSuperType(launcher.getFactory().Type().createReference(Tostada.class));
+ // nothing is sent to the consumer
+ resolver.forEachSubTypeInPackage(new CtConsumer() {
+ @Override
+ public void accept(CtTypeInformation ctTypeInformation) {
+ fail();
+ }
+ });
+
+ // we add a new type
+ resolver.addSuperType(launcher.getFactory().Type().createReference(ITostada.class));
+ Counter c2 = new Counter();
+ resolver.forEachSubTypeInPackage(new CtConsumer() {
+ @Override
+ public void accept(CtTypeInformation ctTypeInformation) {
+ c2.counter++;
+ assertEquals("spoon.test.filters.testclasses.Tacos", ctTypeInformation.getQualifiedName());
+ }
+ });
+
+ // only one subtype remains unvisited
+ assertEquals(1, c2.counter);
+ }
}