/******************************************************************************* * Copyright (c) 2005, 2016 The Chisel Group and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: Ian Bull (The Chisel Group) - initial API and implementation * Mateusz Matela - "Tree Views for Zest" contribution, Google Summer of Code 2009 * Matthias Wienand (itemis AG) - refactorings * ******************************************************************************/ package org.eclipse.gef.layout.algorithms; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.eclipse.gef.graph.Node; import org.eclipse.gef.layout.LayoutContext; /** * A helper class for layout algorithms that are based on tree structure. It * keeps track of changes in observed layout context and stores current * information about the tree structure - children of each node and several * other parameters. * * @author Ian Bull * @author Mateusz Matela * @author mwienand */ public class TreeLayoutHelper { /** * <code>TreeLayoutObserver</code> uses instance of this class to create * instances of {@link TreeNode}. It may be extended and passed to * <code>TreeLayoutObserver</code>'s constructor in order to build a tree * structure made of <code>TreeNode</code>'s subclasses. */ public static class TreeNodeFactory { /** * Creates a new {@link TreeNode} for the given {@link Node} and * {@link TreeLayoutHelper}. * * @param nodeLayout * The {@link Node} that is wrapped. * @param observer * The {@link TreeLayoutHelper} that initiated the creation. * @return The new {@link TreeNode}. */ public TreeNode createTreeNode(Node nodeLayout, TreeLayoutHelper observer) { return new TreeNode(nodeLayout, observer); } } /** * Represents a node in a tree structure and stores all information related * to it. May be subclassed if additional data and behavior is necessary. */ public static class TreeNode { /** * The wrapped {@link Node}. */ final protected Node node; /** * The {@link TreeLayoutHelper} that controls this {@link TreeNode}. */ final protected TreeLayoutHelper owner; /** * The height of the node. */ protected int height = 0; /** * The depth of the node. */ protected int depth = -1; /** * The number of leaves. */ protected int numOfLeaves = 0; /** * The number of descendants. */ protected int numOfDescendants = 0; /** * The order of this node. */ protected int order = 0; /** * The children of this node. */ protected final List<TreeNode> children = new ArrayList<>(); /** * The parent of this node. */ protected TreeNode parent; /** * <code>true</code> if this node is the first child, otherwise * <code>false</code>. */ protected boolean firstChild = false; /** * <code>true</code> if this node is the last child, otherwise * <code>false</code>. */ protected boolean lastChild = false; /** * * @return node layout related to this tree node (null for * {@link TreeLayoutHelper#getSuperRoot() Super Root}) */ public Node getNode() { return node; } /** * * @return <code>TreeLayoutObserver</code> owning this tree node */ public TreeLayoutHelper getOwner() { return owner; } /** * * @return height of this node in the tree (the longest distance to a * leaf, 0 for a leaf itself) */ public int getHeight() { return height; } /** * * @return depth of this node in the tree (distance from root, 0 for a * root and -1 for {@link TreeLayoutHelper#getSuperRoot() Super * Root} */ public int getDepth() { return depth; } /** * * @return number of all leaves descending from this node (1 for a leaf * itself) */ public int getNumOfLeaves() { return numOfLeaves; } /** * * @return total number of descendants of this node (0 for leafs) */ public int getNumOfDescendants() { return numOfDescendants; } /** * Returns order in which nodes are visited during Deep First Search. * Children are visited in the same order as they were added to their * layout context, unless {@link TreeLayoutHelper#computeTree(Node[])} * was called after the nodes were added. In that case the order is * determined by order of nodes returned by * {@link Node#getAllSuccessorNodes()}. Leaves are assigned successive * numbers starting from 0, other nodes have order equal to the smallest * order of their children. * * @return order of this node */ public int getOrder() { return order; } /** * * @return an unmodifiable list of this node's children */ public List<TreeNode> getChildren() { return Collections.unmodifiableList(children); } /** * * @return this node's parent */ public TreeNode getParent() { return parent; } /** * * @return true if this node is the first child of its parent (has the * smallest order) */ public boolean isFirstChild() { return firstChild; } /** * * @return true if this node is the last child of its parent (has the * highest order) */ public boolean isLastChild() { return lastChild; } /** * Creates a tree node related to given layout node * * @param node * the layout node * @param owner * <code>TreeLayoutObserver</code> owning created node */ protected TreeNode(Node node, TreeLayoutHelper owner) { this.node = node; this.owner = owner; } /** * Adds given node to the list of this node's children and set its * parent to this node. * * @param child * node to add */ protected void addChild(TreeNode child) { if (child == this) return; children.add(child); child.parent = this; } /** * Performs a DFS on the tree structure and calculates all parameters of * its nodes. Should be called on {@link TreeLayoutHelper#getSuperRoot() * Super Root}. Uses recurrence to go through all the nodes. */ protected void precomputeTree() { if (children.isEmpty()) { height = 0; numOfLeaves = 1; numOfDescendants = 0; } else { height = 0; numOfLeaves = 0; numOfDescendants = 0; for (ListIterator<TreeNode> iterator = children .listIterator(); iterator.hasNext();) { TreeNode child = iterator.next(); child.depth = this.depth + 1; child.order = this.order + this.numOfLeaves; child.precomputeTree(); child.firstChild = (this.numOfLeaves == 0); child.lastChild = !iterator.hasNext(); this.height = Math.max(this.height, child.height + 1); this.numOfLeaves += child.numOfLeaves; this.numOfDescendants += child.numOfDescendants + 1; } } } /** * Finds a node that is the best parent for this node. Add this node as * a child of the found node. */ protected void findNewParent() { if (parent != null) parent.children.remove(this); Node[] predecessingNodes = node.getAllPredecessorNodes() .toArray(new Node[] {}); parent = null; for (int i = 0; i < predecessingNodes.length; i++) { TreeNode potentialParent = owner.layoutToTree .get(predecessingNodes[i]); if (!children.contains(potentialParent) && isBetterParent(potentialParent)) parent = potentialParent; } if (parent == null) parent = owner.superRoot; parent.addChild(this); } /** * Checks if a potential parent would be better for this node than its * current parent. A better parent has smaller depth (with exception to * {@link TreeLayoutHelper#getSuperRoot() Super Root}, which has depth * equal to -1 but is never a better parent than any other node). * * @param potentialParent * potential parent to check * @return true if potentialParent can be a parent of this node and is * better than its current parent */ protected boolean isBetterParent(TreeNode potentialParent) { if (potentialParent == null) return false; if (this.parent == null && !this.isAncestorOf(potentialParent)) return true; if (potentialParent.depth <= this.depth && potentialParent.depth != -1) return true; if (this.parent != null && this.parent.depth == -1 && potentialParent.depth >= 0 && !this.isAncestorOf(potentialParent)) return true; return false; } /** * @param descendant * The {@link TreeNode} in question. * @return true if this node is an ancestor if given descendant node */ public boolean isAncestorOf(TreeNode descendant) { while (descendant.depth > this.depth && descendant != descendant.parent) { descendant = descendant.parent; } return descendant == this; } } /** * A superclass for listeners that can be added to this observer to get * notification whenever the tree structure changes. */ public static class TreeListener { /** * Called when new node is added to the tree structure. The new node * will not have any connections, so it will be a child of * {@link TreeLayoutHelper#getSuperRoot() Super Root} * * @param newNode * the added node */ public void nodeAdded(TreeNode newNode) { defaultHandle(newNode); } /** * Called when a node is removed from the tree structure. The given node * no longer exists in the tree at the moment of call. * * @param removedNode * the removed node */ public void nodeRemoved(TreeNode removedNode) { defaultHandle(removedNode); } /** * Called when a node changes its parent. * * @param node * node that changes its parent * @param previousParent * previous parent of the node */ public void parentChanged(TreeNode node, TreeNode previousParent) { defaultHandle(node); } /** * A convenience method that can be overridden if a listener reacts the * same way to all events. By default it's called in every event handler * and does nothing. * * @param changedNode * the node that has changed */ protected void defaultHandle(TreeNode changedNode) { } } private final HashMap<Object, TreeNode> layoutToTree = new HashMap<>(); private final TreeNodeFactory factory; private TreeNode superRoot; /** * Constructs a new {@link TreeLayoutHelper} for observing the given * {@link LayoutContext}. The given {@link TreeNodeFactory} will be used for * the construction of {@link TreeNode}s. If no factory is supplied, the * {@link TreeNodeFactory} will be used. * * @param nodeFactory * The {@link TreeNodeFactory} to use. */ public TreeLayoutHelper(TreeNodeFactory nodeFactory) { if (nodeFactory == null) this.factory = new TreeNodeFactory(); else this.factory = nodeFactory; } /** * Recomputes all the information about the tree structure (the same effect * as creating new <code>TreeLayoutObserver</code>). * * @param nodes * nodes */ public void computeTree(Node[] nodes) { superRoot = factory.createTreeNode(null, this); layoutToTree.put(null, superRoot); createTrees(nodes); } /** * Returns Super Root, that is an artificial node being a common parent for * all nodes in observed tree structure. * * @return Super Root */ protected TreeNode getSuperRoot() { return superRoot; } /** * Returns a {@link TreeNode} related to given node layout. If such a * <code>TreeNode</code> doesn't exist, it's created. * * @param node * The {@link Node} for which to return the corresponding * {@link TreeNode}. * @return The already existing {@link TreeNode} related to the given * {@link Node} or a newly created one in case there was no related * {@link TreeNode} before. */ protected TreeNode getTreeNode(Node node) { TreeNode treeNode = layoutToTree.get(node); if (treeNode == null) { treeNode = factory.createTreeNode(node, this); layoutToTree.put(node, treeNode); } return treeNode; } /** * Builds a tree structure using BFS method. Created trees are children of * {@link #superRoot}. * * @param nodes */ private void createTrees(Node[] nodes) { HashSet<Node> alreadyVisited = new HashSet<>(); LinkedList<Object[]> nodesToAdd = new LinkedList<>(); for (int i = 0; i < nodes.length; i++) { Node root = findRoot(nodes[i], alreadyVisited); if (root != null) { alreadyVisited.add(root); nodesToAdd.addLast(new Object[] { root, superRoot }); } } while (!nodesToAdd.isEmpty()) { Object[] dequeued = nodesToAdd.removeFirst(); TreeNode currentNode = factory.createTreeNode((Node) dequeued[0], this); layoutToTree.put(dequeued[0], currentNode); TreeNode currentRoot = (TreeNode) dequeued[1]; currentRoot.addChild(currentNode); Node[] children = currentNode.node.getAllSuccessorNodes() .toArray(new Node[] {}); for (int i = 0; i < children.length; i++) { if (!alreadyVisited.contains(children[i])) { alreadyVisited.add(children[i]); nodesToAdd .addLast(new Object[] { children[i], currentNode }); } } } superRoot.precomputeTree(); } /** * Searches for a root of a tree containing given node by continuously * grabbing a predecessor of current node. If it reaches an node that exists * in alreadyVisited set, it returns null. If it detects a cycle, it returns * the first found node of that cycle. If it reaches a node that has no * predecessors, it returns that node. * * @param nodeLayout * starting node * @param alreadyVisited * set of nodes that can't lay on path to the root (if one does, * method stops and returns null). * @return */ private Node findRoot(Node nodeLayout, Set<Node> alreadyVisited) { HashSet<Node> alreadyVisitedRoot = new HashSet<>(); while (true) { if (alreadyVisited.contains(nodeLayout)) return null; if (alreadyVisitedRoot.contains(nodeLayout)) return nodeLayout; alreadyVisitedRoot.add(nodeLayout); Node[] predecessingNodes = nodeLayout.getAllPredecessorNodes() .toArray(new Node[] {}); if (predecessingNodes.length > 0) { nodeLayout = predecessingNodes[0]; } else { return nodeLayout; } } } }