/* * Copyright 2007-2013 * Licensed under GNU Lesser General Public License * * This file is part of EpochX * * EpochX is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * EpochX 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with EpochX. If not, see <http://www.gnu.org/licenses/>. * * The latest version is available from: http://www.epochx.org */ package org.epochx.monitor.tree; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import org.epochx.monitor.graph.GraphVertex; import org.epochx.refactoring.representation.TreeAble; import org.epochx.refactoring.representation.TreeNodeAble; /** * A <code>TreeNodeAble</code>. */ public class TreeNode implements TreeNodeAble, RootAble, Iterable<TreeNode> { //////////////////////////////////////////////////////////////////////////// // D A T A F I E L D S // //////////////////////////////////////////////////////////////////////////// private String name; private TreeNode parent; private TreeNode[] children; private TreeNodeAble nodeAbleInstance; //////////////////////////////////////////////////////////////////////////// // V I S U A L I Z I T I O N F I E L D S // //////////////////////////////////////////////////////////////////////////// private transient double angle; private transient double rightLimit; private transient double leftLimit; private transient boolean selected; private transient boolean highlighted; private transient Color color; private transient int diameter; private transient int x; private transient int y; //////////////////////////////////////////////////////////////////////////// // C O N S T R U C T O R S // //////////////////////////////////////////////////////////////////////////// /** * Constructs a <code>TreeNode</code>. */ public TreeNode() { this.name = ""; this.nodeAbleInstance = null; this.parent = null; this.children = new TreeNode[0]; this.angle = Tree.NO_ANGLE; this.rightLimit = Tree.NO_ANGLE; this.leftLimit = Tree.NO_ANGLE; this.selected = false; this.highlighted = false; this.color = Color.LIGHT_GRAY; this.diameter = 0; this.x = 0; this.y = 0; } /** * Constructs a <code>TreeNode</code>. * * @param nodeAble the object to set as the nodeAbleInstance. Must be * "nodeable". * * @throw ClassCastException if the given object is not "nodeable". * * @see #setNodeAbleInstance(Object) */ public TreeNode(Object nodeAble) throws ClassCastException { this(); setNodeAbleInstance(nodeAble); if (this.nodeAbleInstance != null) { this.name = this.nodeAbleInstance.getName(); this.children = createChildren(); } } /** * Constructs a <code>TreeNode</code>. * * @param root the object to set as the root. Must be "node-able". * @parem parent the parent of the nodeAbleInstance. * * @throw ClassCastException if the given object is not "node-able". * * @see #setNodeAbleInstance(Object) */ public TreeNode(Object nodeAble, TreeNode parent) throws ClassCastException { this(nodeAble); this.parent = parent; } //////////////////////////////////////////////////////////////////////////// // D A T A R E L A T E D M E T H O D S // //////////////////////////////////////////////////////////////////////////// /** * Returns the name. * * @return the name. */ public String getName() { return name; } /** * Sets the name. * * @param name the name to set. */ public void setName(String name) { this.name = name; } /** * Returns the nodeAbleInstance. * * @return the nodeAbleInstance. */ public TreeNodeAble getNodeAbleInstance() { return nodeAbleInstance; } /** * Sets the <code>TreeNodeAble</code> instance of this node according to the * specified object which must be "nodeable" ; otherwise a * <code>ClassCastException</code> is thrown. * * @param o the "nodeable" object to set as the <code>TreeNodeAble</code> * instance of this nodeAbleInstance. * @throws ClassCastException if the given object is not rootable. */ public void setNodeAbleInstance(Object o) throws ClassCastException { if (o instanceof TreeNodeAble) { this.nodeAbleInstance = (TreeNodeAble) o; } else if (o instanceof TreeAble) { setNodeAbleInstance(((TreeAble) o).getTree()); } else if (o instanceof GraphVertex) { setNodeAbleInstance(((GraphVertex) o).getIndividual()); } else if (o != null) { throw new ClassCastException("This object is not an instance of TreeNodeAble :" + o.toString()); } } /** * Returns the parent. * * @return the parent. */ public TreeNode getParent() { return parent; } /** * Sets the parent. * * @param parent the parent to set. */ public void setParent(TreeNode parent) { this.parent = parent; } /** * Returns the root of this node. * * @return the root of this node. */ public TreeNode getRoot() { if (parent == null) { return this; } else { return parent.getRoot(); } } /** * Returns the children. * * @return the children. */ public TreeNode[] getChildren() { return children; } /** * Sets the children. * * @param children the children to set. */ public void setChildren(TreeNode[] children) { this.children = children; } /** * Creates the children of this node with the <code>TreeNodeAble</code> * nodes given by the {@link TreeNodeAble#getChildren()} method. * * @return the <code>TreeNode</code> created. * @throws NullPointerException if the <code>TreeNodeAble</code> instance is * null. */ public TreeNode[] createChildren() throws NullPointerException { if (nodeAbleInstance == null) { throw new NullPointerException("The TreeNodeAble instance is null."); } TreeNodeAble[] childrenNodeAble = nodeAbleInstance.getChildren(); children = new TreeNode[childrenNodeAble.length]; for (int i = 0; i < children.length; i++) { children[i] = new TreeNode(childrenNodeAble[i], this); children[i].createChildren(); } return children; } /** * Returns an <code>ArrayList</code> of this node's ancestors. * * @return the list of this node's ancestors from this * node to the root. */ public TreeNode[] ancestors() { TreeNode[] res = new TreeNode[level()]; TreeNode n = getParent(); int i = 0; while (n != null) { res[i++] = n; n = n.getParent(); } return res; } /** * Returns an <code>ArrayList</code> of this node's progenies in * in {@link Tree#PRE_ORDER preorder}. * * @return the list of this node's progenies. * * @see #getNodes(int); * @see Tree#PRE_ORDER */ public ArrayList<TreeNode> descendants() { return descendants(Tree.PRE_ORDER); } /** * Returns an <code>ArrayList</code> of this node's progenies * according to the specified order. * * @return the list of this node's progenies according to the * specified * order. * @throw IllegalArgumentException if the specified order is not among : * <ul> * <li>{@link Tree#POST_ORDER} * <li>{@link Tree#PRE_ORDER} * <ul> */ public ArrayList<TreeNode> descendants(int iteration) throws IllegalArgumentException { ArrayList<TreeNode> nodes = new ArrayList<TreeNode>(size()); switch (iteration) { case Tree.POST_ORDER: nodes.add(this); for (TreeNode child: children) { nodes.addAll(child.descendants(Tree.POST_ORDER)); } break; case Tree.PRE_ORDER: for (TreeNode child: children) { nodes.addAll(child.descendants(Tree.PRE_ORDER)); } nodes.add(this); break; default: throw new IllegalArgumentException("Unknown order."); } return nodes; } /** * The <code>Iterable</code> implemented method ; Returns an * <code>Iterator</code> visiting the subtree's nodes in * {@link Tree#POST_ORDER post-order}. * * @return the iterator of this tree's nodes. * @see #descendants() */ public Iterator<TreeNode> iterator() { return descendants().iterator(); } /** * Returns <code>true</code> is this node has no child nodes. * * @return <code>true</code> is this node has no child nodes; * otherwise <code>false</code>. */ public boolean isLeaf() { return children.length == 0; } /** * Returns the depth of this node. * * @return the depth of this node. */ public int depth() { if (isLeaf()) { return 0; } int maximum = 0; for (TreeNode child: children) { maximum = Math.max(maximum, child.depth()); } return maximum + 1; } /** * Returns the level of this node. * * @return the level of this node (in relation to the derivation * tree). */ public int level() { TreeNode root = parent; int level = 0; while (root != null) { root = root.parent; level++; } return level; } /** * Returns the total number of nodes of the (sub)tree represented by this * node. * * @return the total number of nodes of the (sub)tree represented by this * node. */ public int size() { int size = 1; for (TreeNode child: children) { size += child.size(); } return size; } /** * Returns the specified node in the (sub-)tree represented by * this * node. Nodes are indexed from left to right in a top-down * fashion. * * @param index the index of the node to be found. * * @return the specified node in the (sub-)tree represented by * this node. * @throws IndexOutOfBoundsException if the index is out of range. */ public TreeNode get(int index) throws IndexOutOfBoundsException { int current = 1; LinkedList<TreeNode> nodes = new LinkedList<TreeNode>(); nodes.add(this); while (!nodes.isEmpty()) { TreeNode node = nodes.removeFirst(); if (index == current) { return node; } else { nodes.addAll(0, Arrays.asList(node.children)); } current++; } throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); } /** * Searches for the first occurence of the given object, testing * for equality using the equals method. * * @param o the object whose index in this (sub-)tree is to be found. * * @return the index of the first occurrence of the argument in this * (sub-)tree; returns -1 if the object is not found. * @see #equals(Object) */ public int indexOf(Object o) { return descendants().indexOf(o); } /** * Checks the subsumption of the specified argument in this node according * to the nodes' names. * <p> * Note : This relation is <b>non- symmetric</b>. * </p> * * @param n the node whose subsumption is to be check. * @param onlySelected if true, check the subsumption only for selected * children. * @return true if the given is subsumed by this node ; false, otherwise. */ public boolean subsumes(TreeNode n, boolean onlySelected) { if (n == null) { return false; } if ((!onlySelected || n.isSelected()) && n.getName() != this.getName()) { return false; } ArrayList<TreeNode> thisChildren = new ArrayList<TreeNode>(Arrays.asList(this.children)); for (TreeNode child: n.getChildren()) { boolean found = onlySelected && !child.isSelected(); int i = 0; while (!found && i < thisChildren.size()) { TreeNode thisChild = thisChildren.get(i); if (thisChild.subsumes(child, onlySelected)) { thisChildren.remove(i); found = true; } else { i++; } } if (!found) { return false; } } return true; } /** * Returns an array of nodes in this node's descendance which subsume the * given node. * <p> * All the nodes <code>node</code> in this node's descendants such that * <code>node.subsumes(n, onlySelected) == true</code> * </p> * * @param n the node whose subsumers is to be found. * @param onlySelected if true, check the subsumption only for selected * children. * * @return an array of nodes in this node's descedance which subsume the * given node. The array can be empty. * * @see #subsumes(TreeNode, boolean) */ public TreeNode[] findSubsumers(TreeNode n, boolean onlySelected) { ArrayList<TreeNode> list = new ArrayList<TreeNode>(); for (TreeNode node: descendants()) { if (node.subsumes(n, onlySelected)) { list.add(node); } } TreeNode[] res = new TreeNode[list.size()]; list.toArray(res); return res; } /** * Sets this node'descendance selected as the given node. * * @param n the node to select as. * * @throws IllegalArgumentException if the given node is not subsumed by * this node. * * @see #subsumes(TreeNode, boolean) */ public void setSelectedAs(TreeNode n) throws IllegalArgumentException { if (!subsumes(n, true)) { throw new IllegalArgumentException("The given node is not subsumed."); } if (n.isSelected()) { this.setSelected(true, false); } ArrayList<TreeNode> thisChildren = new ArrayList<TreeNode>(Arrays.asList(this.children)); for (TreeNode child: n.getChildren()) { for (int i = 0; i < thisChildren.size(); i++) { TreeNode thisChild = thisChildren.get(i); if (thisChild.subsumes(child, true)) { thisChild.setSelectedAs(child); thisChildren.remove(i); } } } } /** * Checks if this node is an ancestor of the given node. * * @param node the node whose ancestry is to be checked. * @return true if this node is an ancestor of the given node, false * otherwise. */ public boolean isAncestorOf(TreeNode node) { TreeNode n = node; while (n != null) { if (n == this) { return true; } else { n = n.getParent(); } } return false; } /** * Checks if this node is a descendant of the given node. * * @param node the node whose descendance is to be checked. * @return true if this node is a descendant of the given node, false * otherwise. */ public boolean isDescendantOf(TreeNode node) { TreeNode n = this; while (n != null) { if (n == node) { return true; } else { n = n.getParent(); } } return false; } /** * Returns the common ancestor of the given nodes. * * @param node1 the node 1. * @param node2 the node 2. * @return the common ancestor of the given nodes, if they have, null * otherwise. */ public static TreeNode commonAncestor(TreeNode node1, TreeNode node2) { if (node1 == null || node2 == null) { return null; } else if (node1 == node2) { return node1; } else if (node1.isAncestorOf(node2)) { return node1; } else if (node2.isAncestorOf(node1)) { return node2; } else if (node1.getRoot() == node2.getRoot()) { if (node1.level() > node2.level()) { return commonAncestor(node1.getParent(), node2); } else { return commonAncestor(node1, node2.getParent()); } } else { throw new IllegalArgumentException("Not the same tree."); } } @Override public boolean equals(Object o) { if (o instanceof TreeNode) { TreeNode n = (TreeNode) o; if (!getName().equals(n.getName())) { return false; } if (parent != null && n.getParent() != null) { if (!parent.getName().equals(n.getParent().getName())) { return false; } } if (children.length != n.getChildren().length) { return false; } for (int i = 0; i < children.length; i++) { if (!children[i].equals(n.getChildren()[i])) { return false; } } return true; } else if (o instanceof TreeAble) { return equals(((TreeAble) o).getTree()); } else { return System.identityHashCode(o) == System.identityHashCode(nodeAbleInstance); } } //////////////////////////////////////////////////////////////////////////// // V I S U A L I Z A T I O N M E T H O D S // //////////////////////////////////////////////////////////////////////////// /** * Returns the angle. * * @return the angle. */ public double getAngle() { return angle; } /** * Sets the angle. * * @param angle the angle to set. */ public void setAngle(double angle) { this.angle = angle; } /** * Returns the rightLimit. * * @return the rightLimit. */ public double getRightLimit() { return rightLimit; } /** * Sets the rightLimit. * * @param rightLimit the rightLimit to set. */ public void setRightLimit(double rightLimit) { this.rightLimit = rightLimit; } /** * Returns the leftLimit. * * @return the leftLimit. */ public double getLeftLimit() { return leftLimit; } /** * Sets the leftLimit. * * @param leftLimit the leftLimit to set. */ public void setLeftLimit(double leftLimit) { this.leftLimit = leftLimit; } /** * Returns the selected. * * @return the selected. */ public boolean isSelected() { return selected; } /** * Sets the selected. * * @param b the selected to set. */ public void setSelected(boolean b, boolean recursively) { this.selected = b; if (recursively) { for (TreeNode child: children) { child.setSelected(b, recursively); } } } /** * Returns the highlighted. * * @return the highlighted. */ public boolean isHighlighted() { return highlighted; } /** * Sets the highlighted. * * @param b the highlighted to set. */ public void setHighlighted(boolean b, boolean recursively) { this.highlighted = b; if (recursively) { for (TreeNode child: children) { child.setHighlighted(b, recursively); } } } /** * Returns the color. * * @return the color. */ public Color getColor() { return color; } /** * Sets the color. * * @param color the color to set. */ public void setColor(Color color, boolean recursively) { this.color = color; if (recursively) { for (TreeNode child: children) { child.setColor(color, recursively); } } } /** * Returns the diameter. * * @return the diameter. */ public int getDiameter() { return diameter; } /** * Sets the diameter. * * @param diameter the diameter to set. */ public void setDiameter(int diameter, boolean recursively) { this.diameter = diameter; if (recursively) { for (TreeNode child: children) { child.setDiameter(diameter, recursively); } } } //////////////////////////////////////////////////////////////////////////// // L O C A T I O N R E L A T E D M E T H O D S // //////////////////////////////////////////////////////////////////////////// /** * Returns the X coordinate. * * @return the X coordinate. */ public int getX() { return x; } /** * Sets the X coordinate. * * @param x the X coordinate to set. */ public void setX(int x) { this.x = x; } /** * Returns the Y coordinate. * * @return the Y coordinate. */ public int getY() { return y; } /** * Sets the Y coordinate. * * @param y the Y coordinate to set. */ public void setY(int y) { this.y = y; } /** * Returns the centre location of this node. * * @return the centre location of this node. */ public Point getCentre() { return new Point(x, y); } /** * Sets the centre location of this node. * * @param x the X coordinate. * @param y the Y coordinate. */ public void setCentre(int x, int y) { this.x = x; this.y = y; } /** * Returns the bounds of this node. * * @return the bounds of this node. */ public Rectangle getBounds() { return new Rectangle(x - diameter / 2, y - diameter / 2, diameter, diameter); } /** * Checks if the specified point is inside this node's bounds. * * @param p the specicied point whose location is to be check. * @return true if the specified point is inside this node's bounds; false * otherwise. */ public boolean contains(Point p) { Rectangle bounds = getBounds(); return bounds.contains(p); } //////////////////////////////////////////////////////////////////////////// // O V E R R I D I N G M E T H O D S // //////////////////////////////////////////////////////////////////////////// @Override public TreeNode clone() { try { TreeNode clone = (TreeNode) super.clone(); clone.name = name; clone.children = new TreeNode[children.length]; clone.parent = null; clone.nodeAbleInstance = null; for (int i = 0; i < children.length; i++) { clone.children[i] = children[i].clone(); } return clone; } catch (CloneNotSupportedException e) { throw new InternalError(e.getMessage()); } } @Override public String toString() { String res = getClass().getSimpleName(); res += "@" + String.valueOf(System.identityHashCode(this)); res += "(" + getName() + ")"; return res; } }