package trees; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Stack; import parser.Match; import util.ArrayIterator; /** * Returns the matches of the tree by doing a depth-first prefix walk of the * tree, from left to right or from right to left. */ public class MatchIterator implements Iterable<Match>, Iterator<Match> { /** * The position in the match tree is kept as a "trace": a stack of NodeState. * The last item returned by next() is always at the top of the stack (we use * some "if" magic for the first invocation of next()). */ /****************************************************************************/ private class NodeState { final Match match; final boolean upperHasNext; final ArrayIterator<Match> iter; NodeState(Match match, NodeState parent) { this.match = match; this.upperHasNext = parent == null ? false : parent.treeHasNext(); Match[] childs = match.children().toArray(Match.EMPTY); this.iter = new ArrayIterator<>(childs, !leftToRight); } boolean treeHasNext() { return upperHasNext || iter.hasNext(); } } ////////////////////////////////////////////////////////////////////////////// /****************************************************************************/ private final Match tree; /****************************************************************************/ private Stack<NodeState> stack = new Stack<>(); /***************************************************************************** * Indicates whether the elements of the match tree are returned in a * left-to-right (true) or right-to-left order (false). */ private final boolean leftToRight; /****************************************************************************/ public MatchIterator(Match tree) { this(tree, true); } /****************************************************************************/ public MatchIterator(Match tree, boolean leftToRight) { this.tree = tree; this.leftToRight = leftToRight; } /****************************************************************************/ @Override public boolean hasNext() { return stack.isEmpty() || stack.peek().treeHasNext(); } /****************************************************************************/ @Override public Match next() { forward(); return stack.peek().match; } /***************************************************************************** * Puts the next item at the top of the stack. */ private void forward() { if (stack.isEmpty()) { stack.push(new NodeState(tree, null)); return; } if (!hasNext()) { throw new NoSuchElementException(); } NodeState top = stack.peek(); while (!top.iter.hasNext()) { stack.pop(); top = stack.peek(); } stack.push(new NodeState(top.iter.next(), top)); } /***************************************************************************** * Ensures that the descendant of the last item returned by next() (if any) * will be skipped in the iteration. Behaves as if next() was called for all * the descendants. */ public void skipChilds() { if (stack.isEmpty() || !hasNext()) { return; } NodeState top = stack.peek(); while (top.iter.hasNext()) { top.iter.next(); } } /***************************************************************************** * Returns a list of matches that forms the path between the root of the match * tree and the last item returned by next(). */ public List<Match> trace() { List<Match> list = new ArrayList<>(); for (NodeState ns : stack) { list.add(ns.match); } return list; } /****************************************************************************/ @Override public void remove() { throw new UnsupportedOperationException(); } /****************************************************************************/ @Override public Iterator<Match> iterator() { return this; } }