/******************************************************************************* * 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.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.zest.custom.sequence.ZestUMLColors; import org.eclipse.zest.custom.sequence.widgets.internal.IWidgetProperties; /** * An activation represents a method execution within a sequence diagram. * @author Del Myers */ public class Activation extends UMLColoredItem implements IExpandableItem { /** * This activation is expanded. */ private boolean expanded; private LinkedList<Message> messageList; /** * List of messages that have this activation as a target. */ private LinkedList<Message> targetMessages; private Message[] messages; private MessageGroup[] messageGroups; private LinkedList<MessageGroup> messageGroupList; private Lifeline target; private Call call; /** * @param parent * @param style */ public Activation(UMLChart parent) { super(parent); expanded = false; setBackground(ZestUMLColors.ColorActivation.getColor()); setForeground(parent.getForeground()); this.messageList = null; messageGroupList = new LinkedList<MessageGroup>(); messages = null; targetMessages = new LinkedList<Message>(); } /* (non-Javadoc) * @see org.eclipse.mylar.zest.custom.sequence.widgets.UMLItem#widgetDisposed(org.eclipse.swt.events.DisposeEvent) */ @Override protected void widgetDisposed(DisposeEvent e) { setLifeline(null); if (messageList != null) { for (Message m : messageList.toArray(new Message[messageList.size()])) { if (!m.isDisposed()) { if (m.getSource() == this) { m.setSource(null); } else if (m.getTarget() == this) { m.setTarget(null); } } } } messageList = null; messages = null; for (MessageGroup g : messageGroupList.toArray(new MessageGroup[messageGroupList.size()])) { if (!g.isDisposed()) { g.setRange(null, 0, 0); } } messageGroupList = null; messageGroups = null; super.widgetDisposed(e); } /** * @return the expanded state of this activation */ public boolean isExpanded() { return expanded; } public void setExpanded(boolean expanded) { if (this.expanded == expanded) return; this.expanded = expanded; getChart().markDirty(); firePropertyChange(IWidgetProperties.EXPANDED, !expanded, expanded); if ((getSequenceChart().getStyle() & SWT.VIRTUAL) != 0 && messageList == null) { //make sure that after this call, the children state will be correct. messageList = new LinkedList<Message>(); firePropertyChange(IWidgetProperties.MESSAGE, null, new Object()); } } public UMLSequenceChart getSequenceChart() { return (UMLSequenceChart) getChart(); } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Widget#toString() */ @Override public String toString() { return "Activation " + getText() + " on " + ((target == null) ? "null" : target.toString()); } public boolean hasChildren() { if ((getSequenceChart().getStyle() & SWT.VIRTUAL) != 0 && messageList == null) return true; if (messageList == null) { return false; } for (Message m : getMessages()) { if (m instanceof Call) { return true; } } return messageGroupList.size() > 0; //return false; } /** * Clears all the messages from this activation, and sets the item to have no children. */ public void clearAllMessages() { for (Message m : getMessages()) { removeMessage(m); } } /** * Adds the given message as a message originating from this activation, at the given index. * If the message is a Call, then the target is reparented to this activation. An Activation * be targeted by only one call, and the old call will be removed completely from the * call stack (though it will not be disposed). If it is a return, then the message is simply connected * to the given target activation. That is, an activation can be targeted by multiple returns. * Note that having a message return to an activation that isn't on the call stack will * result in an exception being thrown when the chart is refreshed. * @param index the index at which to add the message * @param message the message to add. * @param target the activation that should be targeted. */ public void addMessage(int index, Message message, Activation target) { checkWidget(); if (message == null || message.isDisposed()) { return; } if (target == this) { throw new IllegalArgumentException("Activation cannot target itself"); } if (messageList == null) { messageList = new LinkedList<Message>(); } if (index > messageList.size() || index < -1) { SWT.error(SWT.ERROR_INVALID_RANGE, null, "index is out of range"); } if (this.equals(message.getSource())) { //make sure that it is in the right order boolean changed = false; if (messageList.get(index) != message) { int i = messageList.indexOf(message); messageList.remove(i); messageList.add(index, message); changed = true; } if (message.getTarget() != target) { if (message instanceof Call) { target.setCall((Call)message); } message.setTarget(target); changed = true; } if (changed) { messages = null; getChart().markDirty(); firePropertyChange(IWidgetProperties.MESSAGE, null, message); } } else { message.setSource(this); if (message instanceof Call) { target.setCall((Call)message); message.setTarget(target); } else { message.setTarget(target); } if (index == -1 || index == messageList.size()) { messageList.add(message); } else { messageList.add(index, message); } messages = null; getChart().markDirty(); firePropertyChange(IWidgetProperties.MESSAGE, null, message); } } private void setCall(Call call) { if (this.call != null) { call.getSource().removeMessage(call); call.setTarget(null); } this.call = call; } /** * Removes the given message from the list of messages extending to/from this * activation. * @param message */ public void removeMessage(Message message) { checkWidget(); if (messageList == null) { return; } if (!messageList.remove(message)) { //don't continue processing if the message didn't exist in the list. return; } messages = null; if (!message.isDisposed()) { if (message.getSource() == this) { message.setSource(null); } else if (message.getTarget() == this) { message.setTarget(null); } else { return; } } if (messageList.size() == 0) { messageList = null; } getChart().markDirty(); firePropertyChange(IWidgetProperties.MESSAGE, message, null); } /** * Gets the call that was used to reach this activation. * @return the call that was used to reach this activation. */ public Call getSourceCall() { return call; } /** * Returns all messages from this activation. Clients should not change the ordering * of the messages in the returned array, as this causes undefined results. * @return references to the messages in this activation. */ public Message[] getMessages() { checkWidget(); if (messageList == null) { return new Message[0]; } if (messages == null) { messages = new Message[messageList.size()]; messages = messageList.toArray(messages); for (int i = 0; i < messages.length; i++) { messages[i].setIndexInActivation(i); } } return messages; } public MessageGroup[] getMessageGroups() { checkWidget(); if (messageGroupList == null) { return new MessageGroup[0]; } if (messageGroups == null) { messageGroups = messageGroupList.toArray(new MessageGroup[messageGroupList.size()]); Arrays.sort(messageGroups, new Comparator<MessageGroup>(){ public int compare(MessageGroup o1, MessageGroup o2) { int diff = o1.getOffset() - o2.getOffset(); if (diff == 0) { diff = o2.getLength() - o1.getLength(); } return diff; } }); } return messageGroups; } /** * Returns the index for the given message in this activation, or -1 if it is not in this * activation. * @param message the message to check. * @return the index of the message. */ public int getMessageIndex(Message message) { if (message.getSource() != this) { return -1; } //refresh the indexes. getMessages(); return message.getIndexInActivation(); } /** * Returns the lifeline that this activation is on. * @return */ public Lifeline getLifeline() { checkWidget(); return target; } /** * Returns the nearest lifeline in the lifeline hierarchy that is collapsed and * visible. * @return the nearest lifeline in the lifeline hierarchy that is collapsed and * visible. */ public Lifeline getVisibleLifeline() { Lifeline top = getLifeline(); Lifeline current = top; while (current != null) { if (!current.isExpanded()) { top = current; } current = current.getParent(); } return top; } /** * Resets the lifeline for this activation to the given lifeline. * @param l the lifeline. */ public void setLifeline(Lifeline l) { if (l == getLifeline()) { return; } Lifeline oldTarget = target; try { if (target != null && !target.isDisposed()) { target.removeActivation(this); } } catch (SWTError e) { //unrecoverable error ignore. Most likely, the old target is disposed. } catch (SWTException e) { //unrecoverable error ignore. Most likely, the old target is disposed. } if (l != null) { l.addActivation(this); } target = l; getChart().markDirty(); firePropertyChange(IWidgetProperties.OWNER, oldTarget, target); } /** * Adds the given message group to this activation. The messages that are contained within * the given range of the group will be enclosed inside the group. * @param messageGroup */ void addGroup(MessageGroup messageGroup) { checkWidget(); if (messageGroup.getActivation() != this) { if (!messageGroupList.contains(messageGroup)) { messageGroupList.add(messageGroup); messageGroups = null; } } } /** * Removes the given group from the list of groups in this activation. * @param messageGroup */ void removeGroup(MessageGroup messageGroup) { checkWidget(); if (messageGroupList.remove(messageGroup)) { messageGroups = null; } } /** * @param message */ void removeTargetMessage(Message message) { checkWidget(); if (message.getTarget() != this) return; targetMessages.remove(message); } /** * @param message */ void addTargetMessage(Message message) { checkWidget(); if (message.getTarget() != this) { targetMessages.add(message); } } /** * Returns all messages that are attached to this activation (both as sources and targets) * ordered by their execution. */ List<Message> getVisibleOrderedMessages() { List<Message> orderedList = new ArrayList<Message>(); for (Message m : getMessages()) { if (m.isVisible()) { orderedList.add(m); } } for (Message m : targetMessages) { if (m.isVisible()) { orderedList.add(m); } } Collections.sort(orderedList, new Comparator<Message>(){ public int compare( Message o1, Message o2) { Rectangle r1 = getSequenceChart().getItemBounds(o1); Rectangle r2 = getSequenceChart().getItemBounds(o2); if (r1 == null) { if (r2 == null) { return 0; } return -1; } if (r2 == null) { return 1; } return r1.y-r2.y; } }); return orderedList; } }