/*
* 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.StringTokenizer;
/**
* A path in a {@link TreeModel}.
*
* @version $Id$
*/
public class TreePath {
public static final TreePath ROOT_PATH = new TreePath();
/**
* Path representing the parent, null if lastPathComponent represents the
* root.
*/
private TreePath parentPath;
/** Last path component. */
private String key;
/** Cached result of toString() */
private String cachedToString;
/**
* Builds a path representing the root node or a tree model.
* Private to only be used by the ROOT_PATH constant.
*/
private TreePath() {
this.key = "";
this.cachedToString = "/";
}
/**
* Constructs a TreePath containing only a single element. This is usually
* used to construct a TreePath for the the root of the TreeModel.
*/
public TreePath(String key) {
if (key == null || key.length() == 0) {
throw new IllegalArgumentException("key must be non empty.");
}
if (key.indexOf('/') != -1) {
throw new IllegalArgumentException("key cannot contain a '/'");
}
this.key = key;
parentPath = ROOT_PATH;
}
/**
* Constructs a new TreePath, which is the path identified by
* <code>parent</code> ending in <code>lastElement</code>.
*/
public TreePath(TreePath parent, String key) {
this(key);
if (parent == null) {
throw new IllegalArgumentException("Parent path must be non null.");
}
this.parentPath = parent;
}
/**
* Returns an ordered array of Objects containing the components of this
* TreePath. The first element (index 0) is the root.
*
* @return an array of Objects representing the TreePath
* @see #TreePath()
*/
public Object[] getObjectPath(TreeModel model) {
int i = getPathCount();
Object[] result = new Object[i--];
for (TreePath path = this; path != null; path = path.parentPath) {
result[i--] = path.getLastPathObject(model);
}
return result;
}
/**
* Returns the last component of this path. For a path returned by
* DefaultTreeModel this will return an instance of TreeNode.
*
* @return the Object at the end of the path
* @see #TreePath()
*/
public Object getLastPathObject(TreeModel model) {
Object parent;
if (this.parentPath == ROOT_PATH) {
parent = model.getRoot();
} else {
parent = this.parentPath.getLastPathObject(model);
}
return model.getChild(parent, this.key);
}
/**
* Returns the number of elements in the path.
*
* @return an int giving a count of items the path
*/
public int getPathCount() {
int result = 0;
for (TreePath path = this; path != null; path = path.parentPath) {
result++;
}
return result;
}
// /**
// * Returns the path component at the specified index.
// *
// * @param element
// * an int specifying an element in the path, where 0 is the first
// * element in the path
// * @return the Object at that index location
// * @throws IllegalArgumentException
// * if the index is beyond the length of the path
// * @see #TreePath(Object[])
// */
// public Object getPathComponent(int element) {
// int pathLength = getPathCount();
//
// if (element < 0 || element >= pathLength)
// throw new IllegalArgumentException("Index " + element + " is out of the specified range");
//
// TreePath path = this;
//
// for (int i = pathLength - 1; i != element; i--) {
// path = path.parentPath;
// }
// return path.lastPathComponent;
// }
//
/**
* Tests if two paths are equal. Two paths are considered equal if they are
* of same length and contain the same keys.
*
* @param obj the object ot compare
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof TreePath)) {
return false;
}
TreePath otherPath = (TreePath)obj;
if (getPathCount() != otherPath.getPathCount()) {
return false;
}
TreePath path = this;
do {
if (otherPath == null || !path.key.equals(otherPath.key)) {
return false;
}
path = path.parentPath;
otherPath = otherPath.parentPath;
} while(path != null);
return true;
}
public int hashCode() {
// Should be enough. We may also xor with parent's hashcode.
return key.hashCode();
}
/**
* Returns true if <code>aTreePath</code> is a descendant of this
* TreePath. A TreePath P1 is a descendent of a TreePath P2 if P1 contains
* all of the components that make up P2's path. For example, if this object
* has the path [a, b], and <code>aTreePath</code> has the path [a, b, c],
* then <code>aTreePath</code> is a descendant of this object. However, if
* <code>aTreePath</code> has the path [a], then it is not a descendant of
* this object.
*
* @return true if <code>aTreePath</code> is a descendant of this path
*/
public boolean isDescendant(TreePath aTreePath) {
if (aTreePath == this)
return true;
if (aTreePath != null) {
int pathLength = getPathCount();
int oPathLength = aTreePath.getPathCount();
if (oPathLength < pathLength)
// Can't be a descendant, has fewer components in the path.
return false;
while (oPathLength-- > pathLength)
aTreePath = aTreePath.getParentPath();
return equals(aTreePath);
}
return false;
}
// /**
// * Returns a new path containing all the elements of this object plus
// * <code>child</code>. <code>child</code> will be the last element of
// * the newly created TreePath. This will throw a NullPointerException if
// * child is null.
// */
// public TreePath pathByAddingChild(Object child) {
// if (child == null)
// throw new NullPointerException("Null child not allowed");
//
// return new TreePath(this, child);
// }
/**
* Returns a path containing all the elements of this object, except the
* last path component.
*/
public TreePath getParentPath() {
return parentPath;
}
/**
* Returns the key of last element of this path.
*/
public String getLastKey() {
return this.key;
}
/**
* Returns a string that displays and identifies this object's properties.
*
* @return a String representation of this object
*/
public String toString() {
if (this.cachedToString == null) {
StringBuffer buf = new StringBuffer();
appendTo(buf);
this.cachedToString = buf.toString();
}
return this.cachedToString;
}
/** Recursively build the text representation of a tree path */
private void appendTo(StringBuffer buf) {
if (this.parentPath != ROOT_PATH) {
this.parentPath.appendTo(buf);
}
buf.append('/');
buf.append(this.key);
}
/**
* Returns the <code>TreePath</code> represented by a given String.
* @param s the string representation of the path
* @return a path object
*
* @see #toString()
*/
public static TreePath valueOf(String s) {
// FIXME: see if some caching could be useful here.
if (s == null || s.length() == 0) {
throw new IllegalArgumentException("Invalid empty string");
}
StringTokenizer stok = new StringTokenizer(s, "/");
TreePath current = ROOT_PATH;
while (stok.hasMoreTokens()) {
String tok = stok.nextToken();
current = current == null ? new TreePath(tok) : new TreePath(current, tok);
}
return current;
}
public Object getObject(TreeModel model) {
return this.parentPath == null ?
model.getRoot() :
model.getChild(this.parentPath.getObject(model), this.key);
}
}