Ptidej-v5.2-master/POM Tests/data/ArgoUML_0.28/src/org/argouml/uml/diagram/sequence/ui/FigClassifierRole.java
// $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;
}
