/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* 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:
* Del Myers -- initial API and implementation
*******************************************************************************/
package org.eclipse.zest.custom.sequence.widgets;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.zest.custom.sequence.widgets.internal.IWidgetProperties;
/**
* An object that holds a set of activations. The activations on a lifeline are not ordered by default.
* Clients can't expect that they are returned in the order that they appear visually.
* @author Del Myers
*
*/
public class Lifeline extends UMLTextColoredItem implements IExpandableItem {
/**
* Draw as a rectangle (default style).
*/
public static final int CLASS = 0;
/**
* Draw as a
*/
public static final int COLLECTION = 1;
/**
* Draw as a stick-figure.
*/
public static final int ACTOR = 2;
/**
* Draw as a boundary.
*/
public static final int BOUNDARY = 3;
/**
* Draw as a control (a circle with an arrow on the circumference.
*/
public static final int CONTROL = 4;
/**
* Draw as an entity (an underlined circle).
*/
public static final int ENTITY = 5;
/**
* Draw as a data store (a cylinder).
*/
public static final int DATA_STORE = 6;
/**
* Draw as a package
*/
public static final int PACKAGE = 7;
private LinkedList<Activation> activations;
/**
* An array of child lifelines for this lifeline.
*/
private Lifeline[] children;
private Lifeline parent;
private int targetStyle;
private int hiddenCount;
private PropertyChangeListener hideListener;
/**
* An ordered list of the visible activations.
*/
private Activation[] orderedActivations;
private String stereotype;
private boolean expanded;
public Lifeline(UMLChart parent) {
super(parent);
activations = new LinkedList<Activation>();
this.targetStyle = CLASS;
this.hideListener = new PropertyChangeListener() {
public void propertyChanged(Object source, String property,
Object oldValue, Object newValue) {
boolean hidden = hiddenCount == activations.size();
if (IWidgetProperties.HIDDEN.equals(property)) {
if (Boolean.TRUE.equals(newValue)) {
hiddenCount++;
} else {
hiddenCount--;
}
orderedActivations = null;
} else if (IWidgetProperties.LAYOUT.equals(property)) {
orderedActivations = null;
}
firePropertyChange(IWidgetProperties.HIDDEN, hidden, hiddenCount == activations.size());
}
};
this.expanded = true;
}
/**
* Sets the style drawing style of the head of this lifeline may be one of
* ACTOR, BOUNDARY, COLLECTION, CONTROL, CLASS, DATA_STORE, ENTITY, or PACKAGE.
*
* @param classStyle the new class style.
*/
public void setClassStyle(int targetStyle) {
checkWidget();
int oldStyle = this.targetStyle;
this.targetStyle = targetStyle;
firePropertyChange(IWidgetProperties.OBJECT_DRAWING_STYLE, oldStyle, targetStyle);
}
/**
* @return the targetStyle
*/
public int getTargetStyle() {
checkWidget();
return targetStyle;
}
/**
* Adds the given activation to the list of activations.
* @param a
*/
void addActivation(Activation a) {
checkWidget();
if (a.getLifeline() == this) {
return;
}
boolean hiddenState = hiddenCount == activations.size();
activations.add(a);
if (a.isHidden()) {
hiddenCount++;
}
a.addPropertyChangeListener(hideListener);
orderedActivations = null;
firePropertyChange(IWidgetProperties.HIDDEN, hiddenState, hiddenCount == activations.size());
}
void removeActivation(Activation a) {
checkWidget();
if (a.getLifeline() != this) {
return;
}
boolean hiddenState = hiddenCount == activations.size();
activations.remove(a);
if (a.isHidden()) {
hiddenCount--;
}
a.addPropertyChangeListener(hideListener);
orderedActivations = null;
firePropertyChange(IWidgetProperties.HIDDEN, hiddenState, hiddenCount == activations.size());
}
/**
* The hidden state of a lifeline is not controlled by the lifeline itself, but
* by the hidden state of all of the activations on the lifeline.
*/
public boolean isHidden() {
checkWidget();
return hiddenCount == activations.size();
}
/**
* This method does nothing because the hidden state is dependent on the hidden state of the
* activations in this life line.
*/
protected void hide() {
}
/**
* Returns the all of the activations on this lifeline, and all of its children. The order is not guaranteed.
* @return the activations on this lifeline. The order is not guaranteed.
*/
public Activation[] getAllActivations() {
checkWidget();
List<Activation> allActivations = collectAllActivations();
return allActivations.toArray(new Activation[allActivations.size()]);
}
private List<Activation> collectAllActivations() {
LinkedList<Activation> allActivations = new LinkedList<Activation>();
allActivations.addAll(activations);
if (children != null) {
for (Lifeline child : children) {
allActivations.addAll(child.collectAllActivations());
}
}
return allActivations;
}
/**
* Returns the activations that are found on this lifeline only (not on any of
* its children).
* @return the activations that are found on this lifeline.
*/
public Activation[] getActivations() {
checkWidget();
return activations.toArray(new Activation[activations.size()]);
}
/**
* Returns a list of the non-hidden activations in the order that they appear on the screen
* from top to bottom. Returns all activations that will be visible on this lifeline,
* that is its activations and all of its children's activations.
* @return a list of the non-hidden activations in the order that they appear on the screen
* from top to bottom.
*/
public Activation[] getOrderedActivations() {
checkWidget();
if (orderedActivations == null) {
TreeSet<Activation> orderedSet = new TreeSet<Activation>(new Comparator<Activation>(){
public int compare(Activation o1,Activation o2){
Rectangle layout1 = (Rectangle) o1.getData(IWidgetProperties.LAYOUT);
Rectangle layout2 = (Rectangle) o2.getData(IWidgetProperties.LAYOUT);
if (layout1 == null && layout2 == null) {
return 0;
}
if (layout2 == null) {
return -1;
}
if (layout1 == null) {
return 1;
}
return layout1.getTop().y - layout2.getTop().y;
}
});
for (Activation a :activations) {
if (a.isVisible() && !a.isHidden()) {
orderedSet.add(a);
}
}
orderedActivations = orderedSet.toArray(new Activation[orderedSet.size()]);
}
return orderedActivations;
}
/**
* Sets the parent of this lifeline to the given parent.
* @param parent
*/
private void setParent(Lifeline parent) {
//make sure that it won't introduce a cycle
if (parent == this.parent) {
return;
}
Lifeline currentParent = parent;
while (currentParent != null && !currentParent.isDisposed()) {
if (currentParent == this) {
throw new IllegalArgumentException("parent " + parent + " introduces a cycle.");
}
if (currentParent.isDisposed()) {
throw new IllegalArgumentException("parent " + parent + " creates a broken hierarchy");
}
currentParent = currentParent.getParent();
}
if (this.parent != null && !this.parent.isDisposed()) {
this.parent.removeChild(this);
}
this.parent = parent;
}
/**
* Removes the given lifeline from the list of children for this lifeline.
* @param lifeline
*/
private void removeChild(Lifeline lifeline) {
checkWidget();
if (children != null && children.length > 0) {
Lifeline[] newchildren = new Lifeline[children.length-1];
//int i=0, j=0;
for (int i=0, j =0; i < children.length; i++) {
if (j >= newchildren.length) {
//the lifeline wasn't found. just return.
return;
}
if (children[i] != lifeline) {
newchildren[j] = children[i];
j++;
}
}
children = newchildren;
invalidate();
firePropertyChange(IWidgetProperties.CHILD, lifeline, null);
getChart().markDirty();
}
}
/**
* Groups the given lifeline under this lifeline.
* @param lifeline
*/
public void addChild(Lifeline lifeline) {
checkWidget();
if (lifeline == null) {
return;
}
if (lifeline.parent == this) {
//don't add it if it is already parented here.
return;
}
try {
lifeline.setParent(this);
} catch (IllegalArgumentException e) {
//just don't add it if this is an illegal parent.
return;
}
if (children == null) {
children = new Lifeline[] {lifeline};
} else {
Lifeline[] newchildren = new Lifeline[children.length + 1];
System.arraycopy(children, 0, newchildren, 0, children.length);
newchildren[children.length] = lifeline;
children = newchildren;
}
invalidate();
firePropertyChange(IWidgetProperties.CHILD, null, lifeline);
getChart().markDirty();
}
/**
* Walk up the parent hierarchy, resetting the ordered activations so that
* the activations visible on this lifeline will be reset.
*/
private void invalidate() {
this.orderedActivations = null;
if (parent != null) {
parent.invalidate();
}
}
/**
* @return the stereotype text for the head of the lifeline.
*/
public String getStereoType() {
return stereotype;
}
/**
* Sets the stereotype for the object
* @param stereotype the new stereotype.
*/
public void setStereotype(String stereotype) {
checkWidget();
String oldStereotype = this.stereotype;
this.stereotype = stereotype;
firePropertyChange(IWidgetProperties.STEREOTYPE, oldStereotype, stereotype);
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.widgets.IExpandableItem#isExpanded()
*/
public boolean isExpanded() {
if (this.children == null || this.children.length == 0) {
return false;
}
return expanded;
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.widgets.IExpandableItem#setExpanded(boolean)
*/
public void setExpanded(boolean expanded) {
if (!hasChildren() || expanded == this.expanded) {
return;
}
this.expanded = expanded;
getChart().markDirty();
firePropertyChange(IWidgetProperties.EXPANDED, !expanded, expanded);
}
boolean hasChildren() {
return (children != null && children.length > 0);
}
/**
* @return the children
*/
public Lifeline[] getChildren() {
if (hasChildren()) {
return children;
}
return new Lifeline[0];
}
/**
* The parent lifeline for this lifeline. Allows lifelines to be grouped together.
* @return the parent lifeline, or null if none.
*/
public Lifeline getParent() {
return parent;
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.widgets.UMLItem#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
*/
@Override
protected void widgetDisposed(DisposeEvent e) {
if (parent != null && !parent.isDisposed()) {
//remove this item from its parent.
parent.removeChild(this);
}
//set all the children's parents to null
Lifeline[] oldChildren = children;
children = null;
if (oldChildren != null) {
for (Lifeline child : oldChildren) {
child.setParent(null);
}
}
super.widgetDisposed(e);
}
}