//ShowTree Tree Visualization System //Copyright (C) 2009 Yuvi Masory // //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, version 3 only. // //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, write to the Free Software //Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. package logic; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Random; import logic.treeIterators.PostorderIterator; /** * Represents a Node in a tree. Has fields for all the values the various tree positioning * algorithms use. There is no explicit "Tree" class. A tree is referenced by its root Node. * * Please note that the children are supposed to be a set with a sequence defined on them. * In other words, if there cannot be "gaps" in the children's position. If there is just one child * it is inherently the "first" child, an only child cannot be reresented a s second child (i.e. the "right" one), * only the first (i.e., the "left" one). * It is up to the programmer to maintain this convention and not allow null elements in the children ArrayList */ public class Node{ private static final String[] numbers = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; private Node parent; private ArrayList<Node> children; private String label; //cannot be null private int height; //length of longest path to a leaf private int depth; //length of path to root private double xPos; //given by positioning algorithms, not the ultimate location on TreePane private double yPos; //given by positioning algorithms, not the ultimate location on TreePane private Rectangle graphicalRectangle; //ultimate location on TreePane, other than left-right translation // used by iterators private boolean visited; // used by WS79_1 and multiple functions using copied code private int status; // used by WS79_2 & W_BJL04 private double modifier; // used by W_BJL04 private Node thread; private Node ancestor; private double prelim; private double shift; private double change; private int number; public Node(boolean makeLabel) { children = new ArrayList<Node>(); if(makeLabel) { label = "<"; int labLen = new Random().nextInt(10); labLen++; for(int i = 0; i < labLen; i++) { label += numbers[i % numbers.length]; } label += ">"; } else { label = ""; } ancestor = this; height = -1; depth = -1; } /** * Makes a _sort of_ Penn Treebank format represenation of the tree rooted as this Node * There are three differences: * (1) Emtpy string labels are represented by @_EMPTYSTRING_@ * (2) Round parens are represented by @_LRB_@ and @_RRB_@ */ //IMPROVE this can be made prettier with PTB-style indentation and line separation, although IterateThroughTreeTestFile crucially assumes one tree per line public String getStringRepresentation() { initializeStatus(this); String output = ""; setStatus(0); Node current = this; while(current != null) { if(current.getStatus() == 0) { current.setStatus(1); //first visit if(current.isLeaf() && current.getParent() != null && current.getParent().degree() == 1) { output += " "; } else { output += "("; } String processedLabel = current.getLabel(); if(current.getLabel().equals("")) { processedLabel = "@_EMPTYSTRING_@"; } if(processedLabel.contains(new StringBuffer("(")) || processedLabel.contains(new StringBuffer(")"))) { processedLabel = processedLabel.replaceAll("\\(", "@_LRB_@"); processedLabel = processedLabel.replaceAll("\\)", "@_RRB_@"); } output += processedLabel; } else if(1 <= current.getStatus() && current.getStatus() <= current.degree()) { current.setStatus(current.getStatus() + 1); current = current.getChildren().get(current.getStatus() - 2); } else{ //backtracking if(current.isLeaf() && current.getParent() != null && current.getParent().degree() == 1) { output += " "; } else { output += ")"; } current = current.getParent(); } } return output; } private void initializeStatus(Node n) { n.setStatus(0); for(int i = 0; i < n.degree(); i++) { initializeStatus(n.getChildren().get(i)); } } /** * assigns the height value to every node in the tree * runs in theta(n) where n is the number of nodes in the caller's tree * assumed to be called on the root */ public void assignHeights() { assignHeightsHelper(this); } private int assignHeightsHelper(Node n) { if(n.getChildren().size() == 0) { n.setHeight(0); return 0; } int i = 0; for(Node child : n.getChildren()) { i = Math.max(i, assignHeightsHelper(child)); } n.setHeight(i + 1); return i + 1; } /** * assigns the depth value to every node in the caller's tree * runs in theta(n) where n is the number of nodes in the caller's tree * assumed to be called on the root */ public void assignDepths() { assignDepthsHelper(this, 0); } private void assignDepthsHelper(Node n, int curDepth) { n.setDepth(curDepth); for(Node child : n.getChildren()) { assignDepthsHelper(child, curDepth + 1); } } /** * determines whether the subtree rooted at this is binary */ public boolean isBinary() { PostorderIterator iter = new PostorderIterator(this); while(iter.hasNext()) { if(iter.next().degree() > 2) { return false; } } return true; } /** * @return true if the current Node has a sibling to the left */ public boolean hasLeftSibling() { if(parent == null) { return false; } else { ArrayList<Node> siblings = parent.getChildren(); if(siblings.get(0) == this) { return false; } else { return true; } } } /** * @return true if the current Node has a sibling to the right */ public boolean hasRightSibling() { if(parent == null) { return false; } else { ArrayList<Node> siblings = parent.getChildren(); if(siblings.get(siblings.size() - 1) == this) { return false; } else { return true; } } } /** * @return the current Node's left sibling, or null if there isn't one */ public Node getLeftSibling() { if(!hasLeftSibling()) { return null; } else { ArrayList<Node> siblings = parent.getChildren(); int myIndex = -1; for(int i = 0; i < siblings.size(); i++){ if(siblings.get(i) == this) { myIndex = i; break; } } return siblings.get(myIndex - 1); } } /** * @return the current Node's right sibling, or null if there isn't one */ public Node getRightSibling() { if(!hasRightSibling()) { return null; } else { ArrayList<Node> siblings = parent.getChildren(); int myIndex = -1; for(int i = 0; i < siblings.size(); i++){ if(siblings.get(i) == this) { myIndex = i; break; } } return siblings.get(myIndex + 1); } } /** * Does NOT use graphical rectangles, strictly x position. * @return the width of the tree, meaning the difference between the largest and smallest x values in the tree * */ public double getEmbeddedWidth() { PostorderIterator iter = new PostorderIterator(this); double minX = Double.MAX_VALUE; double maxX = Double.MIN_VALUE; Node cur = null; while(iter.hasNext()) { cur = iter.next(); double curX = cur.getX(); if(curX < minX) { minX = curX; } if(curX > maxX) { maxX = curX; } } return maxX - minX; } /** * Does NOT use graphical rectangles, strictly x position. * @return the sum of the distances between siblings in the tree. */ public double getEmbeddedSiblingSpacing() { PostorderIterator iter = new PostorderIterator(this); double spacingSum = 0; Node cur; while(iter.hasNext()) { cur = iter.next(); if(cur.hasRightSibling()) { spacingSum += (cur.getRightSibling().getX() - cur.getX()); } } return spacingSum; } /** * @return the degree of the current Node (number of children) */ public int degree() { return children.size(); } /** * @return true if the current Node has at least one child */ public boolean hasLeft() { return degree() > 0; } /** * @return true if the current Node has at least two children */ public boolean hasRight() { return degree() > 1; } /** * @return true if the the current Node has children, same as hasLeft() */ public boolean isInternal() { return degree() > 0; } /** * @return true if the current Node has no children */ public boolean isLeaf() { return degree() == 0; } /** * @return first child - must exist or NullPointerException results */ public Node getLeft() { return children.get(0); } /** * @return second child - must exist or NullPointerException results */ public Node getRight() { return children.get(1); } public ArrayList<Node> getChildren() { return children; } public void setChildren(ArrayList<Node> childs) { children = childs; } public double getX() { return xPos; } public double getY() { return yPos; } public void setX(double x) { xPos = x; } public void setY(double y) { yPos = y; } public Node getParent() { return parent; } public void setParent(Node par) { parent = par; } public String getLabel() { return label; } public void setLabel(String val) { label = val; } public int getStatus() { return status; } public void setStatus(int stat) { status = stat; } /** * warning: height may not be initialized */ public int getHeight() { return height; } public void setHeight(int h) { height = h; } public void setDepth(int dep) { depth = dep; } /** * warning: depth may not be initialized */ public int getDepth() { return depth; } public boolean isVisited() { return visited; } public void visit() { visited = true; } public void unVisit() { visited = false; } public Node getAncestor() { return ancestor; } public void setAncestor(Node n) { ancestor = n; } public Node getThread() { return thread; } public void setThread(Node n) { thread = n; } public double getPrelim() { return prelim; } public void setPrelim(double p) { prelim = p; } public void setModifier(double shiftAmount) { this.modifier = shiftAmount; } public double getModifier() { return modifier; } public void setGraphicalRectangle(Rectangle rect) { graphicalRectangle = rect; } public Rectangle getGraphicalRectangle() { return graphicalRectangle; } public void setShift(double shift) { this.shift = shift; } public double getShift() { return shift; } public void setChange(double change) { this.change = change; } public double getChange() { return change; } public void setNumber(int number) { this.number = number; } public int getNumber() { return number; } /** * Returns string representation of Node, a bracketed representation. * (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { return getStringRepresentation(); } }