treeNormalizer.structure.treeBucket.java Source code

Java tutorial

Introduction

Here is the source code for treeNormalizer.structure.treeBucket.java

Source

/*
 * Copyright (C) 2017 Till Uhlig <till.uhlig@student.uni-halle.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package treeNormalizer.structure;

import treeNormalizer.structure.internal.treeBucketNode;
import treeNormalizer.structure.internal.nodeReference;
import treeNormalizer.structure.internal.internalTree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.jdom.Document;
import treeNormalizer.utils.UID;

/**
 * Diese Klasse verwaltet eine Menge von Bumen, mglichst kompakt.
 *
 * @author Till Uhlig {@literal <till.uhlig@student.uni-halle.de>}
 */
public class treeBucket {

    /*
     * erzeugt fr uns eindeutige IDs
     */
    private UID UID_object = null;

    /**
     * diese Liste von Knotenreferenzen bilden auf die realen Knoten ab
     */
    private final Map<Integer, treeBucketNode> nodeReferences = new HashMap<>();

    /**
     * diese Sammlung enthlt die Teilgraphen (also die realen Knoten anhand
     * deren Hash)
     */
    private final Map<Integer, treeBucketNode> nodes = new HashMap<>();
    /*
     * hier wird die Position vermerkt, an der er mit der Vergabe der IDs fr
     * Referenzen weitermachen soll. Wenn beispielsweise eine ID gelscht wird,
     * soll er vielleicht dort erstmal anfragen.
     */
    private int possibleNextReferenceId = 1;

    /**
     * enthlt alle Zeiger auf die Wurzeln der Bume
     */
    private final ArrayList<internalTree> trees = new ArrayList<>();

    /**
     * initialisiert einen Baumbehlter
     */
    public treeBucket() {
        this.UID_object = new UID();
    }

    /**
     * fgt einen Knoten der Verwaltung hinzu
     *
     * @param tree der Zielbaum
     * @param node der Knoten
     * @return die eindeutige Knotenreferenz auf diesen Knoten bzw. den Inhalt
     *         den er darstellt
     */
    private nodeReference addNode(internalTree tree, treeBucketNode node) {
        node = addNodeSimple(node);
        return createNodeReference(tree, node);
    }

    /**
     * fgt einen Knoten in die Verwaltung ein, wenn der Knoten so schon
     * existiert, dann wird der bestehende Knoten mit diesem verschmolzen
     *
     * @param node der einzufgende Knoten
     * @return der bergebene Knoten oder ein bereits vorhandener Knoten
     */
    private treeBucketNode addNodeSimple(treeBucketNode node) {
        if (nodes.containsKey(node.hashCode())) {
            // der Knoten existiert bereits
            treeBucketNode localNode = nodes.get(node.hashCode());
            mergeNode(localNode, node);
            node = localNode;
            node.updateStoreId();
        } else {
            // der Knoten existiert so noch nicht
            nodes.put(node.hashCode(), node);
            node.updateStoreId();
        }
        return node;
    }

    /**
     * fgt einen Baum in die Verwaltung ein
     *
     * @param tree der Baum
     * @return der Baum oder null, wenn nichts eingefgt werden konnte
     */
    private internalTree addTree(internalTree tree) {
        // der Name des Baums soll eindeutig sein
        if (getTreeByName(tree.getName()) != null) {
            // der Baum existiert bereits
            return null;
        }

        trees.add(tree);
        return tree;
    }

    /**
     * fgt einen Knoten in einen Baum ein
     *
     * @param newId die neue ID des Knotens (normalerweise automatisch gesetzt)
     *              (wir vorallem fr Tests genutzt)
     * @param tree  der Baum
     * @param name  der Name des Knotens
     * @param type  der Typ des Knotens
     * @return die Referenz auf den Knoten
     */
    private nodeReference createNode(long newId, internalTree tree, String name, String type) {
        treeBucketNode tmp = new treeBucketNode(newId, name, type);
        return addNode(tree, tmp);

    }

    /**
     * erzeugt eine neue Referenz
     *
     * @param tree der Baum
     * @param node der Knoten
     * @return die neue Referenz
     */
    private nodeReference createNodeReference(internalTree tree, treeBucketNode node) {
        int newId = getNextReferenceId();
        nodeReference tmp = new nodeReference(tree, newId);
        getNodeReferences().put(newId, node);
        node.addNodeReference(tmp);
        return tmp;
    }

    /**
     * liefert zu einer Knotenreferenz den internen Knoten
     *
     * @param node die Referenz
     * @return der Knoten
     */
    private treeBucketNode getInternalNodeByReference(nodeReference node) {
        if (node == null) {
            return null;
        }
        return getNodeReferences().get(node.getId());
    }

    /**
     * liefert alle internen Knoten
     *
     * @return die Knoten
     */
    private Map<Integer, treeBucketNode> getInternalNodes() {
        return nodes;
    }

    /**
     * liefert die nchste eindeutige Id einen treeBucketNode
     *
     * @return die neue KnotenId
     */
    private long getNextNodeId() {
        return this.UID_object.nextUID();
    }

    /**
     * liefert die nchste eindeutige Id fr eine neue Referenz
     *
     * @return die neue ReferenzId
     */
    private int getNextReferenceId() {
        if (getNodeReferences().containsKey(possibleNextReferenceId)) {
            possibleNextReferenceId = random.nextNonZero();
            while (getNodeReferences().containsKey(increaseToNextPossibleReferenceId())) {
                // kann endlos laufen
            }
        }
        int a = possibleNextReferenceId;
        increaseToNextPossibleReferenceId();
        return a;
    }

    private treeBucketNode getParent(nodeReference node) {
        if (!node.hasParent()) {
            return null;
        }
        return getInternalNodeByReference(node.getParent());
    }

    /**
     * liefert zu der Referenz einen Knoten, welcher fr eine nderung
     * vorbereitet wurde (er muss danach mit propagade wieder normal in die
     * Verwaltung bernommen werden)
     *
     * @param node der Knoten
     * @return der nderbare Knoten
     */
    private treeBucketNode getPreparedNode(nodeReference node) {
        treeBucketNode realNode = getInternalNodeByReference(node);

        if (realNode.numberOfNodeReferences() == 1) {
            return realNode;
        } else if (realNode.numberOfNodeReferences() > 1) {
            return splitNode(node);
        }

        /*
         * Referenzen: 0 der Knoten wird nicht genutzt, dieser Fall sollte
         * niemals eintreten
         */
        return null;
    }

    /**
     * setzt den possibleNextReferenceId-Zeiger auf den nchsten mglichen Wert
     *
     * @return der nchste Wert
     */
    private int increaseToNextPossibleReferenceId() {
        possibleNextReferenceId++;
        if (possibleNextReferenceId == 0) {
            possibleNextReferenceId = 1;
        }
        return possibleNextReferenceId;
    }

    /**
     * sorgt dafr, das ein Knoten aufgenommen werden kann, ohne das er mit
     * einem anderen verschmelzt wird
     *
     * @param node der Knoten
     * @return der angepasst (eindeutige) Knoten
     */
    private treeBucketNode makeNodeUnique(treeBucketNode node) {
        // wenn wir viele dieser Knoten nacheinander unique machen wollen,
        // dann mchte ich nicht immer bei 0 beginnen mssen
        node.setUniqueId(random.nextPositive());

        while (nodeExists(node)) {
            node.increaseUniqueId();
        }
        return node;
    }

    /**
     * vereint zwei Graphen miteinander, wobei targetNode den Graphen sourceNode
     * aufnimmt
     *
     * @param targetNode der Zielknoten
     * @param sourceNode der Quellknoten
     */
    private void mergeNode(treeBucketNode targetNode, treeBucketNode sourceNode) {
        // der Zielknoten erhlt nun die Eltern vom Quellknoten
        targetNode.addParents(sourceNode.getParents());
        targetNode.cleanParents(); // doppelte Eltern werden entfernt

        // die bisherigen Eltern des Quellknoten erhalten nun den Zielknoten
        // als Kind
        sourceNode.getParents().forEach((parent) -> {
            int sourceid = parent.findChild(sourceNode);
            parent.setChild(sourceid, targetNode);
        });

        // der Zielknoten nimmt nun die Referenzen des Quellknoten auf
        targetNode.addNodeReferences(sourceNode.getNodeReferences());

        // setzt alle alten Referenzen auf den neuen Knoten (in der Verwaltung)
        sourceNode.getNodeReferences().forEach((ref) -> {
            getNodeReferences().replace(ref.getId(), targetNode);
        });

        // die Kinder der beiden mssen auch verschmolzen werden
        if (targetNode.hasChilds()) {
            // der Graph ist noch nicht unten angekommen
            ArrayList<treeBucketNode> targetChilds = targetNode.getChilds();
            ArrayList<treeBucketNode> sourceChilds = sourceNode.getChilds();
            for (int i = 0; i < targetChilds.size(); i++) {
                if (targetChilds.get(i) != sourceChilds.get(i)) {
                    mergeNode(targetChilds.get(i), sourceChilds.get(i));
                } else {
                    // die beiden Kinder sind bereits verschmolzen, sodass nun
                    // doppelte Eltern entstehen und der Vater einmal entfernt
                    // werden kann
                    targetChilds.get(i).removeParent(sourceNode);
                }
            }
        }
        // der Quellknoten kann nun gelscht werden
        nodes.remove(sourceNode.getStoreId());
    }

    /**
     * prft, ob ein Knoten in dieser Form bereits existiert
     *
     * @param node der zu prfende Knoten
     * @return true = Knoten existiert bereits, false = sonst
     */
    private boolean nodeExists(treeBucketNode node) {
        return nodes.containsKey(node.hashCode());
    }

    /**
     * aktualisiert einen Knoten und den Elternpfad
     *
     * @param node der Knoten
     */
    private void propagadeNode(treeBucketNode node) {
        // nun wird der neue Hash berechnet
        node.resetUniqueId();
        node.rehash(); // damit werden auch die Kinder rehasht

        // wenn sich der Hash gendert hat, mssen wir handeln
        if (node.getStoreId() != node.hashCode()) {
            // entferne den Knoten aus der Verwaltung
            nodes.remove(node.getStoreId());

            // nun den Knoten wieder neu einfgen, wobei der Knoten eventuell
            // mit einem anderen verschmolzen wird
            node = addNodeSimple(node);

            // eventuell haben sich die Elternknoten auch gendert
            for (treeBucketNode parent : node.getParents()) {
                propagadeNode(parent);
            }
        }
    }

    /**
     * aktualisiert einen Knoten und den Elternpfad
     *
     * @param node der Knoten
     */
    private void propagadeNode(nodeReference node) {
        treeBucketNode tmp = getInternalNodeByReference(node);

        if (tmp == null) {
            // die Referenz existiert nicht
            return;
        }
        propagadeNode(tmp);
    }

    /**
     * entfernt einen interen Knoten (also den konkreten Knoten)
     *
     * @param node der Knoten
     */
    private void removeInternalNode(treeBucketNode node) {
        ArrayList<treeBucketNode> parents = node.getParents();
        node.disconnect();

        parents.forEach((parent) -> {
            propagadeNode(parent);
        });

        nodes.remove(node.getStoreId());
    }

    /**
     * entfernt eine Knotenreferenz
     *
     * @param node der Knoten
     */
    private void removeNodeReference(nodeReference node) {
        node.disconnect();
        getNodeReferences().remove(node.getId());
        possibleNextReferenceId = node.getId();
        node.setId(0); // quasi ein zurcksetzen der ID
    }

    /**
     * spaltet eine Kante ab und macht daraus einen eigenstndigen Arm diese
     * Operation bereitet eine nderung an einem Knoten vor, wobei immer ein
     * propagade() folgen muss, um den neuen Arm korrekt in die Verwaltung zu
     * integrieren
     *
     * @param node der Knoten
     * @return der neue Knoten
     */
    private treeBucketNode splitNode(nodeReference node) {
        // der reale Knoten wird nun ermittelt
        treeBucketNode realNode = getInternalNodeByReference(node);

        // wenn der referenzierte Knoten alleine in diesem Knoten ist,
        // muss er nicht aufgespalten werden
        if (realNode.getNodeReferences().size() <= 1) { // obwohl 0 nicht mglich ist
            // damit gab es auch keine weitere Anpassung
            return realNode;
        }

        // nun wird die Referenz aus dem Knoten entfernt
        realNode.removeNodeReference(node);

        // um Fehler zu vermeiden, setzen wir die Referenz vorbergehend auf
        // etwas Ungltiges
        getNodeReferences().replace(node.getId(), null);

        // jetzt muss ein neuer Knoten erzeugt werden (eine Kopie)
        // (aber ohne Kinder und Eltern)
        long nextId = getNextNodeId();
        treeBucketNode splittedNode = realNode.cloneNodeBase(nextId);
        splittedNode.addNodeReference(node);

        // der neue Knoten soll eindeutig sein, damit er beim Einfgen nicht
        // mit einem anderen vereint wird
        splittedNode = makeNodeUnique(splittedNode);
        addNodeSimple(splittedNode); // fgt den Knoten ein

        // die Knotenreferenz soll nun auf einen neuen realen Knoten zeigen
        getNodeReferences().replace(node.getId(), splittedNode);

        // der neue Knoten bekommt die Kinder des ursprnglichen Knotens
        // (darunter bleibt ja alles gleich)
        splittedNode.addChilds(realNode.getChilds());

        // nun muss der Pfad ber die Eltern bis zur Wurzel des Baums
        // aufgespalten werden
        if (node.hasParent()) {
            // mglicherweise gibt es noch Elternknoten des Baums
            nodeReference parent = node.getParent();

            // die Eltern werden ebenfalls in den neuen Baum berfhrt
            treeBucketNode realParent = getInternalNodeByReference(parent);

            int myChildPos = realParent.findChild(realNode);

            if (myChildPos == -1) {
                // das ist ein Problem
            }

            treeBucketNode splittedParent = splitNode(parent);
            splittedNode.addParent(splittedParent);

            // ich muss mich bei meinem Vater noch als Kind korrekt setzen
            splittedParent.setChild(myChildPos, splittedNode);
        }

        return splittedNode;
    }

    /**
     * aktualisiert eine Knotenreferenz
     *
     * @param id   die ID der Referenz
     * @param node der neue Zielknoten
     */
    private void updateNodeReference(int id, treeBucketNode node) {
        getNodeReferences().replace(id, node);
    }

    /**
     * aktualisiert eine Knotenreferenz
     *
     * @param nodeReference die Referenz
     * @param node          der neue Zielknoten
     */
    private void updateNodeReference(nodeReference nodeReference, treeBucketNode node) {
        this.getNodeReferences().replace(nodeReference.getId(), node);
    }

    /**
     * fgt eine Kante ein
     *
     * @param referenceA der Startknoten
     * @param referenceB der Zielknoten
     */
    public void addEdge(reference referenceA, reference referenceB) {
        nodeReference nodeA = (nodeReference) referenceA;
        nodeReference nodeB = (nodeReference) referenceB;
        treeBucketNode nA = splitNode(nodeA); // dieser Knoten wird sicher verndern
        treeBucketNode nB = getInternalNodeByReference(nodeB);

        if (nA == null) {
            throw new NullPointerException("der Knoten A existiert nicht");
        }

        if (nB == null) {
            throw new NullPointerException("der Knoten B existiert nicht");
        }

        if (nodeB.hasParent()) {
            throw new IllegalArgumentException("der Knoten hat bereits einen" + "Vater");
        }

        if (nodeA.getTree() == null || nodeB.getTree() == null) {
            throw new IllegalArgumentException("der Baum bentigt eine Wurzel");
        }

        if (!nodeA.getTree().equals(nodeB.getTree())) {
            throw new IllegalArgumentException("die Knoten gehren nicht zum" + "gleichen Baum");
        }

        if (nodeA.getTreeRoot().equals(nodeB)) {
            throw new IllegalArgumentException("es darf keine Kante zur Wurzel" + "gezogen werden");
        }

        // jetzt werden die zwei miteinander verbunden, dabei werden die
        // Referenzen und die realen Knoten verknpft
        nodeA.addChild(nodeB);
        nodeB.setParent(nodeA);
        nA.addEdgeTo(nB);

        // nodeA hat sich verndert
        propagadeNode(nodeA);
    }

    /**
     * fgt eine Kante ein
     *
     * @param edge die Definition der Kante
     */
    public void addEdge(edge edge) {
        addEdge(edge.getSource(), edge.getTarget());
    }

    /**
     * prft, ob ein Attribut in dieser Knotenreferenz existiert
     *
     * @param ref  die Referenz des Knotens
     * @param name der Name des Attributs
     * @return true = existiert, false = nicht
     */
    public boolean attributeExists(reference ref, String name) {
        nodeReference node = (nodeReference) ref;
        treeBucketNode tmp = getInternalNodeByReference(node);
        return tmp.attributeExists(name);
    }

    /**
     * ndert den Typbezeichner eines Knotens (der Knoten wir anschlieend
     * automatisch in der Verwaltung aktualisiert)
     *
     * @param ref     die Referenz auf den Knoten
     * @param newType der neue Bezeichner
     */
    public void changeNodeType(reference ref, String newType) {
        nodeReference node = (nodeReference) ref;
        // der Knoten muss eventuell aufgespalten werden
        treeBucketNode tmp = getInternalNodeByReference(node);
        if (tmp == null) {
            // fehlerhafter Knoten
            return;
        }
        if (newType.equals(tmp.getType())) {
            // der Name hat sich nicht gendert
            return;
        }

        treeBucketNode preparedNode = getPreparedNode(node);
        preparedNode.setType(newType);
        propagadeNode(preparedNode);
    }

    /**
     * fgt einen Knoten in einen Baum ein
     *
     * @param tree der Baum
     * @param name der Name des Knotens
     * @return die Referenz auf den Knoten
     */
    public reference createNode(tree tree, String name) {
        return createNode(getNextNodeId(), (internalTree) tree, name, "");
    }

    /**
     * fgt einen Knoten in einen Baum ein
     *
     * @param tree der Baum
     * @param name der Name des Knotens
     * @param type der Typ des Knotens
     * @return die Referenz auf den Knoten
     */
    public reference createNode(tree tree, String name, String type) {
        return createNode(getNextNodeId(), (internalTree) tree, name, type);
    }

    /**
     * erzeugt einen neuen Baum
     *
     * @param name der Name
     * @return der neue Baum
     */
    public tree createTree(String name) {
        internalTree tmp = new internalTree(name);
        tmp = addTree(tmp);
        return tmp;
    }

    /**
     * liefert den Wert eiens Attributs
     *
     * @param ref  die Referenz des Knotens
     * @param name der Name des Attributs
     * @return der Wert des Attributs oder null (wenn es nicht existiert)
     */
    public String getAttribute(reference ref, String name) {
        nodeReference node = (nodeReference) ref;
        treeBucketNode tmp = getInternalNodeByReference(node);
        if (tmp == null) {
            return null;
        }
        return tmp.getAttribute(name);
    }

    /**
     * liefert die Anzahl der real existierenden Knoten
     *
     * @return die Anzahl
     */
    public int getNumberOfInternalNodes() {
        return nodes.size();
    }

    /**
     * ermittelt einen Baum anhand seines Namens
     *
     * @param name der Name
     * @return der Baum
     */
    public tree getTreeByName(String name) {
        for (tree tmp : getTrees()) {
            if (name.equals(tmp.getName())) {
                return tmp;
            }
        }
        return null;
    }

    /**
     * setzt den Wurzelknoten des Baums
     *
     * @param root der Wurzelknoten
     */
    public void setTreeRoot(reference root) {
        internalTree tree = root.getTree();

        // wenn root nicht neu ist, muss auch nichts gemacht werden
        if (tree.getRoot() == root) {
            return;
        }

        // nun kann die neue Wurzel gesetzt werden
        tree.setRoot((nodeReference) root);
    }

    /**
     * liefert alle Bume des Behlters
     *
     * @return die Bume
     */
    public ArrayList<tree> getTrees() {
        return (ArrayList<tree>) (ArrayList<? extends tree>) trees;
    }

    /**
     * prft, ob die beiden Bume gleich sind (also auf die gleiche Wurzel
     * verweisen)
     *
     * @param treeA der erste Baum
     * @param treeB der zweite Baum
     * @return true = sind gleich, false = sind nicht gleich
     */
    public boolean isTreeEquivalentTo(tree treeA, tree treeB) {
        internalTree realA = (internalTree) treeA;
        internalTree realB = (internalTree) treeB;

        if (realA.equals(realB)) {
            return true;
        }

        if (realA.getRoot().equals(realB.getRoot())) {
            return true;
        }

        if (realA.getRoot() == null) {
            return false;
        }

        if (realB.getRoot() == null) {
            return false;
        }

        treeBucketNode nodeA = getInternalNodeByReference(realA.getRoot());
        treeBucketNode nodeB = getInternalNodeByReference(realB.getRoot());

        return nodeA.getId() == nodeB.getId();
    }

    /**
     * prft, ob die beiden Bume gleich sind (also auf die gleiche Wurzel
     * verweisen)
     *
     * @param nodeA ein Knoten des ersten Baums
     * @param nodeB ein Knoten des zweiten Baums
     * @return true = sind gleich, false = sind nicht gleich
     */
    public boolean isTreeEquivalentTo(reference nodeA, reference nodeB) {
        return isTreeEquivalentTo(nodeA.getTree(), nodeB.getTree());
    }

    /**
     * liefert eine Textdarstellung des Baumbehlters
     *
     * @return die Textdarstellung
     */
    public String print() {
        String tmp = "";
        tmp += "Knoten: " + nodes.size() + "\n";
        for (Map.Entry<Integer, treeBucketNode> nodeEntry : nodes.entrySet()) {
            tmp += "\n";
            tmp += nodeEntry.getValue().print();
        }

        return tmp;
    }

    /**
     * prft, ob eine Knotenreferenz bereits in der Verwaltung existiert
     *
     * @param node die zu prfende Referenz
     * @return true = vorhanden, false = nicht vorhanden
     */
    public boolean referenceExists(reference node) {
        if (node.getId() == 0) {
            return false;
        }
        return getNodeReferences().containsKey(node.getId());
    }

    /**
     * entfernt ein Attribut einer Referenz
     *
     * @param ref  die Referenz des Knotens
     * @param name der Name des Attributs
     */
    public void removeAttribute(reference ref, String name) {
        nodeReference node = (nodeReference) ref;
        // der Knoten muss eventuell aufgespalten werden
        treeBucketNode tmp = getInternalNodeByReference(node);
        if (tmp == null) {
            return;
        }
        if (!tmp.attributeExists(name)) {
            // das Attribut existiert nicht, also muss es nicht entfernt werden
            return;
        }

        treeBucketNode preparedNode = getPreparedNode(node);
        preparedNode.removeAttribute(name);
        propagadeNode(preparedNode);
    }

    /**
     * entfernt eine Kante
     *
     * @param refA der Startknoten
     * @param refB der Zielknoten
     */
    public void removeEdge(reference refA, reference refB) {
        nodeReference nodeA = (nodeReference) refA;
        nodeReference nodeB = (nodeReference) refB;
        // dazu muss die Kante aus den Referenzen entfernt werden
        nodeA.removeEdgeTo(nodeB);

        // der Knoten nodeA hat sich nun verndert
        propagadeNode(nodeA);
    }

    /**
     * entfernt eine Kante
     *
     * @param edge die Kante
     */
    public void removeEdge(edge edge) {
        removeEdge(edge.getSource(), edge.getTarget());
    }

    /**
     * entfernt eine Knotenreferenz sauber aus der Struktur/Verwaltung
     *
     * @param ref der Knoten
     */
    public void removeNode(reference ref) {
        nodeReference node = (nodeReference) ref;
        treeBucketNode realNode = getInternalNodeByReference(node);

        if (realNode.numberOfNodeReferences() == 1) {
            // der Knoten wird von niemandem mehr genutzt, also kann er
            // entfernt werden
            removeNodeReference(node);
            removeInternalNode(realNode);

            nodeReference parent = node.getParent();
            if (parent != null) {
                parent.removeEdgeTo(node);
                treeBucketNode parentNode = getInternalNodeByReference(parent);
                propagadeNode(parentNode);
            }
        } else if (realNode.numberOfNodeReferences() > 1) {
            // der Knoten wird noch von anderen Referenzen genutzt
            nodeReference parent = node.getParent();
            removeNodeReference(node);

            if (parent != null) {
                parent.removeEdgeTo(node);

                // es existierte ein Vater
                treeBucketNode parentNode = getInternalNodeByReference(parent);

                if (parentNode.numberOfNodeReferences() > 1) {
                    // der Vater bentigt einen eigenen Zweig, denn eine andere
                    // Referenz nutzt diesen Knoten weiterhin
                    treeBucketNode splittedParent = splitNode(parent);
                    propagadeNode(splittedParent); // TODO: stimmt das so?
                }
            }
        } else {
            // der Knoten ist bereits leer und diese Situation ist unlogisch
        }
    }

    /**
     * entfernt einen Knoten und dessen Untergraph
     *
     * @param node der Startknoten
     */
    public void removeSubtree(reference node) {
        // dazu wandern wir zunchst Rekursiv bis zu den Kindern und beginnen
        // dort mit dem Lschen
        node.getExistingChilds().forEach((child) -> {
            removeSubtree(child);
        });

        // wenn die Kinder dieses Knotens weg sind, dann knnen wir diesen
        // Knoten lschen (erst ist dann also ein Blatt)
        removeNode(node);
    }

    /**
     * entfernt einen kompletten Baum aus der Verwaltung
     *
     * @param tree der zu entfernende Baum
     */
    public void removeTree(tree tree) {
        internalTree realTree = (internalTree) tree;
        nodeReference root = realTree.getRoot();
        if (root != null) {
            // der Baum bestitzt eine Wurzel
            removeSubtree(root);
        }
        trees.remove(realTree);
    }

    /**
     * benennt einen Knoten um (der Knoten wir anschlieend automatisch in der
     * Verwaltung aktualisiert)
     *
     * @param ref     der Knoten
     * @param newName der neue Name
     */
    public void renameNode(reference ref, String newName) {
        nodeReference node = (nodeReference) ref;
        // der Knoten muss eventuell aufgespalten werden
        treeBucketNode tmp = getInternalNodeByReference(node);

        if (newName.equals(tmp.getLabel())) {
            // der Name hat sich nicht gendert
            return;
        }

        treeBucketNode preparedNode = getPreparedNode(node);
        preparedNode.setLabel(newName);
        propagadeNode(preparedNode);
    }

    /**
     * benennt einen Knoten um der Baum ist hierbei eigentlich nicht notwendig
     *
     * @param tree der Baum
     * @param node der Knoten
     * @param name der neue Name
     */
    public void renameNode(tree tree, reference node, String name) {
        if (node.getTree() != tree) {
            throw new IllegalArgumentException("die beiden Elemente gehren nicht zueinander");
        }
        renameNode(node, name);
    }

    /**
     * benennt einen Baum um
     *
     * @param tree der Baum
     * @param name der neue Name
     * @return true = Umbenennung war erfolgreich, false = sonst
     */
    public boolean renameTree(tree tree, String name) {
        internalTree realTree = (internalTree) tree;
        if (name == null) {
            return false;
        }

        if (realTree.getName().equals(name)) {
            return false;
        }

        // der Name eines Baums soll eindeutig sein
        if (getTreeByName(name) != null) {
            return false;
        }

        realTree.setName(name);
        return true;
    }

    /**
     * benennt einen Baum um
     *
     * @param node ein Knoten, welcher zum Baum gehrt
     * @param name der neue Name
     * @return true = Umbenennung war erfolgreich, false = sonst
     */
    public boolean renameTree(reference node, String name) {
        return renameTree(node.getTree(), name);
    }

    /**
     * benennt einen Baum um
     *
     * @param edge eine Kante des Baums
     * @param name der neue Name
     * @return true = Umbenennung war erfolgreich, false = sonst
     */
    public boolean renameTree(edge edge, String name) {
        nodeReference tmp = edge.getSource();
        if (tmp == null) {
            throw new IllegalArgumentException("die Kante besitzt keine Quelle");
        }

        return renameTree(tmp.getTree(), name);
    }

    /**
     * setzt den Wert eines Attributs (oder berschreibt ihn)
     *
     * @param ref   die Referenz des Knotens
     * @param name  der Name des Attributs
     * @param value der neue Wert
     */
    public void setAttribute(reference ref, String name, String value) {
        nodeReference node = (nodeReference) ref;
        // der Knoten muss eventuell aufgespalten werden
        treeBucketNode tmp = getInternalNodeByReference(node);
        if (tmp == null) {
            return;
        }
        if (tmp.attributeExists(name)) {
            if (tmp.getAttribute(name).equals(value)) {
                // der Wert existiert so bereits
                return;
            }
        }

        treeBucketNode preparedNode = getPreparedNode(node);
        preparedNode.setAttribute(name, value);
        propagadeNode(preparedNode);
    }

    /**
     * setzt den Wurzelknoten des Baums, wobei der Parameter internalTree
     * eigentlich nicht notwendig ist (dient nur der lesbarkeit)
     *
     * @param tree der Baum
     * @param root der Wurzelknoten
     */
    public void setTreeRoot(tree tree, reference root) {
        if (root.getTree() != tree) {
            throw new IllegalArgumentException("die beiden Elemente gehren nicht zueinander");
        }

        setTreeRoot(root);
    }

    /**
     * druckt eine Darstellung der realen Knoten, wobei nur deren interen
     * Knotennummern aufgelistet werden, mit Kindern
     *
     * @return die Textdarstellung der Struktur der realen Knoten
     */
    public String simplePrint() {
        String tmp = "";
        for (Map.Entry<Integer, treeBucketNode> nodeEntry : nodes.entrySet()) {
            treeBucketNode a = nodeEntry.getValue();
            List<String> collect = new ArrayList<>();
            a.getChilds().forEach((b) -> {
                collect.add(String.valueOf(b.getId()));
            });
            tmp += "{" + a.getId() + "[" + StringUtils.join(collect, ",") + "]}";
        }
        return tmp;
    }

    /**
     * bietet Zufallszahlen an
     */
    public static class random {

        private static Random rnd = null;

        /**
         * liefert eine Zahl zwischen 1 und Integer.Size-1
         *
         * @return die Zufallszahl
         */
        public static int nextPositive() {
            if (rnd == null) {
                rnd = new Random();
            }
            return 1 + rnd.nextInt(Integer.SIZE - 2);
        }

        /**
         * liefert eine Zahl ungleich 0
         *
         * @return die Zufallszahl
         */
        public static int nextNonZero() {
            if (rnd == null) {
                rnd = new Random();
            }
            int r = rnd.nextInt();
            if (r == 0) {
                r = 1;
            }
            return r;
        }

    }

    /**
     * @return the nodeReference
     */
    private Map<Integer, treeBucketNode> getNodeReferences() {
        return nodeReferences;
    }

}