001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel.tree;
018:
019: import java.util.Collections;
020: import java.util.Iterator;
021:
022: import org.apache.commons.collections.ArrayStack;
023:
024: /**
025: * A helper to crawl a tree and quickly access important node-related information.
026: * <p>
027: * It's an <code>Iterator</code> on the "current level" of the tree. This level starts
028: * at the root node (and therefore obviously contains only one element), and can then
029: * be changed to children of the current node using {@link #enterChildren()} or popped
030: * back to the parent level using {@link #leave()}.
031: * <p>
032: * The {@link #next()} method will return the next node in the iteration,
033: * and set the current node used by many convenience methods giving information about
034: * that node.
035: * <p>
036: * This class was primarily written for page templates containing {@link Tree}s (see
037: * <code>org/apache/cocoon/forms/generation/jx-macros.xml</code>) but can of course be
038: * used in other places as well.
039: *
040: * @version $Id: TreeWalker.java 449149 2006-09-23 03:58:05Z crossley $
041: */
042: public class TreeWalker implements Iterator {
043: ArrayStack stack = new ArrayStack();
044: Tree tree;
045: Object node;
046: TreePath path;
047: Iterator iter;
048:
049: public TreeWalker(Tree tree) {
050: // Root node has no siblings
051: this .iter = Collections.EMPTY_LIST.iterator();
052: this .tree = tree;
053: this .node = tree.getModel().getRoot();
054: this .path = TreePath.ROOT_PATH;
055:
056: stack.push(this .iter);
057: stack.push(this .node);
058: }
059:
060: /**
061: * Starts iterating the children of the current node. The current iterator is pushed
062: * on a stack and will be restored on {@link #leave()}.
063: * <p>
064: * Right after calling this method, there is no current node. Calling {@link #next()}
065: * will move to the first child, if any.
066: *
067: * @return the current tree walker (i.e. <code>this</code>).
068: */
069: public TreeWalker enterChildren() {
070: Iterator newIter;
071: if (isLeaf()) {
072: newIter = Collections.EMPTY_LIST.iterator();
073: } else {
074: newIter = tree.getModel().getChildren(node).iterator();
075: }
076: this .stack.push(this .iter);
077: this .stack.push(this .path);
078: this .stack.push(this .node);
079: this .iter = newIter;
080: this .node = null;
081: this .path = null;
082:
083: return this ;
084: }
085:
086: /**
087: * Go back to the parent node, restoring the iterator at this node.
088: */
089: public void leave() {
090: this .node = this .stack.pop();
091: this .path = (TreePath) this .stack.pop();
092: this .iter = (Iterator) this .stack.pop();
093: this .path = this .path.getParentPath();
094: }
095:
096: /**
097: * Are there more nodes to iterate on at this level?
098: */
099: public boolean hasNext() {
100: return this .iter.hasNext();
101: }
102:
103: /**
104: * Get the next node in the current iteration level.
105: */
106: public Object next() {
107: this .node = iter.next();
108:
109: this .path = new TreePath((TreePath) this .stack.peek(1), tree
110: .getModel().getChildKey(stack.peek(), this .node));
111: return this .node;
112: }
113:
114: /**
115: * Required by the <code>Iterator</code> interface, but not supported here.
116: *
117: * @throws UnsupportedOperationException whenever called.
118: */
119: public void remove() {
120: throw new UnsupportedOperationException();
121: }
122:
123: /**
124: * Get the current depth of this walker (can be used e.g. to compute indentation margins
125: * or CSS styles). If root node is visible (see {@link Tree#isRootVisible()}), depth 0 is
126: * for the root. Otherwise, children of the root node are at depth 0.
127: *
128: * @return the current depth
129: */
130: public int getDepth() {
131: return path.getPathCount()
132: - (this .tree.isRootVisible() ? 1 : 2);
133: }
134:
135: /**
136: * Get the current node, which is the result of the last call to {@link #next()} (except if
137: * {@link #enterChildren()} or {@link #leave()} where called inbetween.
138: *
139: * @return the current node.
140: */
141: public Object getNode() {
142: return this .node;
143: }
144:
145: /**
146: * Get the path of the current node.
147: *
148: * @return the path
149: */
150: public TreePath getPath() {
151: return this .path;
152: }
153:
154: /**
155: * Is the current node a leaf?
156: */
157: public boolean isLeaf() {
158: return this .tree.getModel().isLeaf(this .node);
159: }
160:
161: /**
162: * Is the current node expanded?
163: */
164: public boolean isExpanded() {
165: return this .tree.isExpanded(this .path);
166: }
167:
168: /**
169: * Is the current node collapsed?
170: */
171: public boolean isCollapsed() {
172: return this .tree.isCollapsed(this .path);
173: }
174:
175: /**
176: * Is the current node visible (i.e. its parent is expanded)?
177: */
178: public boolean isVisible() {
179: return this .tree.isVisible(this .path);
180: }
181:
182: /**
183: * Is the current node selected?
184: */
185: public boolean isSelected() {
186: return this .tree.isPathSelected(this .path);
187: }
188:
189: /**
190: * Get the "icon type" that should be used for this node, according to the common
191: * visual paradigms used to render trees:
192: * <ul>
193: * <li>"<code>leaf</code>" for leaf nodes (will be e.g. a file icon),</li>
194: * <li>"<code>expanded</code>" for non-leaf expanded nodes (will be e.g. a "minus" icon)</li>
195: * <li>"<code>collapsed</code>" for non-leaf collapsed nodes (will be e.g. a "plus" icon)</li>
196: * </ul>
197: *
198: * @return the icon type
199: */
200: public String getIconType() {
201: if (isLeaf()) {
202: return "leaf";
203: } else if (isExpanded()) {
204: return "expanded";
205: } else {
206: return "collapsed";
207: }
208: }
209:
210: /**
211: * Get the "selection type" that should be used for this node, that can be used e.g. as
212: * a CSS class name:
213: * <ul>
214: * <li>"<code>selected</code>" for selected nodes,</li>
215: * <li>"<code>unselected</code>" for unselected nodes.</li>
216: * </ul>
217: *
218: * @return the selection type
219: */
220: public String getSelectionType() {
221: return this .tree.isPathSelected(this .path) ? "selected"
222: : "unselected";
223: }
224: }
|