/*
* 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.jorphan.collections;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This class is used to create a tree structure of objects. Each element in the
* tree is also a key to the next node down in the tree. It provides many ways
* to add objects and branches, as well as many ways to retrieve.
* <p>
* HashTree implements the Map interface for convenience reasons. The main
* difference between a Map and a HashTree is that the HashTree organizes the
* data into a recursive tree structure, and provides the means to manipulate
* that structure.
* <p>
* Of special interest is the {@link #traverse(HashTreeTraverser)} method, which
* provides an expedient way to traverse any HashTree by implementing the
* {@link HashTreeTraverser} interface in order to perform some operation on the
* tree, or to extract information from the tree.
*
* @see HashTreeTraverser
* @see SearchByClass
*/
public class HashTree implements Serializable, Map<Object, HashTree>, Cloneable {
private static final long serialVersionUID = 240L;
// Used for the RuntimeException to short-circuit the traversal
private static final String FOUND = "found"; // $NON-NLS-1$
// N.B. The keys can be either JMeterTreeNode or TestElement
protected final Map<Object, HashTree> data;
/**
* Creates an empty new HashTree.
*/
public HashTree() {
this(null, null);
}
/**
* Allow subclasses to provide their own Map.
* @param _map {@link Map} to use
*/
protected HashTree(Map<Object, HashTree> _map) {
this(_map, null);
}
/**
* Creates a new HashTree and adds the given object as a top-level node.
*
* @param key
* name of the new top-level node
*/
public HashTree(Object key) {
this(new HashMap<Object, HashTree>(), key);
}
/**
* Uses the new HashTree if not null and adds the given object as a
* top-level node if not null
*
* @param _map
* the map to be used. If <code>null</code> a new {@link HashMap}
* will be created
* @param key
* the object to be used as the key for the root node (may be
* <code>null</code>, in which case no root node will be created)
*/
private HashTree(Map<Object, HashTree> _map, Object key) {
if(_map != null) {
data = _map;
} else {
data = new HashMap<>();
}
if(key != null) {
data.put(key, new HashTree());
}
}
/**
* The Map given must also be a HashTree, otherwise an
* UnsupportedOperationException is thrown. If it is a HashTree, this is
* like calling the add(HashTree) method.
*
* @see #add(HashTree)
* @see java.util.Map#putAll(Map)
*/
@Override
public void putAll(Map<?, ? extends HashTree> map) {
if (map instanceof HashTree) {
this.add((HashTree) map);
} else {
throw new UnsupportedOperationException("can only putAll other HashTree objects");
}
}
/**
* Exists to satisfy the Map interface.
*
* @see java.util.Map#entrySet()
*/
@Override
public Set<Entry<Object, HashTree>> entrySet() {
return data.entrySet();
}
/**
* Implemented as required by the Map interface, but is not very useful
* here. All 'values' in a HashTree are HashTree's themselves.
*
* @param value
* Object to be tested as a value.
* @return True if the HashTree contains the value, false otherwise.
* @see java.util.Map#containsValue(Object)
*/
@Override
public boolean containsValue(Object value) {
return data.containsValue(value);
}
/**
* This is the same as calling HashTree.add(key,value).
*
* @param key
* to use
* @param value
* to store against key
* @see java.util.Map#put(Object, Object)
*/
@Override
public HashTree put(Object key, HashTree value) {
HashTree previous = data.get(key);
add(key, value);
return previous;
}
/**
* Clears the HashTree of all contents.
*
* @see java.util.Map#clear()
*/
@Override
public void clear() {
data.clear();
}
/**
* Returns a collection of all the sub-trees of the current tree.
*
* @see java.util.Map#values()
*/
@Override
public Collection<HashTree> values() {
return data.values();
}
/**
* Adds a key as a node at the current level and then adds the given
* HashTree to that new node.
*
* @param key
* key to create in this tree
* @param subTree
* sub tree to add to the node created for the first argument.
*/
public void add(Object key, HashTree subTree) {
add(key).add(subTree);
}
/**
* Adds all the nodes and branches of the given tree to this tree. Is like
* merging two trees. Duplicates are ignored.
*
* @param newTree the tree to be added
*/
public void add(HashTree newTree) {
for (Object item : newTree.list()) {
add(item).add(newTree.getTree(item));
}
}
/**
* Creates a new HashTree and adds all the objects in the given collection
* as top-level nodes in the tree.
*
* @param keys
* a collection of objects to be added to the created HashTree.
*/
public HashTree(Collection<?> keys) {
data = new HashMap<>();
for (Object o : keys) {
data.put(o, new HashTree());
}
}
/**
* Creates a new HashTree and adds all the objects in the given array as
* top-level nodes in the tree.
*
* @param keys
* array with names for the new top-level nodes
*/
public HashTree(Object[] keys) {
data = new HashMap<>();
for (Object key : keys) {
data.put(key, new HashTree());
}
}
/**
* If the HashTree contains the given object as a key at the top level, then
* a true result is returned, otherwise false.
*
* @param o
* Object to be tested as a key.
* @return True if the HashTree contains the key, false otherwise.
* @see java.util.Map#containsKey(Object)
*/
@Override
public boolean containsKey(Object o) {
return data.containsKey(o);
}
/**
* If the HashTree is empty, true is returned, false otherwise.
*
* @return True if HashTree is empty, false otherwise.
*/
@Override
public boolean isEmpty() {
return data.isEmpty();
}
/**
* Sets a key and it's value in the HashTree. It actually sets up a key, and
* then creates a node for the key and sets the value to the new node, as a
* key. Any previous nodes that existed under the given key are lost.
*
* @param key
* key to be set up
* @param value
* value to be set up as a key in the secondary node
*/
public void set(Object key, Object value) {
data.put(key, createNewTree(value));
}
/**
* Sets a key into the current tree and assigns it a HashTree as its
* subtree. Any previous entries under the given key are removed.
*
* @param key
* key to be set up
* @param t
* HashTree that the key maps to
*/
public void set(Object key, HashTree t) {
data.put(key, t);
}
/**
* Sets a key and its values in the HashTree. It sets up a key in the
* current node, and then creates a node for that key, and sets all the
* values in the array as keys in the new node. Any keys previously held
* under the given key are lost.
*
* @param key
* Key to be set up
* @param values
* Array of objects to be added as keys in the secondary node
*/
public void set(Object key, Object[] values) {
data.put(key, createNewTree(Arrays.asList(values)));
}
/**
* Sets a key and its values in the HashTree. It sets up a key in the
* current node, and then creates a node for that key, and set all the
* values in the array as keys in the new node. Any keys previously held
* under the given key are removed.
*
* @param key
* key to be set up
* @param values
* Collection of objects to be added as keys in the secondary
* node
*/
public void set(Object key, Collection<?> values) {
data.put(key, createNewTree(values));
}
/**
* Sets a series of keys into the HashTree. It sets up the first object in
* the key array as a key in the current node, recurses into the next
* HashTree node through that key and adds the second object in the array.
* Continues recursing in this manner until the end of the first array is
* reached, at which point all the values of the second array are set as
* keys to the bottom-most node. All previous keys of that bottom-most node
* are removed.
*
* @param treePath
* array of keys to put into HashTree
* @param values
* array of values to be added as keys to bottom-most node
*/
public void set(Object[] treePath, Object[] values) {
if (treePath != null && values != null) {
set(Arrays.asList(treePath), Arrays.asList(values));
}
}
/**
* Sets a series of keys into the HashTree. It sets up the first object in
* the key array as a key in the current node, recurses into the next
* HashTree node through that key and adds the second object in the array.
* Continues recursing in this manner until the end of the first array is
* reached, at which point all the values of the Collection of values are
* set as keys to the bottom-most node. Any keys previously held by the
* bottom-most node are lost.
*
* @param treePath
* array of keys to put into HashTree
* @param values
* Collection of values to be added as keys to bottom-most node
*/
public void set(Object[] treePath, Collection<?> values) {
if (treePath != null) {
set(Arrays.asList(treePath), values);
}
}
/**
* Sets a series of keys into the HashTree. It sets up the first object in
* the key list as a key in the current node, recurses into the next
* HashTree node through that key and adds the second object in the list.
* Continues recursing in this manner until the end of the first list is
* reached, at which point all the values of the array of values are set as
* keys to the bottom-most node. Any previously existing keys of that bottom
* node are removed.
*
* @param treePath
* collection of keys to put into HashTree
* @param values
* array of values to be added as keys to bottom-most node
*/
public void set(Collection<?> treePath, Object[] values) {
HashTree tree = addTreePath(treePath);
tree.set(Arrays.asList(values));
}
/**
* Sets the nodes of the current tree to be the objects of the given
* collection. Any nodes previously in the tree are removed.
*
* @param values
* Collection of objects to set as nodes.
*/
public void set(Collection<?> values) {
clear();
this.add(values);
}
/**
* Sets a series of keys into the HashTree. It sets up the first object in
* the key list as a key in the current node, recurses into the next
* HashTree node through that key and adds the second object in the list.
* Continues recursing in this manner until the end of the first list is
* reached, at which point all the values of the Collection of values are
* set as keys to the bottom-most node. Any previously existing keys of that
* bottom node are lost.
*
* @param treePath
* list of keys to put into HashTree
* @param values
* collection of values to be added as keys to bottom-most node
*/
public void set(Collection<?> treePath, Collection<?> values) {
HashTree tree = addTreePath(treePath);
tree.set(values);
}
/**
* Adds an key into the HashTree at the current level. If a HashTree exists
* for the key already, no new tree will be added
*
* @param key
* key to be added to HashTree
* @return newly generated tree, if no tree was found for the given key;
* existing key otherwise
*/
public HashTree add(Object key) {
if (!data.containsKey(key)) {
HashTree newTree = createNewTree();
data.put(key, newTree);
return newTree;
}
return getTree(key);
}
/**
* Adds all the given objects as nodes at the current level.
*
* @param keys
* Array of Keys to be added to HashTree.
*/
public void add(Object[] keys) {
for (Object key : keys) {
add(key);
}
}
/**
* Adds a bunch of keys into the HashTree at the current level.
*
* @param keys
* Collection of Keys to be added to HashTree.
*/
public void add(Collection<?> keys) {
for (Object o : keys) {
add(o);
}
}
/**
* Adds a key and it's value in the HashTree. The first argument becomes a
* node at the current level, and the second argument becomes a node of it.
*
* @param key
* key to be added
* @param value
* value to be added as a key in the secondary node
* @return HashTree for which <code>value</code> is the key
*/
public HashTree add(Object key, Object value) {
return add(key).add(value);
}
/**
* Adds a key and it's values in the HashTree. The first argument becomes a
* node at the current level, and adds all the values in the array to the
* new node.
*
* @param key
* key to be added
* @param values
* array of objects to be added as keys in the secondary node
*/
public void add(Object key, Object[] values) {
add(key).add(values);
}
/**
* Adds a key as a node at the current level and then adds all the objects
* in the second argument as nodes of the new node.
*
* @param key
* key to be added
* @param values
* Collection of objects to be added as keys in the secondary
* node
*/
public void add(Object key, Collection<?> values) {
add(key).add(values);
}
/**
* Adds a series of nodes into the HashTree using the given path. The first
* argument is an array that represents a path to a specific node in the
* tree. If the path doesn't already exist, it is created (the objects are
* added along the way). At the path, all the objects in the second argument
* are added as nodes.
*
* @param treePath
* an array of objects representing a path
* @param values
* array of values to be added as keys to bottom-most node
*/
public void add(Object[] treePath, Object[] values) {
if (treePath != null) {
add(Arrays.asList(treePath), Arrays.asList(values));
}
}
/**
* Adds a series of nodes into the HashTree using the given path. The first
* argument is an array that represents a path to a specific node in the
* tree. If the path doesn't already exist, it is created (the objects are
* added along the way). At the path, all the objects in the second argument
* are added as nodes.
*
* @param treePath
* an array of objects representing a path
* @param values
* collection of values to be added as keys to bottom-most node
*/
public void add(Object[] treePath, Collection<?> values) {
if (treePath != null) {
add(Arrays.asList(treePath), values);
}
}
public HashTree add(Object[] treePath, Object value) {
return add(Arrays.asList(treePath), value);
}
/**
* Adds a series of nodes into the HashTree using the given path. The first
* argument is a List that represents a path to a specific node in the tree.
* If the path doesn't already exist, it is created (the objects are added
* along the way). At the path, all the objects in the second argument are
* added as nodes.
*
* @param treePath
* a list of objects representing a path
* @param values
* array of values to be added as keys to bottom-most node
*/
public void add(Collection<?> treePath, Object[] values) {
HashTree tree = addTreePath(treePath);
tree.add(Arrays.asList(values));
}
/**
* Adds a series of nodes into the HashTree using the given path. The first
* argument is a List that represents a path to a specific node in the tree.
* If the path doesn't already exist, it is created (the objects are added
* along the way). At the path, the object in the second argument is added
* as a node.
*
* @param treePath
* a list of objects representing a path
* @param value
* Object to add as a node to bottom-most node
* @return HashTree for which <code>value</code> is the key
*/
public HashTree add(Collection<?> treePath, Object value) {
HashTree tree = addTreePath(treePath);
return tree.add(value);
}
/**
* Adds a series of nodes into the HashTree using the given path. The first
* argument is a SortedSet that represents a path to a specific node in the
* tree. If the path doesn't already exist, it is created (the objects are
* added along the way). At the path, all the objects in the second argument
* are added as nodes.
*
* @param treePath
* a SortedSet of objects representing a path
* @param values
* Collection of values to be added as keys to bottom-most node
*/
public void add(Collection<?> treePath, Collection<?> values) {
HashTree tree = addTreePath(treePath);
tree.add(values);
}
protected HashTree addTreePath(Collection<?> treePath) {
HashTree tree = this;
for (Object temp : treePath) {
tree = tree.add(temp);
}
return tree;
}
/**
* Gets the HashTree mapped to the given key.
*
* @param key
* Key used to find appropriate HashTree()
* @return the HashTree for <code>key</code>
*/
public HashTree getTree(Object key) {
return data.get(key);
}
/**
* Returns the HashTree object associated with the given key. Same as
* calling {@link #getTree(Object)}.
*
* @see java.util.Map#get(Object)
*/
@Override
public HashTree get(Object key) {
return getTree(key);
}
/**
* Gets the HashTree object mapped to the last key in the array by recursing
* through the HashTree structure one key at a time.
*
* @param treePath
* array of keys.
* @return HashTree at the end of the recursion.
*/
public HashTree getTree(Object[] treePath) {
if (treePath != null) {
return getTree(Arrays.asList(treePath));
}
return this;
}
/**
* Create a clone of this HashTree. This is not a deep clone (ie, the
* contents of the tree are not cloned).
*
*/
@Override
public Object clone() {
HashTree newTree = new HashTree();
cloneTree(newTree);
return newTree;
}
protected void cloneTree(HashTree newTree) {
for (Object key : list()) {
newTree.set(key, (HashTree) getTree(key).clone());
}
}
/**
* Creates a new tree. This method exists to allow inheriting classes to
* generate the appropriate types of nodes. For instance, when a node is
* added, it's value is a HashTree. Rather than directly calling the
* HashTree() constructor, the createNewTree() method is called. Inheriting
* classes should override these methods and create the appropriate subclass
* of HashTree.
*
* @return HashTree
*/
protected HashTree createNewTree() {
return new HashTree();
}
/**
* Creates a new tree. This method exists to allow inheriting classes to
* generate the appropriate types of nodes. For instance, when a node is
* added, it's value is a HashTree. Rather than directly calling the
* HashTree() constructor, the createNewTree() method is called. Inheriting
* classes should override these methods and create the appropriate subclass
* of HashTree.
*
* @param key
* object to use as the key for the top level
*
* @return newly created {@link HashTree}
*/
protected HashTree createNewTree(Object key) {
return new HashTree(key);
}
/**
* Creates a new tree. This method exists to allow inheriting classes to
* generate the appropriate types of nodes. For instance, when a node is
* added, it's value is a HashTree. Rather than directly calling the
* HashTree() constructor, the createNewTree() method is called. Inheriting
* classes should override these methods and create the appropriate subclass
* of HashTree.
*
* @param values objects to be added to the new {@link HashTree}
*
* @return newly created {@link HashTree}
*/
protected HashTree createNewTree(Collection<?> values) {
return new HashTree(values);
}
/**
* Gets the HashTree object mapped to the last key in the SortedSet by
* recursing through the HashTree structure one key at a time.
*
* @param treePath
* Collection of keys
* @return HashTree at the end of the recursion
*/
public HashTree getTree(Collection<?> treePath) {
return getTreePath(treePath);
}
/**
* Gets a Collection of all keys in the current HashTree node. If the
* HashTree represented a file system, this would be like getting a
* collection of all the files in the current folder.
*
* @return Set of all keys in this HashTree
*/
public Collection<Object> list() {
return data.keySet();
}
/**
* Gets a Set of all keys in the HashTree mapped to the given key of the
* current HashTree object (in other words, one level down. If the HashTree
* represented a file system, this would like getting a list of all files in
* a sub-directory (of the current directory) specified by the key argument.
*
* @param key
* key used to find HashTree to get list of
* @return Set of all keys in found HashTree.
*/
public Collection<?> list(Object key) {
HashTree temp = data.get(key);
if (temp != null) {
return temp.list();
}
return new HashSet<>();
}
/**
* Removes the entire branch specified by the given key.
*
* @see java.util.Map#remove(Object)
*/
@Override
public HashTree remove(Object key) {
return data.remove(key);
}
/**
* Recurses down into the HashTree stucture using each subsequent key in the
* array of keys, and returns the Set of keys of the HashTree object at the
* end of the recursion. If the HashTree represented a file system, this
* would be like getting a list of all the files in a directory specified by
* the treePath, relative from the current directory.
*
* @param treePath
* Array of keys used to recurse into HashTree structure
* @return Set of all keys found in end HashTree
*/
public Collection<?> list(Object[] treePath) { // TODO not used?
if (treePath != null) {
return list(Arrays.asList(treePath));
}
return list();
}
/**
* Recurses down into the HashTree stucture using each subsequent key in the
* List of keys, and returns the Set of keys of the HashTree object at the
* end of the recursion. If the HashTree represented a file system, this
* would be like getting a list of all the files in a directory specified by
* the treePath, relative from the current directory.
*
* @param treePath
* List of keys used to recurse into HashTree structure
* @return Set of all keys found in end HashTree
*/
public Collection<?> list(Collection<?> treePath) {
HashTree tree = getTreePath(treePath);
if (tree != null) {
return tree.list();
}
return new HashSet<>();
}
/**
* Finds the given current key, and replaces it with the given new key. Any
* tree structure found under the original key is moved to the new key.
*
* @param currentKey name of the key to be replaced
* @param newKey name of the new key
*/
public void replaceKey(Object currentKey, Object newKey) {
HashTree tree = getTree(currentKey);
data.remove(currentKey);
data.put(newKey, tree);
}
/**
* Gets an array of all keys in the current HashTree node. If the HashTree
* represented a file system, this would be like getting an array of all the
* files in the current folder.
*
* @return array of all keys in this HashTree.
*/
public Object[] getArray() {
return data.keySet().toArray();
}
/**
* Gets an array of all keys in the HashTree mapped to the given key of the
* current HashTree object (in other words, one level down). If the HashTree
* represented a file system, this would like getting a list of all files in
* a sub-directory (of the current directory) specified by the key argument.
*
* @param key
* key used to find HashTree to get list of
* @return array of all keys in found HashTree
*/
public Object[] getArray(Object key) {
HashTree t = getTree(key);
if (t != null) {
return t.getArray();
}
return null;
}
/**
* Recurses down into the HashTree stucture using each subsequent key in the
* array of keys, and returns an array of keys of the HashTree object at the
* end of the recursion. If the HashTree represented a file system, this
* would be like getting a list of all the files in a directory specified by
* the treePath, relative from the current directory.
*
* @param treePath
* array of keys used to recurse into HashTree structure
* @return array of all keys found in end HashTree
*/
public Object[] getArray(Object[] treePath) {
if (treePath != null) {
return getArray(Arrays.asList(treePath));
}
return getArray();
}
/**
* Recurses down into the HashTree structure using each subsequent key in the
* treePath argument, and returns an array of keys of the HashTree object at
* the end of the recursion. If the HashTree represented a file system, this
* would be like getting a list of all the files in a directory specified by
* the treePath, relative from the current directory.
*
* @param treePath
* list of keys used to recurse into HashTree structure
* @return array of all keys found in end HashTree
*/
public Object[] getArray(Collection<?> treePath) {
HashTree tree = getTreePath(treePath);
return (tree != null) ? tree.getArray() : null;
}
protected HashTree getTreePath(Collection<?> treePath) {
HashTree tree = this;
for (Object aTreePath : treePath) {
// Fixme why is this check here ?
if (tree == null) {
return null;
}
tree = tree.getTree(aTreePath);
}
return tree;
}
/**
* Returns a hashcode for this HashTree.
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return data.hashCode() * 7;
}
/**
* Compares all objects in the tree and verifies that the two trees contain
* the same objects at the same tree levels. Returns true if they do, false
* otherwise.
*
* @param o
* Object to be compared against
* @see java.lang.Object#equals(Object)
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof HashTree)) {
return false;
}
HashTree oo = (HashTree) o;
if (oo.size() != this.size()) {
return false;
}
return data.equals(oo.data);
}
/**
* Returns a Set of all the keys in the top-level of this HashTree.
*
* @see java.util.Map#keySet()
*/
@Override
public Set<Object> keySet() {
return data.keySet();
}
/**
* Searches the HashTree structure for the given key. If it finds the key,
* it returns the HashTree mapped to the key. If it finds nothing, it
* returns null.
*
* @param key
* Key to search for
* @return HashTree mapped to key, if found, otherwise <code>null</code>
*/
public HashTree search(Object key) {// TODO does not appear to be used
HashTree result = getTree(key);
if (result != null) {
return result;
}
TreeSearcher searcher = new TreeSearcher(key);
try {
traverse(searcher);
} catch (RuntimeException e) {
if (!e.getMessage().equals(FOUND)){
throw e;
}
// do nothing - means object is found
}
return searcher.getResult();
}
/**
* Method readObject.
*
* @param ois
* the stream to read the objects from
* @throws ClassNotFoundException
* when the class for the deserialization can not be found
* @throws IOException
* when I/O error occurs
*/
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
}
/**
* Returns the number of top-level entries in the HashTree.
*
* @see java.util.Map#size()
*/
@Override
public int size() {
return data.size();
}
/**
* Allows any implementation of the HashTreeTraverser interface to easily
* traverse (depth-first) all the nodes of the HashTree. The Traverser
* implementation will be given notification of each node visited.
*
* @see HashTreeTraverser
* @param visitor
* the visitor that wants to traverse the tree
*/
public void traverse(HashTreeTraverser visitor) {
for (Object item : list()) {
visitor.addNode(item, getTree(item));
getTree(item).traverseInto(visitor);
}
}
/**
* The recursive method that accomplishes the tree-traversal and performs
* the callbacks to the HashTreeTraverser.
*
* @param visitor
* the {@link HashTreeTraverser} to be notified
*/
private void traverseInto(HashTreeTraverser visitor) {
if (list().size() == 0) {
visitor.processPath();
} else {
for (Object item : list()) {
final HashTree treeItem = getTree(item);
visitor.addNode(item, treeItem);
treeItem.traverseInto(visitor);
}
}
visitor.subtractNode();
}
/**
* Generate a printable representation of the tree.
*
* @return a representation of the tree
*/
@Override
public String toString() {
ConvertToString converter = new ConvertToString();
try {
traverse(converter);
} catch (Exception e) { // Just in case
converter.reportError(e);
}
return converter.toString();
}
private static class TreeSearcher implements HashTreeTraverser {
private final Object target;
private HashTree result;
public TreeSearcher(Object t) {
target = t;
}
public HashTree getResult() {
return result;
}
/** {@inheritDoc} */
@Override
public void addNode(Object node, HashTree subTree) {
result = subTree.getTree(target);
if (result != null) {
// short circuit traversal when found
throw new RuntimeException(FOUND);
}
}
/** {@inheritDoc} */
@Override
public void processPath() {
// Not used
}
/** {@inheritDoc} */
@Override
public void subtractNode() {
// Not used
}
}
private static class ConvertToString implements HashTreeTraverser {
private final StringBuilder string = new StringBuilder(getClass().getName() + "{");
private final StringBuilder spaces = new StringBuilder();
private int depth = 0;
@Override
public void addNode(Object key, HashTree subTree) {
depth++;
string.append("\n").append(getSpaces()).append(key);
string.append(" {");
}
@Override
public void subtractNode() {
string.append("\n" + getSpaces() + "}");
depth--;
}
@Override
public void processPath() {
}
@Override
public String toString() {
string.append("\n}");
return string.toString();
}
void reportError(Throwable t){
string.append("Error: ").append(t.toString());
}
private String getSpaces() {
if (spaces.length() < depth * 2) {
while (spaces.length() < depth * 2) {
spaces.append(" ");
}
} else if (spaces.length() > depth * 2) {
spaces.setLength(depth * 2);
}
return spaces.toString();
}
}
}