/*
* 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();
}
}