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