/* * Copyright 2003-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.mps.util; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.util.TreeIterator; import java.util.ArrayDeque; import java.util.Deque; import java.util.NoSuchElementException; /** * Iterator for a {@link org.jetbrains.mps.openapi.model.SNode} sub-tree. * <p/> * Pre-order traversal, parent node is visited prior to any of its children, * no sibling is visited prior to all children of its predecessor. * <p/> * Iterator gives control over sub-tree iteration with {@link #skipChildren()} method to completely skip * processing of a sub-tree rooted at latest node returned from {@link #next()}. * <p/> * At the moment, iterator doesn't support deletions. * * @author Artem Tikhomirov */ // The reason this class lives in (openapi)mps.util is that openapi.model.SNodeUtil uses it; // besides, I don't want to make it openapi now, hence not mps.openapi.util. public final class DescendantsTreeIterator implements TreeIterator<SNode> { private final SNode myStart; /* * Stack of non-leaf nodes we've visited so far. Grows as much as depth of the tree. * We use stack despite presence of SNode#getParent() to minimize number of notifications sent out on node access. */ private final Deque<SNode> myVisitedNodes = new ArrayDeque<SNode>(20); /* * myNext == null when there are no more children to visit */ private SNode myNext; /* * true iff myNext is a sibling of last element returned from #next() */ private boolean myNextIsSibling; public DescendantsTreeIterator(@Nullable SNode start) { myStart = myNext = start; } @Override public boolean hasNext() { return myNext != null; } @Override public SNode next() { SNode result = myNext; if (result == null) { throw new NoSuchElementException(); } final SNode firstChild = myNext.getFirstChild(); if (firstChild == null) { nextSibling(); // leaf node, try sibling or go level up myNextIsSibling = true; } else { // record the parent of next element (last node with children we've visited) myVisitedNodes.push(result); myNext = firstChild; myNextIsSibling = false; } return result; } @Override public void skipChildren() { if (myNext == null) { // skipChildren for very last descendant we'd be iterating over, or the very first of empty iterator return; } if (myVisitedNodes.isEmpty()) { // empty stack with non-empty myNext is possible when no next() has been called throw new IllegalStateException(); } if (myNextIsSibling) { return; } // return to last non-leaf node, and try next from there myNext = myVisitedNodes.pop(); nextSibling(); myNextIsSibling = true; // not really necessary here as subsequent #next() would put correct value. } private void nextSibling() { while (myNext != myStart) { final SNode nextSibling = myNext.getNextSibling(); if (nextSibling != null) { myNext = nextSibling; return; } if (myVisitedNodes.isEmpty()) { myNext = null; return; } myNext = myVisitedNodes.pop(); } myNext = null; } @Override public void remove() { throw new UnsupportedOperationException(); } }