blob: bb58eb30aaa4585770d3bccecdcdcc5776d7929f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.cocoon.forms.formmodel.tree;
import java.util.Collections;
import java.util.Iterator;
import org.apache.commons.collections.ArrayStack;
/**
* A helper to crawl a tree and quickly access important node-related information.
* <p>
* It's an <code>Iterator</code> on the "current level" of the tree. This level starts
* at the root node (and therefore obviously contains only one element), and can then
* be changed to children of the current node using {@link #enterChildren()} or popped
* back to the parent level using {@link #leave()}.
* <p>
* The {@link #next()} method will return the next node in the iteration,
* and set the current node used by many convenience methods giving information about
* that node.
* <p>
* This class was primarily written for page templates containing {@link Tree}s (see
* <code>org/apache/cocoon/forms/generation/jx-macros.xml</code>) but can of course be
* used in other places as well.
*
* @version $Id$
*/
public class TreeWalker implements Iterator {
ArrayStack stack = new ArrayStack();
Tree tree;
Object node;
TreePath path;
Iterator iter;
public TreeWalker(Tree tree) {
// Root node has no siblings
this.iter = Collections.EMPTY_LIST.iterator();
this.tree = tree;
this.node = tree.getModel().getRoot();
this.path = TreePath.ROOT_PATH;
stack.push(this.iter);
stack.push(this.node);
}
/**
* Starts iterating the children of the current node. The current iterator is pushed
* on a stack and will be restored on {@link #leave()}.
* <p>
* Right after calling this method, there is no current node. Calling {@link #next()}
* will move to the first child, if any.
*
* @return the current tree walker (i.e. <code>this</code>).
*/
public TreeWalker enterChildren() {
Iterator newIter;
if (isLeaf()) {
newIter = Collections.EMPTY_LIST.iterator();
} else {
newIter = tree.getModel().getChildren(node).iterator();
}
this.stack.push(this.iter);
this.stack.push(this.path);
this.stack.push(this.node);
this.iter = newIter;
this.node = null;
this.path = null;
return this;
}
/**
* Go back to the parent node, restoring the iterator at this node.
*/
public void leave() {
this.node = this.stack.pop();
this.path = (TreePath)this.stack.pop();
this.iter = (Iterator)this.stack.pop();
this.path = this.path.getParentPath();
}
/**
* Are there more nodes to iterate on at this level?
*/
public boolean hasNext() {
return this.iter.hasNext();
}
/**
* Get the next node in the current iteration level.
*/
public Object next() {
this.node = iter.next();
this.path = new TreePath(
(TreePath)this.stack.peek(1),
tree.getModel().getChildKey(stack.peek(), this.node));
return this.node;
}
/**
* Required by the <code>Iterator</code> interface, but not supported here.
*
* @throws UnsupportedOperationException whenever called.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Get the current depth of this walker (can be used e.g. to compute indentation margins
* or CSS styles). If root node is visible (see {@link Tree#isRootVisible()}), depth 0 is
* for the root. Otherwise, children of the root node are at depth 0.
*
* @return the current depth
*/
public int getDepth() {
return path.getPathCount() - (this.tree.isRootVisible() ? 1 : 2);
}
/**
* Get the current node, which is the result of the last call to {@link #next()} (except if
* {@link #enterChildren()} or {@link #leave()} where called inbetween.
*
* @return the current node.
*/
public Object getNode() {
return this.node;
}
/**
* Get the path of the current node.
*
* @return the path
*/
public TreePath getPath() {
return this.path;
}
/**
* Is the current node a leaf?
*/
public boolean isLeaf() {
return this.tree.getModel().isLeaf(this.node);
}
/**
* Is the current node expanded?
*/
public boolean isExpanded() {
return this.tree.isExpanded(this.path);
}
/**
* Is the current node collapsed?
*/
public boolean isCollapsed() {
return this.tree.isCollapsed(this.path);
}
/**
* Is the current node visible (i.e. its parent is expanded)?
*/
public boolean isVisible() {
return this.tree.isVisible(this.path);
}
/**
* Is the current node selected?
*/
public boolean isSelected() {
return this.tree.isPathSelected(this.path);
}
/**
* Get the "icon type" that should be used for this node, according to the common
* visual paradigms used to render trees:
* <ul>
* <li>"<code>leaf</code>" for leaf nodes (will be e.g. a file icon),</li>
* <li>"<code>expanded</code>" for non-leaf expanded nodes (will be e.g. a "minus" icon)</li>
* <li>"<code>collapsed</code>" for non-leaf collapsed nodes (will be e.g. a "plus" icon)</li>
* </ul>
*
* @return the icon type
*/
public String getIconType() {
if (isLeaf()) {
return "leaf";
} else if (isExpanded()) {
return "expanded";
} else {
return "collapsed";
}
}
/**
* Get the "selection type" that should be used for this node, that can be used e.g. as
* a CSS class name:
* <ul>
* <li>"<code>selected</code>" for selected nodes,</li>
* <li>"<code>unselected</code>" for unselected nodes.</li>
* </ul>
*
* @return the selection type
*/
public String getSelectionType() {
return this.tree.isPathSelected(this.path) ? "selected" : "unselected";
}
}