/* $Id: FigMessage.java 18775 2010-09-23 16:39:06Z bobtarling $ ***************************************************************************** * Copyright (c) 2009 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: * bobtarling ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // 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.Color; import java.awt.Point; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Vector; import javax.swing.JSeparator; import org.apache.log4j.Logger; import org.argouml.model.Model; import org.argouml.model.UmlChangeEvent; import org.argouml.notation.Notation; import org.argouml.notation.NotationProvider; import org.argouml.notation.NotationProviderFactory2; import org.argouml.notation.SDNotationSettings; import org.argouml.ui.ArgoJMenu; import org.argouml.uml.diagram.DiagramSettings; import org.argouml.uml.diagram.ui.FigEdgeModelElement; import org.argouml.uml.diagram.ui.FigTextGroup; import org.argouml.uml.diagram.ui.PathItemPlacement; import org.tigris.gef.base.Selection; import org.tigris.gef.presentation.ArrowHeadGreater; import org.tigris.gef.presentation.ArrowHeadTriangle; import org.tigris.gef.presentation.Fig; import org.tigris.gef.presentation.FigNode; import org.tigris.gef.presentation.FigPoly; import org.tigris.gef.presentation.FigText; /** * The Fig that represents a message between classifier roles. * @author penyaskito */ public class FigMessage extends FigEdgeModelElement { private static final long serialVersionUID = -2961220746360335159L; private static final Logger LOG = Logger.getLogger(FigEdgeModelElement.class); private FigTextGroup textGroup; /** * The action owned by the message */ private Object action = null; private SDNotationSettings notationSettings; /** * Construct a fig owned by the given UML element with the provided render * settings. * @param edge owning UML element * @param settings rendering settings */ public FigMessage(Object edge, DiagramSettings settings) { super(edge, settings); textGroup = new FigTextGroup(edge, settings); initialize(); action = Model.getFacade().getAction(getOwner()); updateArrow(); if (action != null) { addElementListener(action, "isAsynchronous"); } } private void initialize() { textGroup.addFig(getNameFig()); textGroup.addFig(getStereotypeFig()); addPathItem(textGroup, new PathItemPlacement(this, textGroup, 50, 10)); notationSettings = new SDNotationSettings(); } @Override protected int getNotationProviderType() { /* Use a different notation as Messages on a collaboration diagram: */ return NotationProviderFactory2.TYPE_SD_MESSAGE; } /* This next argument may be used to switch off * the generation of sequence numbers - this is * still to be implemented. * They are less desired in sequence diagrams, * since they do not add any information. * In collaboration diagrams they are needed, * and they are still optional in sequence diagrams. */ @Override protected void initNotationProviders(Object own) { super.initNotationProviders(own); notationSettings.setShowSequenceNumbers(false); } @Override protected void textEditStarted(FigText ft) { /* This is a temporary hack until the notation provider * for a SD Message will be able to parse successfully when the sequence * number is missing. * Remove this method completely then.*/ notationSettings.setShowSequenceNumbers(true); super.textEditStarted(ft); notationSettings.setShowSequenceNumbers(false); } protected SDNotationSettings getNotationSettings() { return notationSettings; } boolean isCallAction() { return Model.getFacade().isACallAction(getAction()); } boolean isCreateAction() { return Model.getFacade().isACreateAction(getAction()); } boolean isDestroyAction() { return Model.getFacade().isADestroyAction(getAction()); } boolean isReturnAction() { return Model.getFacade().isAReturnAction(getAction()); } boolean isSendAction() { return Model.getFacade().isASendAction(getAction()); } /** * Updates the arrow head and the arrow line according * to the action type.. */ private void updateArrow() { getFig().setDashed(isReturnAction()); Object act = getAction(); if (act != null && Model.getFacade().isAsynchronous(getAction())) { setDestArrowHead(new ArrowHeadGreater()); } else { setDestArrowHead(new ArrowHeadTriangle()); } getDestArrowHead().setLineColor(getLineColor()); getDestArrowHead().setFillColor(getLineColor()); } /** * Gets the action attached to the message. * @return the action * @deprecated This method is only called internally. If its needed at all * it will become private. External callers can test for action type using * isCallAction etc. If absolutely required the actions can be accessed * via the model. */ public Object getAction() { return action; } /** * @param me the MouseEvent that triggered the popup menu request * @return a Vector containing a combination of these 4 types: Action, * JMenu, JMenuItem, JSeparator. */ @Override public Vector getPopUpActions(MouseEvent me) { Vector popUpActions = super.getPopUpActions(me); // Operations ... if (Model.getFacade().isACallAction(getAction())) { ArgoJMenu opMenu = buildOperationMenu(); int index = popUpActions.size() - getPopupAddOffset() - 1; if (index < 0) { index = 0; } popUpActions.add(index, new JSeparator()); popUpActions.add(index, opMenu); } return popUpActions; } protected ArgoJMenu buildOperationMenu() { ArgoJMenu opMenu = new ArgoJMenu("Operation"); Iterator<Object> iter = getReceiverOperations().iterator(); opMenu.setEnabled(iter.hasNext()); while (iter.hasNext()) { Object op = iter.next(); NotationProvider np = null; try { String s = getNotationSettings().getNotationLanguage(); np = NotationProviderFactory2.getInstance() .getNotationProvider( NotationProviderFactory2.TYPE_OPERATION, op, Notation.findNotation(s)); } catch (Exception e) { //TODO: add logging, but this will never happen and is handled np = null; } String label = (np != null) ? np.toString(op, getNotationSettings()) : Model.getFacade().getName(op); opMenu.add(new ActionSetOperation(getAction(), op, label)); } return opMenu; } @Override public Selection makeSelection() { return new SelectionMessage(this); } /* * @see org.tigris.gef.presentation.FigEdge#setFig(org.tigris.gef.presentation.Fig) */ public void setFig(Fig f) { super.setFig(f); updateArrow(); } int getFinalY() { int finalY = 0; Point[] points = getFig().getPoints(); if (points.length > 0) { finalY = points[points.length - 1].y; } return finalY; } int getStartY() { int finalY = 0; Point[] points = getFig().getPoints(); if (points.length > 0) { finalY = points[0].y; } return finalY; } /** * Checks if the message source and dest are the same. * @return true if they are the same, otherwise false. */ boolean isSelfMessage() { // If possible we determine this by checking the destination // and source Figs are the same. If this is not possible // because the edge is not yet connected then we check the // model. if (getDestPortFig() == null || getSourcePortFig() == null) { return getDestination().equals(getSource()); } else { return (getDestPortFig().equals(getSourcePortFig())); } } /** * Converts the message into a spline. * This is needed for self-referencing messages. */ public void convertToArc() { if (getPoints().length > 0) { FigMessageSpline spline = new FigMessageSpline(getPoint(0)); spline.setDashed(isReturnAction()); super.setFig(spline); computeRoute(); } } @Override public void computeRouteImpl() { super.computeRouteImpl(); updateActivations(); } public void calcBounds() { final FigPoly fp = (FigPoly) getFig(); final FigNode node = getSourceFigNode(); if (node != null && isSelfMessage() && fp.isComplete()) { // TODO: calcBounds is called by SelectionManager when the Fig is // dragged. This code is needed to reposition any self message // as they are become detached from their classifier role // (see issue 5562). The cause of the detachment is not yet // understood. // Unfortunately calcBounds is called from several other places // so the code here is not optimal but is the best workaround until // ArgoUML can provide its own replacement SelectionManager for // sequence diagram requirements // See - http://gef.tigris.org/issues/show_bug.cgi?id=344 final int x = node.getX() + (node.getWidth() + FigActivation.DEFAULT_WIDTH) / 2; final Point startPoint = new Point(x, getYs()[0]); final FigMessageSpline spline = new FigMessageSpline(startPoint); spline.setComplete(true); spline.setDashed(isReturnAction()); super.setFig(spline); } super.calcBounds(); } private synchronized void updateActivations() { // we update the activations... FigClassifierRole source = (FigClassifierRole) getSourceFigNode(); if (source != null) { source.createActivations(); } // for performance, we check if this is a selfmessage // if it is, we have just updated the activations if (!isSelfMessage()) { FigClassifierRole dest = (FigClassifierRole) getDestFigNode(); if (dest != null) { dest.createActivations(); } } } @Override public void setLineColor(Color c) { super.setLineColor(c); getDestArrowHead().setFillColor(c); } @Override public void deleteFromModel() { ((FigClassifierRole) getSourceFigNode()).createActivations(); if (!isSelfMessage()) { ((FigClassifierRole) getDestFigNode()).createActivations(); } super.deleteFromModel(); } /** * The default behaviour from FigEdgeModelElement is not correct * here. See issue 5005. TODO: We must determine what to do here but for * now doing nothing is better. I'm not sure why the super method would * not work as I would expect that to do nothing if the ends are already * correct. * @return true at all times for now */ @Override protected boolean determineFigNodes() { return true; } @Override public void translate(int dx, int dy) { if (isSelfMessage()) { ((FigMessageSpline) getFig()).translateFig(dx, dy); } super.translate(dx, dy); } /** * {@inheritDoc} * @see org.argouml.uml.diagram.ui.FigEdgeModelElement#updateLayout(org.argouml.model.UmlChangeEvent) */ @Override public void updateLayout(UmlChangeEvent event) { if ("isAsynchronous".equals(event.getPropertyName())) { updateArrow(); } super.updateLayout(event); } /* * Overridden purely to keep our superclass from removing the listener * that we just added. * * @see org.argouml.uml.diagram.ui.FigEdgeModelElement#updateListeners(java.lang.Object, java.lang.Object) */ @Override protected void updateListeners(Object oldOwner, Object newOwner ) { action = Model.getFacade().getAction(newOwner); Set<Object[]> listeners = new HashSet<Object[]>(); Object action = getAction(); listeners.add(new Object[] {getOwner(), "remove"}); if (action != null) { listeners.add(new Object[] {action, "isAsynchronous"}); } try { updateElementListeners(listeners); } catch (Exception e) { // This call seems not very robust. Yet to determine cause. LOG.error("Exception caught", e); } } private Collection<Object> getReceiverOperations() { ArrayList<Object> opList = new ArrayList<Object>(); Object action = getAction(); Object receiver = Model.getFacade().getReceiver(getOwner()); if (action != null && receiver != null) { //TODO: What can we do with other kind of actions? if (Model.getFacade().isACallAction(action)) { Iterator bases = Model.getFacade().getBases(receiver).iterator(); while (bases.hasNext()) { Object base = bases.next(); opList.addAll(Model.getFacade().getOperations(base)); } } } return opList; } /** * Determines the activator of this message based on the message position * in relation to other messages. * <p>Currently this only manages return messages. Any other message type * returns with no action taking place. * <p>The activator is set to the first call or create message found above * this message. * @return the activator that has been applied to the message. */ public Object determineActivator() { final FigClassifierRole fcr = (FigClassifierRole) getSourceFigNode(); final List<FigMessage> messageFigs = fcr.getFigMessages(); final Iterator<FigMessage> it = messageFigs.iterator(); Object activator = null; while (it.hasNext()) { FigMessage messageFig = it.next(); if ((messageFig.isCreateAction() || messageFig.isCallAction()) && messageFig.getDestFigNode() == fcr) { activator = messageFig.getOwner(); } else if (messageFig == this) { Model.getCollaborationsHelper().setActivator( getOwner(), activator); return activator; } else if (messageFig.isReturnAction()) { activator = null; } } return null; } }