/* $Id: ClassdiagramNode.java 17863 2010-01-12 20:07:22Z linus $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * 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: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2008 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.uml.diagram.static_structure.layout; import java.awt.Dimension; import java.awt.Point; import java.util.ArrayList; import java.util.List; import org.argouml.uml.diagram.layout.LayoutedNode; import org.argouml.uml.diagram.static_structure.ui.FigComment; import org.argouml.uml.diagram.static_structure.ui.FigInterface; import org.argouml.uml.diagram.static_structure.ui.FigPackage; import org.tigris.gef.presentation.Fig; import org.tigris.gef.presentation.FigNode; /** * This class represents a node in the classdiagram (a class, interface or * package). * <p> * * Things a node has to know: * <ul> * <li>Up- and downlinks for positioning in the hierarchy * <li>Weight of this node. This weight has to be strongly influenced by the * parent-nodes, because otherwise the order of nodes in the current row will * not be compatible with the order of the nodes in the row above. * </ul> */ class ClassdiagramNode implements LayoutedNode, Comparable { /** * Constant to be used as an initializer when this node is not placed at an * column. */ public static final int NOCOLUMN = -1; /** * Constant to be used as an initializer when this node has no rank assigned * yet. */ public static final int NORANK = -1; /** * Constant to be used as an initializer when this node has no weight. */ public static final int NOWEIGHT = -1; /** * The current column of this node. */ private int column = NOCOLUMN; /** * List of the nodes that contain the figures, which * are sources of edges with the figure of this node as destination. */ private List<ClassdiagramNode> downlinks = new ArrayList<ClassdiagramNode>(); /** * Offset used for edges, which have this node as the "upper" node. */ private int edgeOffset = 0; /** * The Fig that this ClassdiagramNode represents during the layout process. */ private FigNode figure = null; /** * The preferred X coordinate for the node. Hint only. May not be used. */ private int placementHint = -1; /** * The current rank (i.e. row number) of this node. */ private int rank = NORANK; /** * List of nodes that contain the figures, which are destinations of edges * with the figure of this node as source. */ private List<ClassdiagramNode> uplinks = new ArrayList<ClassdiagramNode>(); /** * The 'weight' of this node. This is a computed * attribute that is used during the horizontal placement process. It's * based on the position of the 'uplinked' objects. The actual purpose is to * minimize the number of link crossings in the diagram. Since we don't * compute the actual number of link crossings, we look where our uplinked * objects are, and then try to place our object in a way, that we can * expect to have a minimal number of crossings. */ private float weight = NOWEIGHT; private static final float UPLINK_FACTOR = 5; /** * Construct a new ClassdiagramNode representing the given Fig. * * @param f * represents the figure in the diagram, that peers this layout * node. */ public ClassdiagramNode(FigNode f) { setFigure(f); } /** * Add a new downlinked node to this node. * * @param newDownlink * The node to be added with a dowlink. */ public void addDownlink(ClassdiagramNode newDownlink) { downlinks.add(newDownlink); } /** * Add a constant to the rank of this node. * * @param n * The value to add. */ public void addRank(int n) { setRank(n + getRank()); } /** * Add an uplink to this node. * * @param newUplink * represents the new uplinks. */ public void addUplink(ClassdiagramNode newUplink) { uplinks.add(newUplink); } /** * Calculate the weight of this node. The function distinguishes between * note-nodes and standard-nodes, because a note should be positioned to the * right of its first related node, if it exists. Therefor the weight is a * function of the weight of the related node. For standard-nodes the weight * is a function of up-/downlinks, column and uplink factor. * * @return The weight of this node. */ public float calculateWeight() { weight = 0; for (ClassdiagramNode node : uplinks) { weight = Math.max(weight, node.getWeight() * UPLINK_FACTOR * (1 + 1 / Math.max(1, node.getColumn() + UPLINK_FACTOR))); } weight += getSubtreeWeight() + (1 / Math.max(1, getColumn() + UPLINK_FACTOR)); return weight; } /** * The "natural order" for ClassdiagramNodes is defined by the following * order. * <ul> * <li>First standalone, then linked nodes * <li>First Packages, then Interfaces/Classes/Notes * <li>increasing rank (rownumber) * <li>decreasing weight * <li>name of model object * <li>increasing hashcode (for uniqueness) * </ul> * * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object arg0) { ClassdiagramNode node = (ClassdiagramNode) arg0; int result = 0; result = Boolean.valueOf(node.isStandalone()).compareTo( Boolean.valueOf(isStandalone())); if (result == 0) { result = this.getTypeOrderNumer() - node.getTypeOrderNumer(); } if (result == 0) { result = this.getRank() - node.getRank(); } if (result == 0) { result = (int) Math.signum(node.getWeight() - this.getWeight()); } if (result == 0) { result = String.valueOf(this.getFigure().getOwner()).compareTo( String.valueOf(node.getFigure().getOwner())); } if (result == 0) { result = node.hashCode() - this.hashCode(); } //LOG.debug(result + " node1: " + this + ", node2 " + node); return result; } /** * @return The column of this node. */ public int getColumn() { return column; } /** * @return The downlinks of this node. */ public List<ClassdiagramNode> getDownNodes() { return downlinks; } /** * Get the offset which shall be used for edges with this node as parent. * * @return The offset */ public int getEdgeOffset() { return edgeOffset; } /** * Get the underlying figure of this node. * * @return The figure. */ public FigNode getFigure() { return figure; } /** * Get the level in the inheritance hierarchy for this node. * * @return The level. */ public int getLevel() { int result = 0; for (ClassdiagramNode node : uplinks) { result = (node == this) ? result : Math.max( node.getLevel() + 1, result); } return result; } /** * Get the location of the underlying figure in the diagram. * * @return The location. */ public Point getLocation() { return getFigure().getLocation(); } /** * Get the current placement hint (X coordinate in the row). * * @return The placement hint for this node. */ public int getPlacementHint() { return placementHint; } /** * @return The rank for this node. */ public int getRank() { return rank == NORANK ? getLevel() : rank; } /** * Return the size of the figure associated with this * layout node. * * @return The size of the associated figure. */ public Dimension getSize() { return getFigure().getSize(); } /** * Get the weight of the subtree defined by this node. Impact on weight is * decreasing with increasing hierarchical distance * * @return The weight of the subtree. */ private float getSubtreeWeight() { float w = 1; for (ClassdiagramNode node : downlinks) { w += node.getSubtreeWeight() / UPLINK_FACTOR; } return w; } /** * Get the type order number of this node. This number may be used to * influence the sort order of ClassdiagramNodes. * * @return Type order number. */ public int getTypeOrderNumer() { int result = 99; if (getFigure() instanceof FigPackage) { result = 0; } else if (getFigure() instanceof FigInterface) { result = 1; } return result; } /** * Get the uplinks of this node. * * @return The uplinks of this node. */ public List<ClassdiagramNode> getUpNodes() { return uplinks; } /** * Return the weight of this node, which is used for positioning in a row. * * @return The weight of this node. */ public float getWeight() { return weight; } /** * Check if this node is associated with a note. * * @return Result of test. */ public boolean isComment() { return (getFigure() instanceof FigComment); } /** * Check if this node is associated with a package. * * @return Result of test. */ public boolean isPackage() { return (getFigure() instanceof FigPackage); } /** * Test whether this node has no connection to other nodes. Return * <code>true</code> if node has no connections, <code>false</code> * otherwise. * * @return Result of test. */ public boolean isStandalone() { return uplinks.isEmpty() && downlinks.isEmpty(); } /** * Set the column of this node. A re-calculation of the weight is performed, * because the column is an input parameter for the weight. * * @param newColumn * The new column. */ public void setColumn(int newColumn) { column = newColumn; calculateWeight(); } /** * Set the offset for edges to this node. * * @param newOffset * Offset for edges with this node as one endpoint. */ public void setEdgeOffset(int newOffset) { edgeOffset = newOffset; } /** * Set the Fig represented by this node. * * @param newFigure * represents the new value of figure. */ public void setFigure(FigNode newFigure) { figure = newFigure; } /** * Set the location of the Fig associated with this node. * * @param newLocation * represents the new location for this figure. */ @SuppressWarnings("unchecked") public void setLocation(Point newLocation) { Point oldLocation = getFigure().getLocation(); getFigure().setLocation(newLocation); int xTrans = newLocation.x - oldLocation.x; int yTrans = newLocation.y - oldLocation.y; for (Fig fig : (List<Fig>) getFigure().getEnclosedFigs()) { fig.translate(xTrans, yTrans); } } /** * A placementhint gives an indication where it might be feasible to place * this node. It is used by the layouter, and there is no guarantee that it * will be used. * * @param hint * x coordinate of the desired placement */ public void setPlacementHint(int hint) { placementHint = hint; } /** * Set the rank * * @param newRank * represents the new value of rank. */ public void setRank(int newRank) { rank = newRank; } /** * Set the weight for this node. * * @param w * The new weight of this node. */ public void setWeight(float w) { weight = w; } }