From 16283572c7d42665e6fc322f016bbd8fd8f8b992 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 9 Feb 2021 14:56:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E5=AF=BC=E5=85=A5jmx=20=E5=A2=9E=E5=8A=A0HashTre?= =?UTF-8?q?e=20=E6=9C=89=E5=BA=8F=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/automation/parse/MsJmeterParser.java | 2 +- .../base/mapper/SwaggerUrlProjectMapper.xml | 437 ++++--- .../apache/jorphan/collections/HashTree.java | 1095 +++++++++++++++++ 3 files changed, 1313 insertions(+), 221 deletions(-) create mode 100644 backend/src/main/java/org/apache/jorphan/collections/HashTree.java diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java index b40763924c..e1030e02e1 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java @@ -332,7 +332,7 @@ public class MsJmeterParser extends ScenarioImportAbstractParser { private void jmterHashTree(HashTree tree, MsTestElement scenario) { for (Object key : tree.keySet()) { - MsTestElement elementNode = null; + MsTestElement elementNode; if (CollectionUtils.isEmpty(scenario.getHashTree())) { scenario.setHashTree(new LinkedList<>()); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/SwaggerUrlProjectMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/SwaggerUrlProjectMapper.xml index e520c74530..ffa39fb45e 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/SwaggerUrlProjectMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/SwaggerUrlProjectMapper.xml @@ -1,231 +1,228 @@ - - - - - - - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + - - - - - - - - - - - and ${criterion.condition} - - - and ${criterion.condition} #{criterion.value} - - - and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} - - - and ${criterion.condition} - - #{listItem} - - - - - - + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + - - - - id, project_id, swagger_url, module_id, module_path, mode_id - - - - - delete - from swagger_url_project - where id = #{id,jdbcType=VARCHAR} - - - delete from swagger_url_project - - - - - - insert into swagger_url_project (id, project_id, swagger_url, - module_id, module_path, mode_id) - values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{swaggerUrl,jdbcType=VARCHAR}, - #{moduleId,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR}, #{modeId,jdbcType=VARCHAR}) - - - insert into swagger_url_project - - - id, - - - project_id, - - - swagger_url, - - - module_id, - - - module_path, - - - mode_id, - - - - - #{id,jdbcType=VARCHAR}, - - - #{projectId,jdbcType=VARCHAR}, - - - #{swaggerUrl,jdbcType=VARCHAR}, - - - #{moduleId,jdbcType=VARCHAR}, - - - #{modulePath,jdbcType=VARCHAR}, - - - #{modeId,jdbcType=VARCHAR}, - - - - - - update swagger_url_project - - - id = #{record.id,jdbcType=VARCHAR}, - - - project_id = #{record.projectId,jdbcType=VARCHAR}, - - - swagger_url = #{record.swaggerUrl,jdbcType=VARCHAR}, - - - module_id = #{record.moduleId,jdbcType=VARCHAR}, - - - module_path = #{record.modulePath,jdbcType=VARCHAR}, - - - mode_id = #{record.modeId,jdbcType=VARCHAR}, - - - - - - - - update swagger_url_project - set id = #{record.id,jdbcType=VARCHAR}, + + + + + id, project_id, swagger_url, module_id, module_path, mode_id + + + + + delete from swagger_url_project + where id = #{id,jdbcType=VARCHAR} + + + delete from swagger_url_project + + + + + + insert into swagger_url_project (id, project_id, swagger_url, + module_id, module_path, mode_id + ) + values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{swaggerUrl,jdbcType=VARCHAR}, + #{moduleId,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR}, #{modeId,jdbcType=VARCHAR} + ) + + + insert into swagger_url_project + + + id, + + + project_id, + + + swagger_url, + + + module_id, + + + module_path, + + + mode_id, + + + + + #{id,jdbcType=VARCHAR}, + + + #{projectId,jdbcType=VARCHAR}, + + + #{swaggerUrl,jdbcType=VARCHAR}, + + + #{moduleId,jdbcType=VARCHAR}, + + + #{modulePath,jdbcType=VARCHAR}, + + + #{modeId,jdbcType=VARCHAR}, + + + + + + update swagger_url_project + + + id = #{record.id,jdbcType=VARCHAR}, + + project_id = #{record.projectId,jdbcType=VARCHAR}, + + swagger_url = #{record.swaggerUrl,jdbcType=VARCHAR}, + + module_id = #{record.moduleId,jdbcType=VARCHAR}, + + module_path = #{record.modulePath,jdbcType=VARCHAR}, - mode_id = #{record.modeId,jdbcType=VARCHAR} - - - - - - update swagger_url_project - - - project_id = #{projectId,jdbcType=VARCHAR}, - - - swagger_url = #{swaggerUrl,jdbcType=VARCHAR}, - - - module_id = #{moduleId,jdbcType=VARCHAR}, - - - module_path = #{modulePath,jdbcType=VARCHAR}, - - - mode_id = #{modeId,jdbcType=VARCHAR}, - - - where id = #{id,jdbcType=VARCHAR} - - - update swagger_url_project - set project_id = #{projectId,jdbcType=VARCHAR}, - swagger_url = #{swaggerUrl,jdbcType=VARCHAR}, - module_id = #{moduleId,jdbcType=VARCHAR}, - module_path = #{modulePath,jdbcType=VARCHAR}, - mode_id = #{modeId,jdbcType=VARCHAR} - where id = #{id,jdbcType=VARCHAR} - + + + mode_id = #{record.modeId,jdbcType=VARCHAR}, + + + + + + + + update swagger_url_project + set id = #{record.id,jdbcType=VARCHAR}, + project_id = #{record.projectId,jdbcType=VARCHAR}, + swagger_url = #{record.swaggerUrl,jdbcType=VARCHAR}, + module_id = #{record.moduleId,jdbcType=VARCHAR}, + module_path = #{record.modulePath,jdbcType=VARCHAR}, + mode_id = #{record.modeId,jdbcType=VARCHAR} + + + + + + update swagger_url_project + + + project_id = #{projectId,jdbcType=VARCHAR}, + + + swagger_url = #{swaggerUrl,jdbcType=VARCHAR}, + + + module_id = #{moduleId,jdbcType=VARCHAR}, + + + module_path = #{modulePath,jdbcType=VARCHAR}, + + + mode_id = #{modeId,jdbcType=VARCHAR}, + + + where id = #{id,jdbcType=VARCHAR} + + + update swagger_url_project + set project_id = #{projectId,jdbcType=VARCHAR}, + swagger_url = #{swaggerUrl,jdbcType=VARCHAR}, + module_id = #{moduleId,jdbcType=VARCHAR}, + module_path = #{modulePath,jdbcType=VARCHAR}, + mode_id = #{modeId,jdbcType=VARCHAR} + where id = #{id,jdbcType=VARCHAR} + \ No newline at end of file diff --git a/backend/src/main/java/org/apache/jorphan/collections/HashTree.java b/backend/src/main/java/org/apache/jorphan/collections/HashTree.java new file mode 100644 index 0000000000..828ab5861a --- /dev/null +++ b/backend/src/main/java/org/apache/jorphan/collections/HashTree.java @@ -0,0 +1,1095 @@ +/* + * 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.*; + +/** + * 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. + *

+ * 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. + *

+ * 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, 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 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 _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 LinkedHashMap(), 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 null a new {@link LinkedHashMap} + * will be created + * @param key + * the object to be used as the key for the root node (may be + * null, in which case no root node will be created) + */ + private HashTree(Map _map, Object key) { + if(_map != null) { + data = _map; + } else { + data = new LinkedHashMap<>(); + } + 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 Map#putAll(Map) + */ + @Override + public void putAll(Map 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 Map#entrySet() + */ + @Override + public Set> 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 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 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 Map#clear() + */ + @Override + public void clear() { + data.clear(); + } + + /** + * Returns a collection of all the sub-trees of the current tree. + * + * @see Map#values() + */ + @Override + public Collection 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 LinkedHashMap<>(); + 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 LinkedHashMap<>(); + 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 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 value 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 value 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 key + */ + 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 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 (i.e., 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 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 Map#remove(Object) + */ + @Override + public HashTree remove(Object key) { + return data.remove(key); + } + + /** + * Recurses down into the HashTree structure 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) { + if (treePath != null) { + return list(Arrays.asList(treePath)); + } + return list(); + } + + /** + * Recurses down into the HashTree structure 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 structure 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) { + tree = tree.getTree(aTreePath); + if (tree == null) { + return null; + } + } + return tree; + } + + /** + * Returns a hashcode for this HashTree. + * + * @see 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 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 Map#keySet() + */ + @Override + public Set 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 null + */ + public HashTree search(Object key) { + 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 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().isEmpty()) { + 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() { + // NOOP + } + + @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(); + } + } +}