FigLifeLine.java
/* $Id$
*******************************************************************************
* Copyright (c) 2010 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bob Tarling
* Christian L\u00f3pez Esp\u00ednola
*******************************************************************************
*
* Some portions of this file were previously release using the BSD License:
*/
// $Id$
// Copyright (c) 2007-2009 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.sequence2.diagram;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.argouml.uml.diagram.DiagramSettings;
import org.argouml.uml.diagram.ui.ArgoFigGroup;
import org.tigris.gef.presentation.FigLine;
import org.tigris.gef.presentation.FigRect;
/**
* This fig is the LifeLine of a ClassifierRole.
* @author penyaskito
*/
class FigLifeLine extends ArgoFigGroup {
private static final long serialVersionUID = 466925040550356L;
private FigLine lineFig;
private FigRect rectFig;
private List<FigActivation> activations;
private List<FigActivation> stackedActivations;
static final int WIDTH = 150;
static final int HEIGHT = 500;
FigLifeLine(Object owner, Rectangle bounds, DiagramSettings settings) {
super(owner, settings);
initialize(bounds.x, bounds.y);
}
private void initialize(int x, int y) {
activations = new LinkedList<FigActivation>();
stackedActivations = new LinkedList<FigActivation>();
rectFig = new FigRect(x, y, WIDTH, HEIGHT);
rectFig.setFilled(false);
rectFig.setLineWidth(0);
lineFig = new FigLine(x + WIDTH / 2, y,
x + WIDTH / 2, y + HEIGHT, LINE_COLOR);
lineFig.setDashed(true);
lineFig.setLineWidth(LINE_WIDTH);
addFig(rectFig);
addFig(lineFig);
}
// TODO: Does this still need to be synchronized? If so then explain why.
synchronized void createActivations(final List<FigMessage> messages) {
clearActivations();
Collections.sort(messages, new FigMessageComparator());
activations = createStandardActivations(messages);
stackedActivations = createStackedActivations(messages);
addActivations(activations);
addActivations(stackedActivations);
// TODO: Do we need this?
calcBounds();
}
/**
* Add the given list of activation Figs to the lifeline. The fill colour
* is forced to the lifeline colour in the process.
* @param activationFigs
*/
private void addActivations(
final List<FigActivation> activationFigs) {
for (final FigActivation figAct : activationFigs) {
figAct.setFillColor(getFillColor());
addFig(figAct);
}
}
private List<FigActivation> createStandardActivations(
final List<FigMessage> figMessages) {
final List<FigActivation> newActivations =
new LinkedList<FigActivation>();
// Check here if there are no incoming call actions
// if not then create an activation at the top of the lifeline
FigActivation currentActivation = null;
if (!hasIncomingCallActionFirst(figMessages)) {
currentActivation = createActivationFig(
getOwner(),
lineFig.getX(),
lineFig.getY(),
lineFig.getWidth(),
lineFig.getHeight(),
getSettings(),
null);
}
// This counts the number of repeated call/returns that take place
// after the first activation. This shouldn't be required once
// we handle stacked activations better and once issue 5692 and 5693
// are sorted.
int activationsCount = 0;
//
for (FigMessage figMessage : figMessages) {
int ySender = 0;
if (!figMessage.isSelfMessage()) {
if (isIncoming(figMessage)) {
if (currentActivation == null) {
if (figMessage.isSynchCallMessage()) {
// if we are the dest and is a call action, create the
// activation, but don't add it until the height is set.
ySender = figMessage.getFinalY();
currentActivation = createActivationFig(
getOwner(),
lineFig.getX(),
ySender,
0,
0,
getSettings(),
figMessage);
activationsCount++;
} else if (figMessage.isCreateMessage()) {
// if we are the destination of a create action,
// create the entire activation
currentActivation = createActivationFig(
getOwner(),
lineFig.getX(),
lineFig.getY(),
0,
0,
getSettings(),
figMessage);
activationsCount++;
}
} else {
if (figMessage.isSynchCallMessage()
&& isSameClassifierRoles(
currentActivation.getActivatingMessage(),
figMessage)) {
activationsCount++;
} else if (figMessage.isDeleteMessage()) {
// if we are the target of a destroy action
// the figlifeline ends here and we add the activation
ySender = figMessage.getFinalY();
currentActivation.setHeight(
ySender - currentActivation.getY());
currentActivation.setDestroy(true);
lineFig.setHeight(ySender - getY());
newActivations.add(currentActivation);
currentActivation = null;
}
}
}
if (isOutgoing(figMessage) && currentActivation != null
&& currentActivation.isActivatorEnd(figMessage)
&& --activationsCount == 0) {
// if we are the source of a return action
// the activation ends here.
ySender = figMessage.getStartY();
currentActivation.setHeight(
ySender - currentActivation.getY());
newActivations.add(currentActivation);
currentActivation = null;
}
}
}
// If we have a currentAct object that means have reached the end
// of the lifeline with a call or a create not returned.
// Add the activation to the list after setting its height to end
// at the end of the lifeline.
if (currentActivation != null) {
currentActivation.setHeight(
getHeight() - (currentActivation.getY() - getY()));
newActivations.add(currentActivation);
}
return newActivations;
}
private boolean isSameClassifierRoles(
final FigMessage mess1,
final FigMessage mess2) {
return mess1 != null
&& mess1.getDestFigNode() == mess2.getDestFigNode()
&& mess1.getSourceFigNode() == mess2.getSourceFigNode();
}
/**
* Return true if the given message fig is pointing in to this lifeline.
* @param messageFig
* @return true if the message is incoming
*/
private boolean isIncoming(FigMessage messageFig) {
return (messageFig.getDestFigNode().getOwner() == getOwner());
}
/**
* Return true if the given message fig is pointing out from this lifeline.
* @param messageFig
* @return true if the message is outgoing
*/
private boolean isOutgoing(FigMessage messageFig) {
return (messageFig.getSourceFigNode().getOwner() == getOwner());
}
private FigActivation createActivationFig(
final Object owner,
final int x,
final int y,
final int w,
final int h,
final DiagramSettings settings,
final FigMessage messageFig) {
return new FigActivation(
owner,
new Rectangle(x, y, w, h),
settings,
messageFig);
}
private List<FigActivation> createStackedActivations(
final List<FigMessage> figMessages) {
final List<FigActivation> newActivations =
new LinkedList<FigActivation>();
FigActivation currentAct = null;
for (FigMessage figMessage : figMessages) {
int ySender = 0;
// if we are the dest and is a call action, create the
// activation, but don't add it until the height is set.
if (figMessage.isSelfMessage()) {
if (figMessage.isSynchCallMessage()) {
ySender = figMessage.getFinalY();
currentAct = new FigActivation(figMessage.getOwner(),
new Rectangle(lineFig.getX()
+ FigActivation.DEFAULT_WIDTH / 2, ySender,
0, 0), getSettings(), figMessage, false);
} else if (currentAct != null
&& figMessage.isReplyMessage()) {
ySender = figMessage.getStartY();
currentAct.setHeight(ySender - currentAct.getY());
newActivations.add(currentAct);
currentAct = null;
}
}
}
return newActivations;
}
private boolean hasIncomingCallActionFirst(
final List<FigMessage> figMessages) {
final FigClassifierRole cr =
(FigClassifierRole) getGroup();
if (figMessages.isEmpty()) {
return false;
}
FigMessage figMessage = figMessages.get(0);
if (cr.equals(figMessage.getDestFigNode())
&& !cr.equals(figMessage.getSourceFigNode())
&& figMessage.isSynchCallMessage()) {
return true;
}
return false;
}
private void clearActivations() {
for (FigActivation oldActivation : activations) {
removeFig(oldActivation);
}
for (FigActivation oldActivation : stackedActivations) {
removeFig(oldActivation);
}
activations.clear();
stackedActivations.clear();
}
@Override
public void setFilled(boolean filled) {
// we do nothing. No call to the parent
}
@Override
// TODO: synchronized is required here as there can be some
// concurrent modification problems when drawing a call message and
// having that automatically draw the reply. Maybe fixing the TODO
// below will resolve this and the synch can go.
protected synchronized void setBoundsImpl(int x, int y, int w, int h) {
final Rectangle oldBounds = getBounds();
rectFig.setBounds(x, y, w, h);
lineFig.setBounds(x + w / 2, y, w, h);
final int yDiff = oldBounds.y - y;
// we don't recalculate activations, just move them
for (FigActivation act : activations) {
// TODO: why do we need to remove then add the Fig?
removeFig(act);
act.setLocation(
lineFig.getX() - FigActivation.DEFAULT_WIDTH / 2,
act.getY() - yDiff);
if (activations.size() == 1
&& act.getHeight() == oldBounds.height) {
act.setHeight(getHeight());
}
addFig(act);
}
damage();
_x = x;
_y = y;
_w = w;
_h = h;
firePropChange("bounds", oldBounds, getBounds());
}
public void setLineWidth(int w) {
lineFig.setLineWidth(w);
}
}