// $Id: FigClassifierRole.java 16487 2009-01-03 04:49:04Z tfmorris $
// 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.sequence.ui;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.log4j.Logger;
import org.argouml.model.Model;
import org.argouml.uml.diagram.sequence.MessageNode;
import org.argouml.uml.diagram.sequence.ui.FigLifeLine.FigLifeLineHandler;
import org.argouml.uml.diagram.ui.FigNodeModelElement;
import org.tigris.gef.base.Globals;
import org.tigris.gef.base.Layer;
import org.tigris.gef.base.Selection;
import org.tigris.gef.persistence.pgml.Container;
import org.tigris.gef.persistence.pgml.FigGroupHandler;
import org.tigris.gef.persistence.pgml.HandlerFactory;
import org.tigris.gef.persistence.pgml.HandlerStack;
import org.tigris.gef.persistence.pgml.PGMLStackParser;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigLine;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Fig to show an object on a sequence diagram. The fig consists of an
* upper box that shows the name of the object (the owner) and a
* lifeline. The lifeline consists of lifeline elements. An element
* can be a dashed line (no link attached) or a rectangle (link
* attached).
* @author jaap.branderhorst@xs4all.nl
* Aug 11, 2003
*/
public class FigClassifierRole extends FigNodeModelElement
implements MouseListener, HandlerFactory {
/**
* Logger.
*/
private static final Logger LOG =
Logger.getLogger(FigClassifierRole.class);
/**
* The width of an activation box.
*/
public static final int ROLE_WIDTH = 20;
/**
* The margin between the outer box and the name and stereotype text box.
*/
public static final int MARGIN = 10;
/**
* The distance between two rows in the object rectangle.
*/
public static final int ROWDISTANCE = 2;
/**
* The defaultheight of the object rectangle. That's 3 times the rowheight +
* 3 times a distance of 2 between the rows + the stereoheight.
*/
public static final int MIN_HEAD_HEIGHT =
(3 * ROWHEIGHT + 3 * ROWDISTANCE + STEREOHEIGHT);
/**
* The defaultwidth of the object rectangle.
*/
public static final int MIN_HEAD_WIDTH = 3 * MIN_HEAD_HEIGHT / 2;
/**
* The filled box for the object box (object fig without lifeline).
*/
private FigHead headFig;
/**
* The lifeline (dashed line under the object box to which activations are
* attached).
*/
private FigLifeLine lifeLineFig;
/**
* The list where the nodes to which links can be attached are stored.
*/
private List<MessageNode> linkPositions = new ArrayList<MessageNode>();
/**
* The comma seperated list of base names of the classifierRole(s)
* that this object represents.
*/
private String baseNames = "";
/**
* The name of the classifier role (the owner of this fig).
*/
private String classifierRoleName = "";
/**
* Default constructor. Constructs the object rectangle, the lifeline,
* the name box and the stereotype box.
*/
private FigClassifierRole() {
super();
headFig = new FigHead(getStereotypeFig(), getNameFig());
getStereotypeFig().setBounds(MIN_HEAD_WIDTH / 2,
ROWHEIGHT + ROWDISTANCE,
0,
0);
getStereotypeFig().setFilled(false);
getStereotypeFig().setLineWidth(0);
getNameFig().setEditable(false);
getNameFig().setFilled(false);
getNameFig().setLineWidth(0);
lifeLineFig =
new FigLifeLine(MIN_HEAD_WIDTH / 2 - ROLE_WIDTH / 2, MIN_HEAD_HEIGHT);
linkPositions.add(new MessageNode(this));
for (int i = 0;
i <= lifeLineFig.getHeight()
/ SequenceDiagramLayer.LINK_DISTANCE;
i++) {
linkPositions.add(new MessageNode(this));
}
// TODO: Why does this give loading problems?
// addFig(getBigPort());
addFig(lifeLineFig);
addFig(headFig);
// TODO: Why does this give loading problems?
// addFig(getStereotypeFig());
// addFig(getNameFig());
}
/**
* Construct a ClassifierRole figure for the given model element.
*
* @param node the ClassifierRole to own the Fig
* @param x x position
* @param y y position
* @param w width
* @param h height
*/
public FigClassifierRole(Object node, int x, int y, int w, int h) {
this();
setBounds(x, y, w, h);
setOwner(node);
}
/**
* Construct a ClassifierRole figure for the given model element.
*
* @param node the ClassifierRole to own the Fig
*/
public FigClassifierRole(Object node) {
this();
setOwner(node);
}
/*
* When the mouse button is released, this fig will be moved into position.
*
* @see MouseListener#mouseReleased(MouseEvent)
*/
@Override
public void mouseReleased(MouseEvent me) {
super.mouseReleased(me);
Layer lay = Globals.curEditor().getLayerManager().getActiveLayer();
if (lay instanceof SequenceDiagramLayer) {
((SequenceDiagramLayer) lay).putInPosition(this);
}
}
/**
* Constructs the contents of the name text box and upates the name text box
* accordingly. The contents of the name text box itself are NOT updated.
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#updateNameText()
*/
@Override
protected void updateNameText() {
String nameText =
(classifierRoleName + ":" + baseNames).trim();
getNameFig().setText(nameText);
calcBounds();
damage();
}
/**
* @return node of MessageNodes/
*/
public int getNodeCount() {
return linkPositions.size();
}
/**
* Change a node to point to an actual FigMessagePort.
*/
Fig createFigMessagePort(Object message, TempFig tempFig) {
Fig fmp = lifeLineFig.createFigMessagePort(message, tempFig);
updateNodeStates();
return fmp;
}
void addFigMessagePort(FigMessagePort messagePortFig) {
lifeLineFig.addFig(messagePortFig);
}
/**
* Connect a FigMessagePort with a MessageNode by position.
*/
void setMatchingNode(FigMessagePort fmp) {
while (lifeLineFig.getYCoordinate(getNodeCount() - 1) < fmp.getY1()) {
growToSize(getNodeCount() + 10);
}
int i = 0;
for (Iterator it = linkPositions.iterator(); it.hasNext(); ++i) {
MessageNode node = (MessageNode) it.next();
if (lifeLineFig.getYCoordinate(i) == fmp.getY1()) {
node.setFigMessagePort(fmp);
fmp.setNode(node);
updateNodeStates();
break;
}
}
}
/**
* Set the node's fig to a FigMessagePort if one is available.
*/
private void setMatchingFig(MessageNode messageNode) {
if (messageNode.getFigMessagePort() == null) {
int y = getYCoordinate(messageNode);
for (Iterator it = lifeLineFig.getFigs().iterator();
it.hasNext();) {
Fig fig = (Fig) it.next();
if (fig instanceof FigMessagePort) {
FigMessagePort messagePortFig = (FigMessagePort) fig;
if (messagePortFig.getY1() == y) {
messageNode.setFigMessagePort(messagePortFig);
messagePortFig.setNode(messageNode);
updateNodeStates();
}
}
}
}
}
/*
* Sets the bounds and coordinates of this Fig. The outerbox (the
* black box around the upper box) and the background box (the
* white box at the background) are scaled to the given size. The
* name text box and the stereo text box are moved to a correct
* position.
*
* @see org.tigris.gef.presentation.Fig#setBoundsImpl(int, int, int, int)
*/
@Override
public void setStandardBounds(int x, int y, int w, int h) {
y = 50;
Rectangle oldBounds = getBounds();
w = headFig.getMinimumSize().width;
headFig.setBounds(x, y, w, headFig.getMinimumSize().height);
lifeLineFig.setBounds(
(x + w / 2) - ROLE_WIDTH / 2,
y + headFig.getHeight(),
ROLE_WIDTH,
h - headFig.getHeight());
this.updateEdges(); //???
growToSize(
lifeLineFig.getHeight() / SequenceDiagramLayer.LINK_DISTANCE
+ 2);
calcBounds(); //_x = x; _height = y; _w = w; _h = h;
firePropChange("bounds", oldBounds, getBounds());
}
/*
* This method is overridden in order to ignore change of y co-ordinate
* during drag.
*
* @see org.tigris.gef.presentation.FigNode#superTranslate(int, int)
*/
@Override
public void superTranslate(int dx, int dy) {
setBounds(getX() + dx, getY(), getWidth(), getHeight());
}
/**
* Returns a beautified name to show in the name text box.<p>
*
* @param o is the object
* @return a name
*/
private String getBeautifiedName(Object o) {
String name = Model.getFacade().getName(o);
if (name == null || name.equals("")) {
name = "(anon " + Model.getFacade().getUMLClassName(o) + ")";
}
return name;
}
// TODO: This helper should be private
public static boolean isCallMessage(Object message) {
return Model.getFacade()
.isACallAction(Model.getFacade().getAction(message));
}
// TODO: This helper should be private
public static boolean isReturnMessage(Object message) {
return Model.getFacade()
.isAReturnAction(Model.getFacade().getAction(message));
}
// TODO: This helper should be private
public static boolean isCreateMessage(Object message) {
return Model.getFacade()
.isACreateAction(Model.getFacade().getAction(message));
}
// TODO: This helper should be private
public static boolean isDestroyMessage(Object message) {
return Model.getFacade()
.isADestroyAction(Model.getFacade().getAction(message));
}
private void setPreviousState(int start, int newState) {
for (int i = start - 1; i >= 0; --i) {
MessageNode node = linkPositions.get(i);
if (node.getFigMessagePort() != null) {
break;
}
node.setState(newState);
}
}
private int setFromActionNode(int lastState, int offset) {
if (lastState == MessageNode.INITIAL) {
lastState = MessageNode.DONE_SOMETHING_NO_CALL;
setPreviousState(offset, lastState);
} else if (lastState == MessageNode.IMPLICIT_RETURNED) {
lastState = MessageNode.CALLED;
setPreviousState(offset, lastState);
} else if (lastState == MessageNode.IMPLICIT_CREATED) {
lastState = MessageNode.CREATED;
setPreviousState(offset, lastState);
}
return lastState;
}
/**
* Every classifier role has a state and a caller list.
* The state is PRECREATED before a node is created
* and DESTROYED after classifier role is
* destroyed.
* The state is INITIAL before anything happens
* After a classifier is created, the state is CREATED and
* the state for prior nodes is set to PRECREATED
* If the classifier does something:<p>
*
* If the state is INITIAL, the state for that and prior
* nodes becomes DONE_SOMETHING_NO_CALL<p>
*
* else if the state is IMPLICIT_RETURNED, the state for that
* and prior nodes becomes CALLED<p>
*
* Otherwise, the state doesn't change<p>
*
* If the classifier is called:<p>
*
* If the caller list is empty, the state becomes CALLED<p>
*
* The caller is added to the caller list<p>
*
* If the classifier returns, the caller being returned to and any callers
* added to the list since that call are removed from the caller list.
* If the caller list is empty the state becomes RETURNED.<p>
*
* If nothing happens on the node:<p>
*
* If the previous state was CALLED, the state becomes IMPLICIT_RETURNED<p>
*
* Otherwise, the state is the same as the previous node's state<p>
*
* Start or stop a rectangle when the state changes.
*
*/
void updateNodeStates() {
int lastState = MessageNode.INITIAL;
ArrayList callers = null;
int nodeCount = linkPositions.size();
for (int i = 0; i < nodeCount; ++i) {
MessageNode node = linkPositions.get(i);
FigMessagePort figMessagePort = node.getFigMessagePort();
// If the node has a FigMessagePort
if (figMessagePort != null) {
int fmpY = lifeLineFig.getYCoordinate(i);
if (figMessagePort.getY() != fmpY) {
figMessagePort.setBounds(lifeLineFig.getX(),
fmpY, ROLE_WIDTH, 1);
}
Object message = figMessagePort.getOwner();
boolean selfMessage =
(Model.getFacade().isAMessage(message)
&& (Model.getFacade().getSender(message)
== Model.getFacade().getReceiver(message)));
boolean selfReceiving = false;
if (selfMessage) {
for (int j = i - 1; j >= 0; --j) {
MessageNode prev = linkPositions.get(j);
FigMessagePort prevmp = prev.getFigMessagePort();
if (prevmp != null && prevmp.getOwner() == message) {
selfReceiving = true;
}
}
}
if (isCallMessage(message)) {
if (Model.getFacade().getSender(message) == getOwner()
&& !selfReceiving) {
lastState = setFromActionNode(lastState, i);
node.setState(lastState);
node.setCallers(callers);
} else {
if (lastState == MessageNode.INITIAL
|| lastState == MessageNode.CREATED
|| lastState == MessageNode.IMPLICIT_CREATED
|| lastState == MessageNode.IMPLICIT_RETURNED
|| lastState == MessageNode.RETURNED) {
lastState = MessageNode.CALLED;
}
if (callers == null) {
callers = new ArrayList();
} else {
callers = new ArrayList(callers);
}
callers.add(Model.getFacade().getSender(message));
node.setState(lastState);
node.setCallers(callers);
}
} else if (isReturnMessage(message)) {
if (lastState == MessageNode.IMPLICIT_RETURNED) {
setPreviousState(i, MessageNode.CALLED);
lastState = MessageNode.CALLED;
}
if (Model.getFacade().getSender(message) == getOwner()
&& !selfReceiving) {
if (callers == null) {
callers = new ArrayList();
}
Object caller = Model.getFacade().getReceiver(message);
int callerIndex = callers.lastIndexOf(caller);
if (callerIndex != -1) {
for (int backNodeIndex = i - 1;
backNodeIndex > 0
&& linkPositions
.get(backNodeIndex)
.matchingCallerList(caller,
callerIndex);
--backNodeIndex) {
// skip
}
if (callerIndex == 0) {
callers = null;
if (lastState == MessageNode.CALLED) {
lastState = MessageNode.RETURNED;
}
} else {
callers =
new ArrayList(callers.subList(0,
callerIndex));
}
}
}
node.setState(lastState);
node.setCallers(callers);
} else if (isCreateMessage(message)) {
if (Model.getFacade().getSender(message) == getOwner()) {
lastState = setFromActionNode(lastState, i);
node.setState(lastState);
node.setCallers(callers);
} else {
lastState = MessageNode.CREATED;
setPreviousState(i, MessageNode.PRECREATED);
node.setState(lastState);
node.setCallers(callers);
}
} else if (isDestroyMessage(message)) {
if (Model.getFacade().getSender(message) == getOwner()
&& !selfReceiving) {
lastState = setFromActionNode(lastState, i);
node.setState(lastState);
node.setCallers(callers);
} else {
lastState = MessageNode.DESTROYED;
callers = null;
node.setState(lastState);
node.setCallers(callers);
}
}
} else {
if (lastState == MessageNode.CALLED) {
lastState = MessageNode.IMPLICIT_RETURNED;
}
if (lastState == MessageNode.CREATED) {
lastState = MessageNode.IMPLICIT_CREATED;
}
node.setState(lastState);
node.setCallers(callers);
}
}
}
private void addActivations() {
MessageNode startActivationNode = null;
MessageNode endActivationNode = null;
int lastState = MessageNode.INITIAL;
boolean startFull = false;
boolean endFull = false;
int nodeCount = linkPositions.size();
int x = lifeLineFig.getX();
for (int i = 0; i < nodeCount; ++i) {
MessageNode node = linkPositions.get(i);
int nextState = node.getState();
if (lastState != nextState && nextState == MessageNode.CREATED) {
lifeLineFig.addActivationFig(
new FigBirthActivation(
lifeLineFig.getX(),
lifeLineFig.getYCoordinate(i)
- SequenceDiagramLayer.LINK_DISTANCE / 4));
}
if (lastState != nextState
&& nextState == MessageNode.DESTROYED) {
int y =
lifeLineFig.getYCoordinate(i)
- SequenceDiagramLayer.LINK_DISTANCE / 2;
lifeLineFig.addActivationFig(
new FigLine(x,
y + SequenceDiagramLayer.LINK_DISTANCE / 2,
x + ROLE_WIDTH,
y + SequenceDiagramLayer.LINK_DISTANCE, LINE_COLOR)
);
lifeLineFig.addActivationFig(
new FigLine(x,
y + SequenceDiagramLayer.LINK_DISTANCE,
x + ROLE_WIDTH,
y
+ SequenceDiagramLayer.LINK_DISTANCE / 2, LINE_COLOR)
);
}
if (startActivationNode == null) {
switch (nextState) {
case MessageNode.DONE_SOMETHING_NO_CALL:
startActivationNode = node;
startFull = true;
break;
case MessageNode.CALLED:
case MessageNode.CREATED:
startActivationNode = node;
startFull = false;
break;
default:
}
} else {
switch (nextState) {
case MessageNode.DESTROYED :
case MessageNode.RETURNED :
endActivationNode = node;
endFull = false;
break;
case MessageNode.IMPLICIT_RETURNED :
case MessageNode.IMPLICIT_CREATED :
endActivationNode = linkPositions.get(i - 1);
endFull = true;
break;
case MessageNode.CALLED :
if (lastState == MessageNode.CREATED) {
endActivationNode =
linkPositions.get(i - 1);
endFull = false;
--i;
nextState = lastState;
}
break;
default:
}
}
lastState = nextState;
if (startActivationNode != null && endActivationNode != null) {
if (startActivationNode != endActivationNode
|| startFull || endFull) {
int y1 = getYCoordinate(startActivationNode);
if (startFull) {
y1 -= SequenceDiagramLayer.LINK_DISTANCE / 2;
}
int y2 = getYCoordinate(endActivationNode);
if (endFull) {
y2 += SequenceDiagramLayer.LINK_DISTANCE / 2;
}
lifeLineFig.addActivationFig(
new FigActivation(x, y1, ROLE_WIDTH, y2 - y1));
}
startActivationNode = null;
endActivationNode = null;
startFull = false;
endFull = false;
}
}
if (startActivationNode != null) {
endActivationNode = linkPositions.get(nodeCount - 1);
endFull = true;
int y1 = getYCoordinate(startActivationNode);
if (startFull) {
y1 -= SequenceDiagramLayer.LINK_DISTANCE / 2;
}
int y2 = getYCoordinate(endActivationNode);
if (endFull) {
y2 += SequenceDiagramLayer.LINK_DISTANCE / 2;
}
lifeLineFig.addActivationFig(
new FigActivation(x, y1, ROLE_WIDTH, y2 - y1));
startActivationNode = null;
endActivationNode = null;
startFull = false;
endFull = false;
}
}
/**
* First removes all current activation boxes, then add new ones
* to the figobject depending on the state of the nodes.
*/
public void updateActivations() {
LOG.debug("Updating activations");
lifeLineFig.removeActivations();
addActivations();
}
//////////////////////////////////////////////////////////////////////////
// HandlerFactory implementation
/*
* @see org.tigris.gef.persistence.pgml.HandlerFactory#getHandler(
* org.tigris.gef.persistence.pgml.HandlerStack, java.lang.Object,
* java.lang.String, java.lang.String, java.lang.String,
* org.xml.sax.Attributes)
*/
public DefaultHandler getHandler(HandlerStack stack,
Object container,
String uri,
String localname,
String qname,
Attributes attributes)
throws SAXException {
PGMLStackParser parser = (PGMLStackParser) stack;
StringTokenizer st =
new StringTokenizer(attributes.getValue("description"), ",;[] ");
if (st.hasMoreElements()) {
st.nextToken();
}
String xStr = null;
String yStr = null;
String wStr = null;
String hStr = null;
if (st.hasMoreElements()) {
xStr = st.nextToken();
yStr = st.nextToken();
wStr = st.nextToken();
hStr = st.nextToken();
}
if (xStr != null && !xStr.equals("")) {
int x = Integer.parseInt(xStr);
int y = Integer.parseInt(yStr);
int w = Integer.parseInt(wStr);
int h = Integer.parseInt(hStr);
setBounds(x, y, w, h);
}
PGMLStackParser.setCommonAttrs(this, attributes);
parser.registerFig(this, attributes.getValue("name"));
((Container) container).addObject(this);
return new FigClassifierRoleHandler(parser, this);
}
/**
* The width of the FigClassifierRole should be equal to the width of the
* name or stereo text box.<p>
*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#updateBounds()
*/
@Override
protected void updateBounds() {
Rectangle bounds = getBounds();
bounds.width =
Math.max(
getNameFig().getWidth() + 2 * MARGIN,
getStereotypeFig().getWidth() + 2 * MARGIN);
setBounds(bounds);
}
/**
* Set the line width. Only change the line width of the outerbox
* and the lifeline are changed. Values of zero are ignored.
* @param w width. Must be greater than zero.
*/
public void setLineWidth(int w) {
if (headFig.getLineWidth() != w && w != 0) {
headFig.setLineWidth(w);
lifeLineFig.setLineWidth(w);
damage();
}
}
/*
* @see org.tigris.gef.presentation.Fig#setFillColor(java.awt.Color)
*/
@Override
public void setFillColor(Color col) {
if (col != null && col != headFig.getFillColor()) {
headFig.setFillColor(col);
damage();
}
}
/*
* @see org.tigris.gef.presentation.Fig#setFilled(boolean)
*/
@Override
public void setFilled(boolean filled) {
if (headFig.isFilled() != filled) {
headFig.setFilled(filled);
damage();
}
}
/*
* @see org.tigris.gef.presentation.Fig#getFillColor()
*/
@Override
public Color getFillColor() {
return headFig.getFillColor();
}
@Override
public boolean isFilled() {
return headFig.isFilled();
}
/*
* @see org.tigris.gef.presentation.Fig#getLineColor()
*/
@Override
public Color getLineColor() {
return headFig.getLineColor();
}
/*
* @see org.tigris.gef.presentation.Fig#getLineWidth()
*/
@Override
public int getLineWidth() {
return headFig.getLineWidth();
}
private FigLifeLine getLifeLineFig() {
return lifeLineFig;
}
/*
* @see FigNodeModelElement#updateListeners(java.lang.Object)
*/
@Override
protected void updateListeners(Object oldOwner, Object newOwner) {
Set<Object[]> l = new HashSet<Object[]>();
if (newOwner != null) {
l.add(new Object[] {newOwner, null});
Iterator it = Model.getFacade().getBases(newOwner).iterator();
while (it.hasNext()) {
Object base = it.next();
l.add(new Object[] {base, "name"});
}
it = Model.getFacade().getStereotypes(newOwner).iterator();
while (it.hasNext()) {
Object stereo = it.next();
l.add(new Object[] {stereo, "name"});
}
}
updateElementListeners(l);
// TODO: The old implementation called the superclasses method.
// Do we really want to do that?
// super.updateListeners(oldOwner, newOwner);
}
void growToSize(int nodeCount) {
grow(linkPositions.size(), nodeCount - linkPositions.size());
}
/**
* Add count link spaces before nodePosition.
*/
void grow(int nodePosition, int count) {
for (int i = 0; i < count; ++i) {
linkPositions.add(nodePosition, new MessageNode(this));
}
if (count > 0) {
updateNodeStates();
Rectangle r = getBounds();
r.height += count * SequenceDiagramLayer.LINK_DISTANCE;
setBounds(r);
updateEdges();
}
}
/*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#modelChanged(java.beans.PropertyChangeEvent)
*/
@Override
protected void modelChanged(PropertyChangeEvent mee) {
if (mee.getPropertyName().equals("name")) {
if (mee.getSource() == getOwner()) {
updateClassifierRoleName();
} else if (Model.getFacade().isAStereotype(mee.getSource())) {
updateStereotypeText();
} else {
updateBaseNames();
}
renderingChanged();
} else if (mee.getPropertyName().equals("stereotype")) {
updateStereotypeText();
updateListeners(getOwner(), getOwner());
renderingChanged();
} else if (mee.getPropertyName().equals("base")) {
updateBaseNames();
updateListeners(getOwner(), getOwner());
renderingChanged();
}
}
/**
* Remove a FigMessagePort that's associated with a removed FigMessage.
*
* @param fmp The FigMessagePort.
*/
void removeFigMessagePort(FigMessagePort fmp) {
fmp.getNode().setFigMessagePort(null);
fmp.setNode(null);
lifeLineFig.removeFig(fmp);
}
/**
* Update an array of booleans to set node indexes that have associated
* FigMessagePort to false.
* @param start Index of first node in array
* @param emptyNodes True where there is no FigMessagePort at the node
* with the index in the array + start (at creation the entire array
* is set to true)
*/
void updateEmptyNodeArray(int start, boolean[] emptyNodes) {
for (int i = 0; i < emptyNodes.length; ++i) {
if (linkPositions.get(i + start).getFigMessagePort()
!= null) {
emptyNodes[i] = false;
}
}
}
/**
* Remove nodes according to the emptyNodes array; contract total height
* of fig.
*
* @param start Index of first node in array
* @param emptyNodes True where there is no FigMessagePort at the node
* with the index in the array + start
*/
void contractNodes(int start, boolean[] emptyNodes) {
int contracted = 0;
for (int i = 0; i < emptyNodes.length; ++i) {
if (emptyNodes[i]) {
if (linkPositions.get(i + start - contracted)
.getFigMessagePort()
!= null) {
throw new IllegalArgumentException(
"Trying to contract non-empty MessageNode");
}
linkPositions.remove(i + start - contracted);
++contracted;
}
}
if (contracted > 0) {
updateNodeStates();
Rectangle r = getBounds();
r.height -= contracted * SequenceDiagramLayer.LINK_DISTANCE;
updateEdges();
setBounds(r);
}
}
private void updateBaseNames() {
StringBuffer b = new StringBuffer();
Iterator it = Model.getFacade().getBases(getOwner()).iterator();
while (it.hasNext()) {
b.append(getBeautifiedName(it.next()));
if (it.hasNext()) {
b.append(',');
}
}
baseNames = b.toString();
}
private void updateClassifierRoleName() {
classifierRoleName = getBeautifiedName(getOwner());
}
/*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#renderingChanged()
*/
@Override
public void renderingChanged() {
super.renderingChanged();
updateBaseNames();
updateClassifierRoleName();
}
/**
* Returns the port for a given coordinate pair. Normally
* deepHitPort returns the owner of the fig in the FigGroup that
* is present at the given coordinate pair (returning figs that
* are added later first). In this case it returns a
* MessagePort.<p>
*
* {@inheritDoc}
*/
@Override
public Object deepHitPort(int x, int y) {
Rectangle rect = new Rectangle(getX(), y - 16, getWidth(), 32);
MessageNode foundNode = null;
if (lifeLineFig.intersects(rect)) {
for (int i = 0; i < linkPositions.size(); i++) {
MessageNode node = linkPositions.get(i);
int position = lifeLineFig.getYCoordinate(i);
if (i < linkPositions.size() - 1) {
int nextPosition =
lifeLineFig.getYCoordinate(i + 1);
if (nextPosition >= y && position <= y) {
if ((y - position) <= (nextPosition - y)) {
foundNode = node;
} else {
foundNode = linkPositions.get(i + 1);
}
break;
}
} else {
foundNode =
linkPositions.get(linkPositions.size() - 1);
MessageNode nextNode;
nextNode = new MessageNode(this);
linkPositions.add(nextNode);
int nextPosition = lifeLineFig.getYCoordinate(i + 1);
if ((y - position) >= (nextPosition - y)) {
foundNode = nextNode;
}
break;
}
}
} else if (headFig.intersects(rect)) {
foundNode = getClassifierRoleNode();
} else {
return null;
}
setMatchingFig(foundNode);
return foundNode;
}
/**
* @param node MessageNode to get Y position of
* @return Y position of given node
*/
public int getYCoordinate(MessageNode node) {
return lifeLineFig.getYCoordinate(linkPositions.indexOf(node));
}
/*
* @see org.tigris.gef.presentation.Fig#setOwner(java.lang.Object)
*/
@Override
public void setOwner(Object own) {
super.setOwner(own);
bindPort(own, headFig);
}
/**
* Returns the index of a given node.
*
* @param node is the given node
* @return the index
*/
public int getIndexOf(MessageNode node) {
return linkPositions.indexOf(node);
}
/**
* Returns the node that's next to the given node.
*
* @param node is the given node
* @return the Node
*/
public MessageNode nextNode(MessageNode node) {
if (getIndexOf(node) < linkPositions.size()) {
return linkPositions.get(getIndexOf(node) + 1);
}
return null;
}
/**
* Returns the node that's before the given node in the nodes list.
*
* @param node is the given node
* @return the node
*/
public MessageNode previousNode(MessageNode node) {
if (getIndexOf(node) > 0) {
return linkPositions.get(getIndexOf(node) - 1);
}
return null;
}
/*
* @see org.tigris.gef.presentation.FigNode#getPortFig(java.lang.Object)
*/
@Override
public Fig getPortFig(Object messageNode) {
if (Model.getFacade().isAClassifierRole(messageNode)) {
// TODO: Not sure of the meaning of the following message. We
// will end up here any time a new FigClassifierRole is constructed
// during the setOwner() call - tfm 20080905
LOG.debug("Got a ClassifierRole - only legal on load");
return null;
}
if (!(messageNode instanceof MessageNode)) {
throw new IllegalArgumentException(
"Expecting a MessageNode but got a "
+ messageNode.getClass().getName());
}
setMatchingFig((MessageNode) messageNode);
if (((MessageNode) messageNode).getFigMessagePort() != null) {
return ((MessageNode) messageNode).getFigMessagePort();
}
return new TempFig(
messageNode, lifeLineFig.getX(),
getYCoordinate((MessageNode) messageNode),
lifeLineFig.getX() + ROLE_WIDTH);
}
/**
* Returns the ClassifierRoleNode. This is the port that represents the
* object Figrect.
*
* @return the ClassifierRoleNode.
*/
private MessageNode getClassifierRoleNode() {
return linkPositions.get(0);
}
/**
* Adds a node at the given position.
*
* @param position the position in which the node will be added
* @param node the node to be added
*/
public void addNode(int position, MessageNode node) {
linkPositions.add(position, node);
Iterator it =
linkPositions
.subList(position + 1, linkPositions.size())
.iterator();
while (it.hasNext()) {
Object o = it.next();
if (o instanceof MessageNode) {
FigMessagePort figMessagePort =
((MessageNode) o).getFigMessagePort();
if (figMessagePort != null) {
figMessagePort.setY(
figMessagePort.getY()
+ SequenceDiagramLayer.LINK_DISTANCE);
}
}
}
calcBounds();
}
/**
* Gets a node that has the given position (creates new nodes if needed).
*
* @param position the position of the resulting node
*
* @return the node with the given position
*/
public MessageNode getNode(int position) {
if (position < linkPositions.size()) {
return linkPositions.get(position);
}
MessageNode node = null;
for (int cnt = position - linkPositions.size(); cnt >= 0; cnt--) {
node = new MessageNode(this);
linkPositions.add(node);
}
calcBounds();
return node;
}
/*
* Override to return a custom SelectionResize class that will not allow
* handles on the north edge to be dragged.
*
* @see org.tigris.gef.presentation.Fig#makeSelection()
*/
@Override
public Selection makeSelection() {
return new SelectionClassifierRole(this);
}
static class TempFig extends FigLine {
/**
* Constructor.
*
* @param owner
* @param x
* @param y
* @param x2
*/
TempFig(Object owner, int x, int y, int x2) {
super(x, y, x2, y);
setVisible(false);
setOwner(owner);
}
/**
* The UID.
*/
private static final long serialVersionUID = 1478952234873792638L;
}
static class FigClassifierRoleHandler extends FigGroupHandler {
/**
* Constructor.
*
* @param parser
* @param classifierRole
*/
FigClassifierRoleHandler(PGMLStackParser parser,
FigClassifierRole classifierRole) {
super(parser, classifierRole);
}
/*
* @see org.tigris.gef.persistence.pgml.BaseHandler#getElementHandler(
* org.tigris.gef.persistence.pgml.HandlerStack, java.lang.Object,
* java.lang.String, java.lang.String, java.lang.String,
* org.xml.sax.Attributes)
*/
@Override
protected DefaultHandler getElementHandler(
HandlerStack stack,
Object container,
String uri,
String localname,
String qname,
Attributes attributes) throws SAXException {
DefaultHandler result = null;
String description = attributes.getValue("description");
if (qname.equals("group")
&& description != null
&& description.startsWith(FigLifeLine.class.getName())) {
FigClassifierRole fcr = (FigClassifierRole)
((FigGroupHandler) container).getFigGroup();
result = new FigLifeLineHandler(
(PGMLStackParser) stack, fcr.getLifeLineFig());
} else if (qname.equals("group")
&& description != null
&& description.startsWith(
FigMessagePort.class.getName())) {
// TODO: This if-else-block exists in order
// to load sequence diagrams
// from 0.20. It must exist until -
// http://argouml.tigris.org/issues/show_bug.cgi?id=4039
PGMLStackParser parser = (PGMLStackParser) stack;
String ownerRef = attributes.getValue("href");
Object owner = parser.findOwner(ownerRef);
FigMessagePort fmp = new FigMessagePort(owner);
FigClassifierRole fcr =
(FigClassifierRole)
((FigGroupHandler) container).getFigGroup();
fcr.getLifeLineFig().addFig(fmp);
result = new FigGroupHandler((PGMLStackParser) stack, fmp);
PGMLStackParser.setCommonAttrs(fmp, attributes);
parser.registerFig(fmp, attributes.getValue("name"));
} else {
result =
((PGMLStackParser) stack).getHandler(stack,
container,
uri,
localname,
qname,
attributes);
}
return result;
}
}
/*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#updateStereotypeText()
*/
@Override
protected void updateStereotypeText() {
Rectangle rect = headFig.getBounds();
getStereotypeFig().setOwner(getOwner());
int minWidth = headFig.getMinimumSize().width;
if (minWidth > rect.width) {
rect.width = minWidth;
}
int headHeight = headFig.getMinimumSize().height;
headFig.setBounds(
rect.x,
rect.y,
rect.width,
headHeight);
if (getLayer() == null) {
return;
}
int h = MIN_HEAD_HEIGHT;
List figs = getLayer().getContents();
for (Iterator i = figs.iterator(); i.hasNext();) {
Object o = i.next();
if (o instanceof FigClassifierRole) {
FigClassifierRole other = (FigClassifierRole) o;
int otherHeight = other.headFig.getMinimumHeight();
if (otherHeight > h) {
h = otherHeight;
}
}
}
int height = headFig.getHeight() + lifeLineFig.getHeight();
setBounds(
headFig.getX(),
headFig.getY(),
headFig.getWidth(),
height);
calcBounds();
// Set all other CLassifierRoles to be the same height as this one
// now is
Layer layer = getLayer();
List layerFigs = layer.getContents();
for (Iterator i = layerFigs.iterator(); i.hasNext();) {
Object o = i.next();
if (o instanceof FigClassifierRole && o != this) {
FigClassifierRole other = (FigClassifierRole) o;
other.setHeight(height);
}
}
}
/**
* @return the fig which forms the top box of the composite fig
*/
FigHead getHeadFig() {
return headFig;
}
/**
* The UID.
*/
private static final long serialVersionUID = 7763573563940441408L;
}