/**
* Copyright 2010 Google Inc.
*
* 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.waveprotocol.wave.model.document.util;
import org.waveprotocol.wave.model.document.ReadableDocument;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.ValueUtils;
import java.util.Iterator;
import java.util.Map;
/**
* Depth-first (pre-order) iteration walker of a subtree within a readable document.
* Checks information about each node in turn, then moves to the next node automatically.
*
* @author patcoleman@google.com (Pat Coleman)
*/
public class ReadableTreeWalker<N, E extends N, T extends N> {
/** Document the subtree resides in. */
protected final ReadableDocument<N, E, T> document;
/** Iterator used to walk over the nodes. */
protected final Iterator<N> nodeWalker;
/** Current node we are at in the subtree. */
protected N currentNode;
/**
* Creates the tree walker by forming an iterator along all nodes in a subtree.
*/
public ReadableTreeWalker(ReadableDocument<N, E, T> document, N rootNode) {
this(document, DocIterate.iterate(document, rootNode,
DocHelper.getNextOrPrevNodeDepthFirst(document, rootNode, null, false, true),
DocIterate.<N, E, T>forwardDepthFirstIterator()));
}
/**
* Creates the tree walker, taking the document it walks plus an iterator for the path.
*/
public ReadableTreeWalker(ReadableDocument<N, E, T> document, Iterable<N> nodeIterable) {
this.document = document;
this.nodeWalker = nodeIterable.iterator();
}
/**
* Checks whether the current walk is the correct type of element.
* @throws IllegalStateException if the element does not match the expected state.
* @return the checked element.
*/
public E checkElement(String tagName, Map<String, String> attributes) {
Preconditions.checkState(nodeWalker.hasNext(),
"Tree Walker: no more nodes to walk, element expected");
progress();
E element = document.asElement(currentNode);
Preconditions.checkState(element != null,
"Tree Walker: At text node, element expected");
Preconditions.checkState(document.getTagName(element).equals(tagName),
"Tree Walker: Incorrect tag name");
Preconditions.checkState(ValueUtils.equal(document.getAttributes(element), attributes),
"Tree Walker: Incorrect attributes");
return element;
}
/**
* Checks whether the current walk is a text node with the right data.
* @throws IllegalStateException if the text node does not match the expected state.
* @return the checked text node
*/
public T checkTextNode(String data) {
Preconditions.checkState(nodeWalker.hasNext(),
"Tree Walker: no more nodes to walk, text node expected");
progress();
T textNode = document.asText(currentNode);
Preconditions.checkState(textNode != null,
"Tree Walker: At element, text node expected");
Preconditions.checkState(document.getData(textNode).equals(data),
"Tree Walker: Incorrect text node data");
return textNode;
}
/**
* Makes sure that the entire tree has been walked.
* @throws IllegalStateException if we are not at the end of the path.
* @return whether we have walked to the next node outside the tree.
*/
public boolean checkComplete() {
Preconditions.checkState(!nodeWalker.hasNext(),
"Tree Walker: Did not end walk at the correct node");
return !nodeWalker.hasNext();
}
/** Performs a walk against a document subtree, returns true if all checks pass. */
public boolean checkWalk(ReadableDocument<N, E, T> doc, N root) {
// curry in the forwards depth first iterator.
N stopAt = DocHelper.getNextOrPrevNodeDepthFirst(doc, root, null, false, true);
return checkWalk(doc, DocIterate.iterate(doc, root, stopAt,
DocIterate.<N, E, T>forwardDepthFirstIterator()));
}
/** Performs a walk against an iterable collection of nodes, returns true if all checks pass. */
public boolean checkWalk(ReadableDocument<N, E, T> doc, Iterable<N> nodes) {
try {
for (N node : nodes) {
E elt = doc.asElement(node);
if (elt != null) {
checkElement(doc.getTagName(elt), doc.getAttributes(elt));
} else {
checkTextNode(doc.getData(doc.asText(node)));
}
}
return checkComplete();
} catch (IllegalStateException e) {
return false; // assertion failed.
}
}
/** Utility to move the node one along. */
protected void progress() {
currentNode = nodeWalker.next();
}
}