From 80f53fbdbbf50338681b1c88842048428eff9a8e Mon Sep 17 00:00:00 2001 From: Nicolas Harrand Date: Sat, 9 Mar 2019 07:55:08 +0100 Subject: [PATCH] feat(CtBFSIterator): add an iterator that explores a CtElement's children in breadth first order (#2904) --- .../spoon/reflect/visitor/CtBFSIterator.java | 73 +++++++++++++++++++ .../reflect/visitor/CtBFSIteratorTest.java | 70 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/main/java/spoon/reflect/visitor/CtBFSIterator.java create mode 100644 src/test/java/spoon/reflect/visitor/CtBFSIteratorTest.java diff --git a/src/main/java/spoon/reflect/visitor/CtBFSIterator.java b/src/main/java/spoon/reflect/visitor/CtBFSIterator.java new file mode 100644 index 00000000000..98846e45ec1 --- /dev/null +++ b/src/main/java/spoon/reflect/visitor/CtBFSIterator.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2006-2018 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.reflect.visitor; + +import spoon.reflect.declaration.CtElement; + +import java.util.ArrayDeque; +import java.util.Iterator; +/** + * A class to be able to iterate over the children elements in the tree of a given node, in breadth-first order. + */ +public class CtBFSIterator extends CtScanner implements Iterator { + + + /** + * A deque containing the elements the iterator has seen but not expanded + */ + private ArrayDeque deque = new ArrayDeque<>(); + + /** + * CtIterator constructor, prepares the iterator from the @root node + * + * @param root the initial node to expand + */ + public CtBFSIterator(CtElement root) { + if (root != null) { + deque.add(root); + } + } + + /** + * prevent scanner from going down the tree, instead save with other CtElement children of the current node + * + * @param element the next direct child of the current node being expanded + */ + @Override + public void scan(CtElement element) { + if (element != null) { + deque.add(element); + } + } + + @Override + public boolean hasNext() { + return !deque.isEmpty(); + } + + /** + * Dereference the "iterator" + * + * @return CtElement the next element in BFS order without going down the tree + */ + @Override + public CtElement next() { + CtElement next = deque.poll(); // get the element to expand from the deque + next.accept(this); // call @scan for each direct child of the node + return next; + } +} diff --git a/src/test/java/spoon/reflect/visitor/CtBFSIteratorTest.java b/src/test/java/spoon/reflect/visitor/CtBFSIteratorTest.java new file mode 100644 index 00000000000..82d6719778d --- /dev/null +++ b/src/test/java/spoon/reflect/visitor/CtBFSIteratorTest.java @@ -0,0 +1,70 @@ +package spoon.reflect.visitor; + +import org.junit.Test; +import spoon.Launcher; +import spoon.reflect.declaration.CtElement; + +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +import static org.junit.Assert.*; + +public class CtBFSIteratorTest { + + @Test + public void testCtElementIteration() { + // contract: CtIterator must go over all nodes in bfs order + final Launcher launcher = new Launcher(); + launcher.setArgs(new String[]{"--output-type", "nooutput"}); + launcher.getEnvironment().setNoClasspath(true); + // resources to iterate + launcher.addInputResource("./src/main/java/spoon/reflect/visitor/CtScanner.java"); + launcher.buildModel(); + + // get the first Type + CtElement root = launcher.getFactory().getModel().getAllTypes().iterator().next(); + + testCtIterator(root); + } + + public void testCtIterator(CtElement root) { + CtBFSIteratorTest.BFS it = new CtBFSIteratorTest.BFS(root); + + CtBFSIterator iterator = new CtBFSIterator(root); + while (iterator.hasNext()) { + assertTrue(it.hasNext()); + assertEquals(it.next(), iterator.next()); + } + } + + /** + * Class that implement an alternative BFS iterator, + * for the {@link CtBFSIterator} test + */ + class BFS implements Iterator { + Queue queue = new ArrayDeque<>(); + + public BFS(CtElement root) { + queue.add(root); + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public CtElement next() { + CtElement cur = queue.poll(); + + //Get all direct children of cur + List toAdd = cur.getElements(el -> (el.getParent() == cur)); + + //Queue them + queue.addAll(toAdd); + return cur; + } + } +}