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.StringTokenizer;
020:
021: /**
022: * A path in a {@link TreeModel}.
023: *
024: * @version $Id: TreePath.java 449149 2006-09-23 03:58:05Z crossley $
025: */
026: public class TreePath {
027:
028: public static final TreePath ROOT_PATH = new TreePath();
029: /**
030: * Path representing the parent, null if lastPathComponent represents the
031: * root.
032: */
033: private TreePath parentPath;
034:
035: /** Last path component. */
036: private String key;
037:
038: /** Cached result of toString() */
039: private String cachedToString;
040:
041: /**
042: * Builds a path representing the root node or a tree model.
043: * Private to only be used by the ROOT_PATH constant.
044: */
045: private TreePath() {
046: this .key = "";
047: this .cachedToString = "/";
048: }
049:
050: /**
051: * Constructs a TreePath containing only a single element. This is usually
052: * used to construct a TreePath for the the root of the TreeModel.
053: */
054: public TreePath(String key) {
055: if (key == null || key.length() == 0) {
056: throw new IllegalArgumentException("key must be non empty.");
057: }
058:
059: if (key.indexOf('/') != -1) {
060: throw new IllegalArgumentException(
061: "key cannot contain a '/'");
062: }
063: this .key = key;
064: parentPath = ROOT_PATH;
065: }
066:
067: /**
068: * Constructs a new TreePath, which is the path identified by
069: * <code>parent</code> ending in <code>lastElement</code>.
070: */
071: public TreePath(TreePath parent, String key) {
072: this (key);
073: if (parent == null) {
074: throw new IllegalArgumentException(
075: "Parent path must be non null.");
076: }
077: this .parentPath = parent;
078: }
079:
080: /**
081: * Returns an ordered array of Objects containing the components of this
082: * TreePath. The first element (index 0) is the root.
083: *
084: * @return an array of Objects representing the TreePath
085: * @see #TreePath()
086: */
087: public Object[] getObjectPath(TreeModel model) {
088: int i = getPathCount();
089: Object[] result = new Object[i--];
090:
091: for (TreePath path = this ; path != null; path = path.parentPath) {
092: result[i--] = path.getLastPathObject(model);
093: }
094: return result;
095: }
096:
097: /**
098: * Returns the last component of this path. For a path returned by
099: * DefaultTreeModel this will return an instance of TreeNode.
100: *
101: * @return the Object at the end of the path
102: * @see #TreePath()
103: */
104: public Object getLastPathObject(TreeModel model) {
105: Object parent;
106: if (this .parentPath == ROOT_PATH) {
107: parent = model.getRoot();
108: } else {
109: parent = this .parentPath.getLastPathObject(model);
110: }
111: return model.getChild(parent, this .key);
112: }
113:
114: /**
115: * Returns the number of elements in the path.
116: *
117: * @return an int giving a count of items the path
118: */
119: public int getPathCount() {
120: int result = 0;
121: for (TreePath path = this ; path != null; path = path.parentPath) {
122: result++;
123: }
124: return result;
125: }
126:
127: // /**
128: // * Returns the path component at the specified index.
129: // *
130: // * @param element
131: // * an int specifying an element in the path, where 0 is the first
132: // * element in the path
133: // * @return the Object at that index location
134: // * @throws IllegalArgumentException
135: // * if the index is beyond the length of the path
136: // * @see #TreePath(Object[])
137: // */
138: // public Object getPathComponent(int element) {
139: // int pathLength = getPathCount();
140: //
141: // if (element < 0 || element >= pathLength)
142: // throw new IllegalArgumentException("Index " + element + " is out of the specified range");
143: //
144: // TreePath path = this;
145: //
146: // for (int i = pathLength - 1; i != element; i--) {
147: // path = path.parentPath;
148: // }
149: // return path.lastPathComponent;
150: // }
151: //
152: /**
153: * Tests if two paths are equal. Two paths are considered equal if they are
154: * of same length and contain the same keys.
155: *
156: * @param obj the object ot compare
157: */
158: public boolean equals(Object obj) {
159: if (obj == this ) {
160: return true;
161: }
162: if (!(obj instanceof TreePath)) {
163: return false;
164: }
165:
166: TreePath otherPath = (TreePath) obj;
167:
168: if (getPathCount() != otherPath.getPathCount()) {
169: return false;
170: }
171:
172: TreePath path = this ;
173: do {
174: if (otherPath == null || !path.key.equals(otherPath.key)) {
175: return false;
176: }
177: path = path.parentPath;
178: otherPath = otherPath.parentPath;
179: } while (path != null);
180:
181: return true;
182: }
183:
184: public int hashCode() {
185: // Should be enough. We may also xor with parent's hashcode.
186: return key.hashCode();
187: }
188:
189: /**
190: * Returns true if <code>aTreePath</code> is a descendant of this
191: * TreePath. A TreePath P1 is a descendent of a TreePath P2 if P1 contains
192: * all of the components that make up P2's path. For example, if this object
193: * has the path [a, b], and <code>aTreePath</code> has the path [a, b, c],
194: * then <code>aTreePath</code> is a descendant of this object. However, if
195: * <code>aTreePath</code> has the path [a], then it is not a descendant of
196: * this object.
197: *
198: * @return true if <code>aTreePath</code> is a descendant of this path
199: */
200: public boolean isDescendant(TreePath aTreePath) {
201: if (aTreePath == this )
202: return true;
203:
204: if (aTreePath != null) {
205: int pathLength = getPathCount();
206: int oPathLength = aTreePath.getPathCount();
207:
208: if (oPathLength < pathLength)
209: // Can't be a descendant, has fewer components in the path.
210: return false;
211: while (oPathLength-- > pathLength)
212: aTreePath = aTreePath.getParentPath();
213: return equals(aTreePath);
214: }
215: return false;
216: }
217:
218: // /**
219: // * Returns a new path containing all the elements of this object plus
220: // * <code>child</code>. <code>child</code> will be the last element of
221: // * the newly created TreePath. This will throw a NullPointerException if
222: // * child is null.
223: // */
224: // public TreePath pathByAddingChild(Object child) {
225: // if (child == null)
226: // throw new NullPointerException("Null child not allowed");
227: //
228: // return new TreePath(this, child);
229: // }
230:
231: /**
232: * Returns a path containing all the elements of this object, except the
233: * last path component.
234: */
235: public TreePath getParentPath() {
236: return parentPath;
237: }
238:
239: /**
240: * Returns the key of last element of this path.
241: */
242: public String getLastKey() {
243: return this .key;
244: }
245:
246: /**
247: * Returns a string that displays and identifies this object's properties.
248: *
249: * @return a String representation of this object
250: */
251: public String toString() {
252: if (this .cachedToString == null) {
253: StringBuffer buf = new StringBuffer();
254: appendTo(buf);
255: this .cachedToString = buf.toString();
256: }
257: return this .cachedToString;
258: }
259:
260: /** Recursively build the text representation of a tree path */
261: private void appendTo(StringBuffer buf) {
262: if (this .parentPath != ROOT_PATH) {
263: this .parentPath.appendTo(buf);
264: }
265: buf.append('/');
266: buf.append(this .key);
267: }
268:
269: /**
270: * Returns the <code>TreePath</code> represented by a given String.
271: * @param s the string representation of the path
272: * @return a path object
273: *
274: * @see #toString()
275: */
276: public static TreePath valueOf(String s) {
277: // FIXME: see if some caching could be useful here.
278: if (s == null || s.length() == 0) {
279: throw new IllegalArgumentException("Invalid empty string");
280: }
281: StringTokenizer stok = new StringTokenizer(s, "/");
282: TreePath current = ROOT_PATH;
283: while (stok.hasMoreTokens()) {
284: String tok = stok.nextToken();
285: current = current == null ? new TreePath(tok)
286: : new TreePath(current, tok);
287: }
288:
289: return current;
290: }
291:
292: public Object getObject(TreeModel model) {
293: return this.parentPath == null ? model.getRoot() : model
294: .getChild(this.parentPath.getObject(model), this.key);
295: }
296: }
|