Thu. Mar 28th, 2024

One thing I have always found lacking in Java collections/containers is TreeMap. A simple class that takes a hierarchical approach to mapping data to a node.  Given a hierarchical string like “/java/myapp/javafx/config/phone” I can set the various objects relative to this on that node.

Java Preferences() class supports this hierarchical approach but it is limited to native data types. I have never understood why this has been missing from the JRE.  So I created my own implementation and here it is for anyone else looking.

Here is the code to let you place any kind of object on a specified node.

package name.mymiller.extensions.containers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * @param <T> Type of Object to map
 * @author jmiller
 * Class used to map values out on a tree.  Allows for infinite depth. Key names should be in form of
 * "/text/text/text/...."
 */
public class TreeMap<T> {

    /**
     * Roote Node of the tree also known as "/"
     */
    private TreeNode root = null;

    /**
     * Adds the Value to the node at specified Key
     *
     * @param key   Specified branch to add values to
     * @param value Values to add at node
     */
    public void add(String key, T value) {
        TreeNode node = this.findNode(key);

        ArrayList<T> values = node.getValues();
        if (values == null) {
            values = new ArrayList<>();
            node.setValues(values);
        }

        values.add(value);
    }

    /**
     * Add Collection of values to the specified node
     *
     * @param key        Specifies the node to add values to.
     * @param collection Collection of values to add to the node.
     * @return
     */
    public boolean addAll(String key, Collection<? extends T> collection) {
        TreeNode node = this.findNode(key);

        ArrayList<T> values = node.getValues();
        if (values == null) {
            values = new ArrayList<T>();
            node.setValues(values);
        }

        return values.addAll(collection);
    }

    /**
     * Remove the colleciton of values from the specified node
     *
     * @param key        Specifies the node to remove values from.
     * @param collection Collection of values to remove.
     * @return Boolean indicating if values remoted.
     */
    public boolean removeAll(String key, Collection<?> collection) {
        TreeNode node = this.findNode(key);

        ArrayList<T> values = node.getValues();
        if (values == null) {
            return false;
        }

        return values.removeAll(collection);
    }

    /**
     * Remove a value from specified node
     *
     * @param key   Specified node to remove value from
     * @param value Valueto remove from specified node.
     * @return boolean indicating success.
     */
    public boolean remove(String key, T value) {
        TreeNode node = this.findNode(key);
        ArrayList<T> values = node.getValues();

        if (values == null) {
            return false;
        }

        return values.remove(value);
    }

    /**
     * Clear all values from specified node
     *
     * @param key Node to clear all values from.
     */
    public void clearNode(String key) {
        TreeNode node = this.findNode(key);
        ArrayList<T> values = node.getValues();

        if (values != null) {
            values.clear();
        }
    }

    /**
     * Returns the values associated with this key, or null if none are set.
     *
     * @param key Specified Node to return values on.
     * @return List of values.
     */
    public List<T> get(String key) {
        TreeNode node = this.findNode(key);
        return node.getValues();
    }

    /**
     * Internal method used to find/create the tree structure to the specified node
     *
     * @param key Node to create/find
     * @return Node specified
     */
    private TreeNode findNode(String key) {
        String[] keys = key.split("/");

        TreeNode currentNode = null;
        for (String nodeKey : keys) {
            if (nodeKey.isEmpty()) {
                if (this.root == null) {
                    this.root = new TreeNode();
                }
                currentNode = this.root;
            } else if (currentNode == null) {
                if (this.root == null) {
                    this.root = new TreeNode();
                }
                currentNode = this.root;
            }

            if (!nodeKey.isEmpty()) {
                HashMap<String, TreeNode> children = currentNode.getChildren();
                if (children == null) {
                    currentNode.setChildren(new HashMap<>());
                    children = currentNode.getChildren();
                } else {
                    currentNode = children.get(nodeKey);
                }

                if (currentNode == null) {
                    currentNode = new TreeNode();
                    children.put(nodeKey, currentNode);
                }
            }
        }
        return currentNode;
    }

    /**
     * Internal class used to map out tree structure and hold values.
     *
     * @author jmiller
     */
    private class TreeNode {
        /**
         * Values of this branch holds
         */
        private ArrayList<T> values = null;

        /**
         * Map directing to the children of this branch.
         */
        private HashMap<String, TreeNode> children = null;

        /**
         * Get the values of the current Node
         *
         * @return ArrayList of the values on this node
         */
        public ArrayList<T> getValues() {
            return values;
        }

        /**
         * Sets the values of this node.
         *
         * @param values ArrayList containing the values of this node.
         */
        public void setValues(ArrayList<T> values) {
            this.values = values;
        }

        /**
         * @return Get the HashMap that has the children of this node.
         */
        public HashMap<String, TreeNode> getChildren() {
            return children;
        }

        /**
         * Sets the HashMap for this node, with the children
         *
         * @param children Hashmap of the children of this node.
         */
        public void setChildren(HashMap<String, TreeNode> children) {
            this.children = children;
        }
    }

}

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d