ListTree.java

package org.europa.together.application;

import java.util.ArrayList;
import java.util.List;
import org.europa.together.business.Logger;
import org.europa.together.business.TreeWalker;
import org.europa.together.domain.LogLevel;
import org.europa.together.domain.TreeNode;
import org.europa.together.exceptions.MisconfigurationException;
import org.europa.together.utils.StringUtils;
import org.springframework.stereotype.Repository;

/**
 * Implementation of a TreeWalker.
 *
 * @param <T> define the TreeType
 */
@Repository
public class ListTree<T> implements TreeWalker<T> {

    private static final long serialVersionUID = 9L;
    private static final Logger LOGGER = new LogbackLogger(ListTree.class);

    private List<TreeNode<T>> tree = new ArrayList<>();
    private String rootUuid;

    /**
     * Constructor.
     */
    public ListTree() {
        LOGGER.log("instance class", LogLevel.INFO);
    }

    /**
     * Constructor.
     *
     * @param root as TreeNode
     */
    public ListTree(final TreeNode<T> root) {
        this.addRoot(root);
        LOGGER.log("instance class (" + root.getNodeName() + ")", LogLevel.INFO);
    }

    @Override
    public boolean addRoot(final TreeNode<T> root) {
        boolean success = false;
        if (StringUtils.isEmpty(rootUuid)) {
            this.rootUuid = root.getUuid();
            root.setParent("-1");
            tree.add(root);
            success = true;
            LOGGER.log("ROOT: [" + this.rootUuid + "] added.", LogLevel.DEBUG);
        } else {
            LOGGER.log("Root node [" + this.rootUuid + "] for this tree already exist.",
                    LogLevel.WARN);
        }
        return success;
    }

    @Override
    public boolean isElementOfTree(final TreeNode<T> node) {
        boolean success = false;
        if (!tree.isEmpty()) {
            for (TreeNode<T> element : this.tree) {
                if (node.equals(element)) {
                    success = true;
                }
            }
        } else {
            LOGGER.log("Operation not possible, because Tree is empty.", LogLevel.WARN);
        }
        return success;
    }

    @Override
    public boolean isEmpty() {
        return tree.isEmpty();
    }

    @Override
    public boolean isLeaf(final TreeNode<T> leaf) {
        boolean isLeaf = false;
        if (!tree.isEmpty()) {
            boolean hasParent = false;
            for (TreeNode<T> check : this.tree) {
                if (leaf.getUuid().equals(check.getParent())) {
                    hasParent = true;
                    break;
                }
            }
            if (!hasParent) {
                isLeaf = true;
            }
        } else {
            LOGGER.log("Operation not possible, because Tree is empty.", LogLevel.WARN);
        }
        return isLeaf;
    }

    @Override
    public boolean removeNode(final TreeNode<T> node) {
        boolean success = false;
        if (this.isLeaf(node)) {
            this.tree.remove(node);
            success = true;
            LOGGER.log("Node " + node.getNodeName() + " removed.", LogLevel.DEBUG);
        } else {
            LOGGER.log("Node " + node.getNodeName()
                    + " can't removed. First you need to remove all childe node(s)",
                    LogLevel.WARN);
        }
        return success;
    }

    @Override
    public int countNodes() {
        return this.tree.size();
    }

    @Override
    public int isNameUnique(final String nodeName) {
        int count = 0;
        if (!tree.isEmpty()) {
            for (TreeNode<T> node : this.tree) {
                if (node.getNodeName().equals(nodeName)) {
                    count++;
                }
            }
        } else {
            LOGGER.log("Operation not possible, because Tree is empty.", LogLevel.WARN);
        }
        return count;
    }

    @Override
    public List<TreeNode<T>> getElementByName(final String nodeName) {
        List<TreeNode<T>> elements = new ArrayList<>();
        if (!tree.isEmpty()) {
            for (TreeNode<T> node : this.tree) {
                if (node.getNodeName().equals(nodeName)) {
                    elements.add(node);
                }
            }

            if (elements.isEmpty()) {
                LOGGER.log("No Node(s) with the name " + nodeName + " found.", LogLevel.DEBUG);
            }
        } else {
            LOGGER.log("Operation not possible, because Tree is empty.", LogLevel.WARN);
        }
        return elements;
    }

    @Override
    public List<TreeNode<T>> getLeafs() {
        List<TreeNode<T>> leafs = new ArrayList<>();
        for (TreeNode<T> node : tree) {
            if (isLeaf(node)) {
                leafs.add(node);
            }
        }
        LOGGER.log(leafs.size() + " Leaf Nodes in the tree detected.", LogLevel.DEBUG);
        return leafs;
    }

    @Override
    public List<TreeNode<T>> getTree() {
        return List.copyOf(tree);
    }

    @Override
    public TreeNode<T> getNodeByUuid(final String uuid) {
        TreeNode<T> search = null;
        if (!tree.isEmpty()) {
            for (TreeNode<T> node : this.tree) {
                if (node.getUuid().equals(uuid)) {
                    search = node;
                    break;
                }
            }
        } else {
            LOGGER.log("Operation not possible, because Tree is empty.", LogLevel.WARN);
        }
        return search;
    }

    @Override
    public TreeNode<T> getRoot() {
        TreeNode<T> root = null;
        if (this.rootUuid != null) {
            for (TreeNode<T> node : this.tree) {
                if (node.getUuid().equals(this.rootUuid)) {
                    root = node;
                }
            }
        }
        return root;
    }

    @Override
    public boolean addNode(final TreeNode<T> node) {
        boolean add = true;
        if (node != null) {
            for (TreeNode<T> check : this.tree) {
                if (node.getUuid().equals(check.getUuid())) {
                    LOGGER.log("Node with this UUID already exist.", LogLevel.WARN);
                    add = false;
                    break;
                }
                if (!StringUtils.isEmpty(node.getParent())
                        && node.getParent().equals(check.getParent())
                        && node.getNodeName().equals(check.getNodeName())) {
                    LOGGER.log("Node with same name AND parent  already exist.",
                            LogLevel.WARN);
                    add = false;
                    break;
                }
            }
            if (add) {
                tree.add(node);
                LOGGER.log("Node [" + node.getNodeName() + "] added.",
                        LogLevel.DEBUG);
            }
        } else {
            add = false;
        }
        return add;
    }

    @Override
    public void clear() {
        this.tree.clear();
        this.rootUuid = "";
        LOGGER.log("Tree successful reseted.", LogLevel.TRACE);
    }

    @Override
    public void prune(final TreeNode<T> cutNode) throws MisconfigurationException {
        List<TreeNode<T>> stack = new ArrayList<>();
        LOGGER.log("Cut Tree by Node "
                + cutNode.getNodeName() + " ["
                + cutNode.getUuid() + "]", LogLevel.DEBUG);
        stack.addAll(tree);
        if (stack.isEmpty()) {
            throw new MisconfigurationException("prune: tree is empty nothing to do");
        } else if (cutNode.equals(getRoot())) {
            clear();
        } else if (isLeaf(cutNode)) {
            removeNode(cutNode);
        } else {
            TreeNode<T> node = cutNode;
            List<TreeNode<T>> helper = new ArrayList<>();
            List<TreeNode<T>> cutted = new ArrayList<>();
            cutted.add(node);
            for (int i = 0; i < tree.size(); i++) {
                for (TreeNode<T> compare : stack) {
                    if (i != 0) {
                        node = compare;
                    }
                    for (int j = 0; j < tree.size(); j++) {
                        if (node.getUuid().equals(compare.getUuid())) {
                            cutted.add(compare);
                            if (!isLeaf(compare)) {
                                helper.add(compare);
                            }
                            break;
                        }
                    }
                }
                if (!helper.isEmpty()) {
                    stack.clear();
                    for (TreeNode<T> compare : helper) {
                        for (TreeNode<T> element : this.tree) {
                            if (compare.getUuid().equals(element.getParent())) {
                                stack.add(element);
                            }
                        }
                    }
                    cutted.addAll(stack);
                    helper.clear();
                } else {
                    break;
                }
            }
            for (TreeNode<T> item : cutted) {
                this.tree.remove(item);
            }
        }
    }

    @Override
    public void merge(final String parentUuid, final TreeWalker<T> appendingTree) {
        if (appendingTree != null && !appendingTree.isEmpty()) {
            TreeNode<T> appendingRoot = appendingTree.getRoot();
            appendingRoot.setParent(parentUuid);
            List<TreeNode<T>> newTree = new ArrayList<>();
            newTree.addAll(appendingTree.getTree());
            this.tree.addAll(newTree);
            LOGGER.log("Append " + newTree.size()
                    + " Nodes to the new Tree.", LogLevel.DEBUG);
        } else {
            LOGGER.log("Merging tree is empty - no merge applied.", LogLevel.DEBUG);
        }
    }

    @Override
    public boolean validateTree(final List<TreeNode<T>> collection)
            throws MisconfigurationException {
        boolean isTree = false;
        int marker = 0;
        int counter = 0;
        int root = 0;
        if (!collection.isEmpty()) {
            marker = collection.size() - 1;
            List<TreeNode<T>> comperator = new ArrayList<>();
            comperator.addAll(collection);
            for (TreeNode<T> node : collection) {
                if (StringUtils.isEmpty(node.getParent())) {
                    counter++;
                    LOGGER.log("Element with empty parent: " + node.getNodeName(), LogLevel.DEBUG);
                }
                if (!StringUtils.isEmpty(node.getParent()) && node.getParent().equals("-1")) {
                    root++;
                    LOGGER.log("ROOT element: " + node.getNodeName(), LogLevel.DEBUG);
                }
                for (TreeNode<T> element : comperator) {
                    if (!StringUtils.isEmpty(node.getParent())
                            && !node.getParent().equals("-1")
                            && !element.equals(node)
                            && element.getUuid().equals(node.getParent())) {
                        marker--;
                        LOGGER.log("Parent of (" + node.getNodeName() + ") : " + node.getUuid()
                                + " : (" + element.getNodeName() + ") " + element.getParent(),
                                LogLevel.DEBUG);
                    }
                }
            }
        }
        if (root == 0) {
            LOGGER.log("Tree has no ROOT element.", LogLevel.DEBUG);
        } else if (root > 1) {
            LOGGER.log("Tree has muliple ROOT elements.", LogLevel.DEBUG);
        }
        if (marker != 0) {
            LOGGER.log("Tree has " + marker + " disconnected Elemets.", LogLevel.DEBUG);
        }
        if (counter == 0
                && root == 1
                && marker == 0) {
            isTree = true;
        }
        return isTree;
    }

    @Override
    public String toString() {
        StringBuilder out = new StringBuilder();
        out.append("TreeWalkerImpl{\n")
                .append("\t Root: ")
                .append(getRoot().getNodeName())
                .append("\n")
                .append("\t Nodes: ")
                .append(tree.size())
                .append("\n TREE >>>\n");
        for (TreeNode<T> node : tree) {
            out.append("\t")
                    .append(node.toString())
                    .append("\n");
        }
        out.append("}");
        return out.toString();
    }
}