// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.diagram; import java.util.LinkedList; import java.util.List; import net.sf.sdedit.drawable.Cross; import net.sf.sdedit.drawable.Drawable; import net.sf.sdedit.drawable.ExtensibleDrawable; import net.sf.sdedit.drawable.Figure; import net.sf.sdedit.drawable.LabeledBox; import net.sf.sdedit.drawable.Line; import net.sf.sdedit.drawable.Rectangle; import net.sf.sdedit.util.Direction; /** * For each object or actor that appears in a diagram and that has not yet been * destroyed, there is at least one <tt>Lifeline</tt> object. It is <i>active</i> * (see {@linkplain #isActive()}) if the corresponding object or actor can * currently send a message. For objects, being active is implied by having * received a message to which no answer has yet been sent. When an object that * has already received a message to which it has not yet answered, receives * another message, a new <tt>Lifeline</tt> object is created and associated * to the original (or "root") <tt>Lifeline</tt> object. * * @author Markus Strauch */ public final class Lifeline { /** * The direction of the lifeline, for root lifelines Direction.CENTER, for * other lifelines it depends on the position of the caller that initiated * the creation. */ private final Direction direction; /** * The name of the object. */ private final String name; /** * The type of the object. */ private final String type; /** * A mnemonic for distinguishing between lifelines belonging to the same * object. */ private String mnemonic; /** * The original lifeline belonging to this one. */ private final Lifeline root; /** * The lifeline corresponding to the activity of the object before the * activity that this lifeline represents started. */ private Lifeline parent; // only the root lifeline can have non-null leftChild AND rightChild private Lifeline leftChild; private Lifeline rightChild; /** * Flag relevant for root lifelines */ private boolean active; /** * Flag denoting if the object corresponding to this lifeline has already * been created. */ private boolean alive; /** * The number of activities that have been started before the activity that * this lifeline represents, and that have not yet been finished. */ private final int level; /** * Similar to <tt>level</tt>, but restricted to the lifelines with the * same direction like this one. */ private final int sideLevel; private final Diagram diagram; /** * The current graphical representation, changes as time passes (a line for * inactive phases, a rectangle for active phases) */ private ExtensibleDrawable view; /** * The head of the lifeline (a labeled box or a figure) */ private final Drawable head; /** * A cross at the bottom of the lifeline, if present, it denotes destruction */ private Cross cross; /** * The thread where the activity represented by this lifeline occurs */ private int thread; /** * Flag denoting if this lifeline belongs to an active object */ private final boolean activeObject; /** * Flag denoting if this lifeline belongs to a process (a passive actor) */ private final boolean process; /** * Flag denoting if a thread is statically spawned for this lifeline */ private final boolean hasThread; /** * The line representation most recently created for this lifeline */ private Line lastLine; /** * Flag denoting if the lifeline is to be destroyed when it has performed * its last activity */ private final boolean autodestroy; /** * The vertical position where the most recently created rectangle for * representing this lifeline ends. */ private int rectangleBottom; /** * This list is non-null for the root lifeline (only). It contains all * ExtensibleDrawable objects that are ever used as a view of the root * lifeline or its descendants. */ private final List<ExtensibleDrawable> allViews; private final boolean external; private boolean waiting; /** * Creates a new root <tt>Lifeline</tt> object that corresponds to an * object and is ready to receive messages. If the object receives a message * while it is active, this 'higher level' activity will be represented by a * dependent lifeline which must be created via the * {@linkplain #Lifeline(Lifeline, Direction, int)} constructor with the * main lifeline as the first argument. * * @param name * the name of the object * @param type * the type of the object * @param label * the label of the lifeline, if it is the empty string, the * label depends on the name and type * @param alive * flag denoting whether the object is visible from the start (<tt>true</tt>) * or it will come into existence by receiving a new message * @param anonymous * flag denoting whether the name of the instance will be shown * on the diagram * @param role * flag denoting whether the object is a role rather, so its * place could be taken by another object of another class, with * a similar behaviour; the visible effect of this is that the * name of the instance and the class will not be underlined * @param activeObject * flag denoting if the object is active, i. e. all messages sent * to the object spawn a new thread * @param diagram * the diagram to which the lifeline belongs */ public Lifeline(String name, String type, String label, boolean alive, boolean anonymous, boolean role, boolean activeObject, boolean process, boolean hasThread, boolean autodestroy, boolean external, Diagram diagram) { this.diagram = diagram; this.name = name; this.type = type; this.direction = Direction.CENTER; this.alive = alive; this.thread = 0; this.level = 0; this.activeObject = activeObject; this.process = process; this.hasThread = hasThread; this.autodestroy = autodestroy; this.external = external; parent = null; root = this; sideLevel = 0; rectangleBottom = 0; waiting = false; allViews = new LinkedList<ExtensibleDrawable>(); if (type.equals("Actor")) { head = new Figure(this, label, diagram.getVerticalPosition(), !role); view = new Rectangle(computeDrawableWidth(), this); active = true; } else if (process) { head = new LabeledBox(this, label, diagram.getVerticalPosition(), anonymous, !role); view = new Rectangle(computeDrawableWidth(), this); active = true; } else { head = new LabeledBox(this, label, diagram.getVerticalPosition(), anonymous, !role); // view = new Line(computeDrawableWidth(), this); view = new Line(1, this); active = false; } view.setTop(head.getTop() + head.getHeight()); head.setVisible(alive); view.setVisible(alive); addView(view); } /** * If a main lifeline is active because it received a message, and then it * receives another message, a dependent lifeline must be created. This is * done by this constructor. * * @param root * the root lifeline * @param direction * <tt>Direction.LEFT</tt>, if the message is sent from a * lifeline with a lower position, otherwise * <tt>Direction.RIGHT</tt> * @param thread * the number of the thread where the message that produced the * activity represented by the new lifeline occured */ private Lifeline(Lifeline root, Direction direction, int thread) { this.name = root.name; this.type = root.type; this.direction = direction; this.activeObject = root.activeObject; this.root = root; this.thread = thread; this.external = root.external; autodestroy = false; hasThread = false; diagram = root.diagram; alive = true; parent = root; level = root.getAllLifelines().size(); allViews = null; waiting = false; if (direction == Direction.LEFT) { while (parent.leftChild != null) { parent = parent.leftChild; } parent.leftChild = this; } else { while (parent.rightChild != null) { parent = parent.rightChild; } parent.rightChild = this; } sideLevel = parent.sideLevel + 1; active = false; view = new Rectangle(computeDrawableWidth(), this); head = null; process = false; // view's top will be set inside setActive(true) } public void addView(ExtensibleDrawable view) { root.allViews.add(view); } public List<ExtensibleDrawable> getAllViews() { return allViews; } public int getCallLevel() { int callLevel = 0; for (Lifeline line : getAllLifelines()) { if (line != this && thread == line.thread) { callLevel++; } } return callLevel; } public boolean isActiveObject() { return activeObject; } public boolean hasThread() { return hasThread; } public void setThread(int thread) { this.thread = thread; } public int getThread() { return thread; } public void setMnemonic(String mnemnonic) { this.mnemonic = mnemnonic; } public String getMnemonic() { return mnemonic; } public boolean isWaiting() { return waiting; } /** * If all activities that correspond to this lifeline, its root and its * root's descendants belong to the same unique thread, the number of this * thread is returned, otherwise -1 * * @return the number of the unique thread where all activities belonging to * this lifeline, its root and its root's descendants occur, it * there is such a thread, otherwise -1 */ public int getUniqueThread() { int t = -1; for (Lifeline line : getAllLifelines()) { if (line.isActive()) { if (t == -1) { t = line.getThread(); } else { if (t != line.getThread()) { return -1; } } } } return t; } public Lifeline getLeftmost() { Lifeline left = root; while (left.leftChild != null) { left = left.leftChild; } return left; } public Lifeline getRightmost() { Lifeline right = root; while (right.rightChild != null) { right = right.rightChild; } return right; } /** * Returns the main or root lifeline that represents the first activity of * the object to which this lifeline belongs. * * @return the main or root lifeline that represents the first activity of * the object to which this lifeline belongs */ public Lifeline getRoot() { return root; } public boolean isAlwaysActive() { return type.equals("Actor") || process; } /** * Returns a list containing this lifeline and all of its sub lifelines. * * @return a list containing this lifeline and all of its sub lifelines */ public LinkedList<Lifeline> getAllLifelines() { LinkedList<Lifeline> list = new LinkedList<Lifeline>(); list.add(this); Lifeline line = leftChild; while (line != null) { list.add(line); line = line.leftChild; } line = rightChild; while (line != null) { list.add(line); line = line.rightChild; } return list; } public Lifeline getLastInThread(int thread) { Lifeline last = null; for (Lifeline lifeline : getAllLifelines()) { if (lifeline.getThread() == thread && (last == null || lifeline.level > last.level)) { last = lifeline; } } return last; } /** * If this is a root or sub lifeline that belongs to the object which is * displayed rightmost, this method returns <tt>null</tt>, otherwise it * returns (with respect to the current state of activities) the leftmost * lifeline belonging to the object that is displayed to the right of the * object to which this lifeline belongs. If the object on the right is not * yet active, this method returns <tt>null</tt>. * * @return the leftmost lifeline belonging to the object that is displayed * to the right of the object to which this lifeline belongs or * <tt>null</tt> if no such object or lifeline exists */ public Lifeline getRightNeighbour() { int pos = getPosition(); if (pos < diagram.getNumberOfLifelines() - 1) { Lifeline right = diagram.getLifelineAt(pos + 1); while (right.leftChild != null) { right = right.leftChild; } return right; } return null; } /** * If this is a root or sub lifeline that belongs to the object which is * displayed leftmost, this method returns <tt>null</tt>, otherwise it * returns (with respect to the current state of activities) the rightmost * lifeline belonging to the object that is displayed to the left of the * object to which this lifeline belongs. If the object on the left is not * yet active, this method returns <tt>null</tt>. * * @return the rightmost lifeline belonging to the object that is displayed * to the left of the object to which this lifeline belongs or * <tt>null</tt> if no such object or lifeline exists */ public Lifeline getLeftNeighbour() { int pos = getPosition(); if (pos > 0) { Lifeline left = diagram.getLifelineAt(pos - 1); while (left.rightChild != null) { left = left.rightChild; } return left; } return null; } /** * Returns a flag denoting whether the object is visible and ready to * receive a message (<tt>true</tt>) or still waiting for a 'new' * message. * * @return a flag denoting whether the object is visible and ready to * receive message (<tt>true</tt>) or still waiting for a 'new' * message */ public boolean isAlive() { return alive; } /** * Call this method when an object is created via a 'new' message. */ public void giveBirth() { alive = true; head.setVisible(true); view.setVisible(true); } /** * Returns <tt>Direction.CENTER</tt>, if this is a lifeline, * <tt>Direction.LEFT</tt>, if this is a dependent lifeline that has been * activated by a message from an object with a lower position, otherwise * <tt>Direction.RIGHT</tt> * * @return <tt>Direction.CENTER</tt>, if this is a lifeline, * <tt>Direction.LEFT</tt>, if this is a dependent lifeline that * has been activated by a message from an object with a lower * position, otherwise <tt>Direction.RIGHT</tt> */ public Direction getDirection() { return direction; } /** * Returns this lifeline's object's position in the object section where it * is declared. * * @return this lifeline's position */ public int getPosition() { return diagram.getPositionOf(this); } /** * Returns 0, if this is a root lifeline; if it is a sub lifeline, returns * the number of sub lifelines that have the same direction like this one. * * @return the level of this lifeline on its side */ public int getSideLevel() { return sideLevel; } public boolean isActive() { return active; } public String getName() { return name; } public String getType() { return type; } // caller determines the direction public Lifeline addActivity(Lifeline caller, int thread) { Direction theDirection; if (caller.getName().equals(getName())) { if (caller.getDirection() == Direction.CENTER) { theDirection = Direction.RIGHT; } else { if (getDirection() == Direction.CENTER) { theDirection = caller.getDirection(); } else { theDirection = getDirection(); } } } else { theDirection = caller.getPosition() < getPosition() ? Direction.LEFT : Direction.RIGHT; } return new Lifeline(root, theDirection, thread); } /** * This method is called when a message was read and the control flow * returns to an object that has been called before the object corresponding * to this lifeline was called. Thus its active flag is set false. */ public void finish() { setActive(false); getRoot().setRectangleBottom(diagram.getVerticalPosition()); } public void terminate() { ExtensibleDrawable _view = lastLine != null ? lastLine : view; if (alive && autodestroy) { int lengthOfLastLine = Math.max(6, rectangleBottom - _view.getTop()); cross = new Cross(this); int y = _view.getTop() + lengthOfLastLine + cross.getHeight(); if (y > diagram.getVerticalPosition()) { diagram.extendLifelines(y - diagram.getVerticalPosition()); } cross.setTop(_view.getTop() + lengthOfLastLine); diagram.getPaintDevice().addOtherDrawable(cross); _view.setHeight(lengthOfLastLine); } alive = false; } /** * Disposes this lifeline, which means that it is taken from the stack of * the main lifeline it belongs to. If it is a main lifeline, it cannot be * disposed and thus an <tt>IllegalStateException</tt> is thrown. A * lifeline must not be active in order to dispose it. * * @throws IllegalStateException * if this is a main lifeline or if this lifeline is still * active */ public void dispose() { if (active) { throw new IllegalStateException("lifeline is still active"); } if (getRoot() == this) { throw new IllegalStateException("lifeline cannot be disposed"); } switch (direction) { case LEFT: parent.leftChild = leftChild; if (leftChild != null) { leftChild.parent = parent; } break; case RIGHT: parent.rightChild = rightChild; if (rightChild != null) { rightChild.parent = parent; } break; case CENTER: throw new IllegalStateException("The lifeline is not root, but" + " has center direction"); } getRoot().setRectangleBottom(diagram.getVerticalPosition()); } public void toggleWaitingStatus() { if (!active) { throw new IllegalArgumentException( "an inactive lifeline cannot change its waiting status"); } waiting = !waiting; int y = view.getTop() + view.getHeight(); if (waiting) { lastLine = new Line(1, this); lastLine.setLeft(view.getLeft() + view.getWidth() / 2); view = lastLine; } else { view = new Rectangle(computeDrawableWidth(), this); } view.setTop(y); } public void setActive(boolean active) { if (this.active == active) { return; } this.active = active; if (this != getRoot()) { if (active) { view.setTop(parent.getView().getTop() + parent.getView().getHeight()); view.setHeight(0); } else { diagram.clearMnemonic(this); } return; } int y = view.getTop() + view.getHeight(); if (active) { view = new Rectangle(computeDrawableWidth(), this); } else { // view = new Line(computeDrawableWidth(), this); view = new Line(1, this); diagram.clearMnemonic(this); lastLine = (Line) view; } view.setTop(y); } public void setRectangleBottom(int bottom) { rectangleBottom = Math.max(rectangleBottom, bottom); } public String toString() { return getName() + ":" + getType(); } public Diagram getDiagram() { return diagram; } public ExtensibleDrawable getView() { return view; } public Drawable getHead() { return head; } private int computeDrawableWidth() { return sideLevel == 0 ? diagram.getConfiguration() .getMainLifelineWidth() : diagram.getConfiguration() .getSubLifelineWidth(); } public boolean isExternal () { return external; } public void destroy() { diagram.removeLifeline(getName()); cross = new Cross(this); diagram.getPaintDevice().addOtherDrawable(cross); } public Cross getCross() { return cross; } }