UMLSequenceDiagram.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2009-2012 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.Point;
import java.awt.Rectangle;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.argouml.i18n.Translator;
import org.argouml.model.CollaborationsHelper;
import org.argouml.model.Facade;
import org.argouml.model.Model;
import org.argouml.model.SequenceDiagram;
import org.argouml.uml.diagram.DiagramElement;
import org.argouml.uml.diagram.DiagramSettings;
import org.argouml.uml.diagram.static_structure.ui.FigComment;
import org.argouml.uml.diagram.ui.ActionSetMode;
import org.argouml.uml.diagram.ui.FigNodeModelElement;
import org.argouml.uml.diagram.ui.RadioAction;
import org.argouml.uml.diagram.ui.UMLDiagram;
import org.tigris.gef.base.Editor;
import org.tigris.gef.base.Globals;
import org.tigris.gef.base.LayerPerspective;
import org.tigris.gef.base.LayerPerspectiveMutable;
import org.tigris.gef.base.ModePlace;
import org.tigris.gef.graph.GraphFactory;
import org.tigris.gef.graph.GraphModel;
import org.tigris.gef.graph.MutableGraphModel;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigNode;

/**
 * The diagram for sequence diagrams.
 *
 * @author penyaskito
 */
public class UMLSequenceDiagram extends UMLDiagram
        implements org.argouml.uml.diagram.SequenceDiagram, SequenceDiagram {

    private Object[] actions;

    private static final Logger LOG =
        Logger.getLogger(UMLSequenceDiagram.class.getName());

    /**
     * TODO: Document!
     *
     * @deprecated for 0.28 by tfmorris.  Use
     * {@link #UMLActivityDiagram(String, Object, GraphModel)}.
     */
    @Deprecated
    public UMLSequenceDiagram() {
        super();
        // Create the graph model
        MutableGraphModel gm = new SequenceDiagramGraphModel();
        setGraphModel(gm);

        // Create the layer
        LayerPerspective lay = new
            LayerPerspectiveMutable(this.getName(), gm);
        setLayer(lay);

        // Create the renderer
        SequenceDiagramRenderer renderer = new SequenceDiagramRenderer();
        lay.setGraphNodeRenderer(renderer);
        lay.setGraphEdgeRenderer(renderer);

        LOG.log(Level.FINE, "Created sequence diagram");
    }

    /**
     * Creates a new UmlSequenceDiagram with a collaboration.
     * @param collaboration The collaboration
     *
     */
    public UMLSequenceDiagram(Object collaboration) {
        this();
        LOG.log(Level.FINE,
                "Constructing Sequence Diagram for collaboration {0}",
                collaboration);
        try {
            this.setName(getNewDiagramName());
        } catch (PropertyVetoException e) {
            LOG.log(Level.SEVERE, "Exception", e);
        }
        ((SequenceDiagramGraphModel) getGraphModel()).
            setCollaboration(collaboration);
        setNamespace(collaboration);
    }


    /**
     * Method called by PGML parser during diagram load to initialize a diagram.
     * We are passed the owner of that diagram which is the collaboration.
     * @param owner UML model element representing the collaboration
     * @see org.tigris.gef.base.Diagram#initialize(java.lang.Object)
     */
    @Override
    public void initialize(Object owner) {
        super.initialize(owner);
        SequenceDiagramGraphModel gm =
            (SequenceDiagramGraphModel) getGraphModel();
        gm.setCollaboration(owner);
    }




    /**
     * Get the Uml actions that can be performed in the diagram
     * @return An array with the Uml actions
     * @see org.argouml.uml.diagram.ui.UMLDiagram#getUmlActions()
     */
    @Override
    protected Object[] getUmlActions() {
        if (actions == null) {
            List actionList = new ArrayList();
            actionList.add(new RadioAction(new ActionAddClassifierRole()));
            getMessageActions(actionList);
            actionList.add(new RadioAction(new ActionSetMode(
                    ModeBroomMessages.class,
                    "button.broom-messages")));
            actions = new Object[8];
            int i = 0;
            actions = actionList.toArray();

        }
        return actions;
    }

    private List getMessageActions(List actions) {
        actions.add(new RadioAction(new ActionSetAddMessageMode(
                Model.getMessageSort().getSynchCall(),
                "button.new-callaction")));
        actions.add(new RadioAction(new ActionSetAddMessageMode(
                Model.getMessageSort().getASynchSignal(),
                "button.new-sendaction")));
        actions.add(new RadioAction(new ActionSetAddMessageMode(
                Model.getMessageSort().getReply(),
                "button.new-returnaction")));
        actions.add(new RadioAction(new ActionSetAddMessageMode(
                Model.getMessageSort().getCreateMessage(),
                "button.new-createaction")));
        actions.add(new RadioAction(new ActionSetAddMessageMode(
                Model.getMessageSort().getDeleteMessage(),
                "button.new-destroyaction")));
        return actions;
    }

    /**
     * Get the localized label name for the diagram
     * @return The localized label name for the diagram
     * @see org.argouml.uml.diagram.ui.UMLDiagram#getLabelName()
     */
    @Override
    public String getLabelName() {
        return Translator.localize("label.sequence-diagram");
    }

    @Override
    public void encloserChanged(FigNode enclosed, FigNode oldEncloser,
            FigNode newEncloser) {
    	// Do nothing.
    }

    @Override
    public boolean isRelocationAllowed(Object base)  {
    	return Model.getFacade().isACollaboration(base);
    }

    @SuppressWarnings("unchecked")
    public Collection getRelocationCandidates(Object root) {
        return
        Model.getModelManagementHelper().getAllModelElementsOfKindWithModel(
            root, Model.getMetaTypes().getCollaboration());
    }

    @Override
    public boolean relocate(Object base) {
        ((SequenceDiagramGraphModel) getGraphModel())
	    	.setCollaboration(base);
        setNamespace(base);
        damage();
        return true;
    }

    /**
     * A sequence diagram can accept all classifiers. It will add them as a new
     * Classifier Role with that classifier as a base.
     * @param objectToAccept element to test for acceptability
     * @return true if the element is acceptable
     * @see org.argouml.uml.diagram.ui.UMLDiagram#doesAccept(java.lang.Object)
     */
    @Override
    public boolean doesAccept(Object objectToAccept) {
        if (Model.getFacade().isALifeline(objectToAccept)) {
            return true;
        } else if (Model.getFacade().isAClassifier(objectToAccept)) {
            return true;
        } else if (Model.getFacade().isAComment(objectToAccept)) {
            return true;
        }
        return false;
    }

    /**
     * Creates a new Classifier Role with a specified base.
     * @param base
     * @return The new CR
     */
    private Object makeNewCR(Object base) {
        Object node = null;
        Editor ce = Globals.curEditor();
        GraphModel gm = ce.getGraphModel();
        if (gm instanceof SequenceDiagramGraphModel) {
            Object collaboration =
                ((SequenceDiagramGraphModel) gm).getCollaboration();
            node =
                Model.getCollaborationsFactory().buildClassifierRole(
                        collaboration);
        }
        Model.getCollaborationsHelper().addBase(node, base);

        return node;
    }

    /**
     * Creates the Fig for the CR. Y position will be adjusted to match other
     * the other CRs.
     * @param classifierRole
     * @param location The position where to put the new fig.
     * @return
     */
    private FigClassifierRole makeNewFigCR(Object classifierRole,
            Point location) {
        if (classifierRole != null) {
            Rectangle bounds = new Rectangle();

            // Y position of the new CR should match existing CRs Y position
            for (Fig fig : (List<Fig>) getLayer().getContentsNoEdges()) {
                if (fig instanceof FigClassifierRole) {
                    bounds.y = fig.getY();
                    bounds.height = fig.getHeight();
                    break;
                }
            }
            if (location != null) {
                if (bounds.y == 0) {
                    bounds.y = location.y;
                }
                bounds.x = location.x;
            }

            FigClassifierRole newCR = new FigClassifierRole(classifierRole,
                    bounds, getDiagramSettings());
            getGraphModel().getNodes().add(newCR.getOwner());

            return newCR;
        }
        return null;
    }

    public DiagramElement createDiagramElement(
            final Object modelElement,
            final Rectangle bounds) {

        FigNodeModelElement figNode = null;

        DiagramSettings settings = getDiagramSettings();

        if (Model.getFacade().isAComment(modelElement)) {
            figNode = new FigComment(modelElement, bounds, settings);
        } else if (Model.getFacade().isAClassifierRole(modelElement)) {
            if (!getGraphModel().getNodes().contains(modelElement)) {
                figNode = makeNewFigCR(modelElement, null);
            }
        } else if (Model.getFacade().isAClassifier(modelElement)) {
            figNode = makeNewFigCR(
                    makeNewCR(modelElement),
                    bounds.getLocation());
        }

        if (figNode != null) {
            LOG.log(Level.FINE,
                    "Model element {0} converted to {1}",
                    new Object[]{modelElement, figNode});
        } else {
            LOG.log(Level.FINE, "Dropped object NOT added {0}", modelElement);
        }
        return figNode;
    }


    @Override
    public String getInstructions(Object droppedObject) {
    	if (Model.getFacade().isAClassifierRole(droppedObject)) {
    	    return super.getInstructions(droppedObject);
    	} else if (Model.getFacade().isAClassifier(droppedObject)) {
            return Translator.localize(
                    "misc.message.click-on-diagram-to-add-as-cr",
                    new Object[] {Model.getFacade().toString(droppedObject)});
        }
        return super.getInstructions(droppedObject);
    }

    @Override
    public ModePlace getModePlace(GraphFactory gf, String instructions) {
        return new ModePlaceClassifierRole(gf, instructions);
    }

    public Object getCollaboration() {
        return ((SequenceDiagramGraphModel) getGraphModel()).getCollaboration();
    }

    /**
     * Ensure that all elements represented in this diagram are part of this
     * diagrams collaboration
     */
    public void postLoad() {
        super.postLoad();

        final Facade facade = Model.getFacade();
        LOG.log(Level.INFO, "doing postLoad on {0}", getName());

        // See issue 5811. We have collaborationroles, associationroles
        // and messages and actions saved to the incorrect interaction and
        // and collaboration. If we detect this circumstance at load then
        // move the model elements and delete the empty collaborations
        // and interactions.
        final Object collaboration = getCollaboration();
        Object correctInteraction = null;
        for (final Fig f : getLayer().getContents()) {
            final Object modelElement = f.getOwner();
            if (f instanceof FigMessage) {
                final Object interaction = facade.getInteraction(modelElement);
                final Object context = facade.getContext(interaction);
                if (context == collaboration) {
                    correctInteraction = interaction;
                }
            }
        }
        if (correctInteraction != null) {
            final CollaborationsHelper collabHelper =
                Model.getCollaborationsHelper();
            for (final Fig f : getLayer().getContents()) {
                if (f instanceof FigMessage) {
                    final Object message = f.getOwner();
                    LOG.log(Level.INFO, "Checking message {0}", f.getOwner());

                    final Object interaction = facade.getInteraction(message);
                    final Object context = facade.getContext(interaction);
                    final Object action = facade.getAction(message);
                    if (context != collaboration) {
                        LOG.log(Level.WARNING,
                                "namespace of interaction does not match "
                                + "collaboration - moving "
                                + message + " to " + correctInteraction);
                        collabHelper.addMessage(correctInteraction, message);
                        Model.getCoreHelper().setNamespace(
                                action, collaboration);
                    }
                } else if (f instanceof FigClassifierRole) {
                    final Object cr = f.getOwner();
                    final Object namespace = facade.getNamespace(cr);
                    if (namespace != collaboration) {
                        LOG.log(Level.WARNING,
                                "namespace of classifierrole does not match "
                                + "collaboration - moving "
                                + cr + " to " + collaboration);

                        Model.getCoreHelper().setNamespace(
                                cr, collaboration);

                        Collection associationEndRoles =
                            facade.getAssociationEnds(cr);
                        for (Object assEndRole : associationEndRoles) {
                            Object assRole = facade.getAssociation(assEndRole);
                            if (facade.getNamespace(assRole) != collaboration) {
                                Model.getCoreHelper().setNamespace(
                                        assRole, collaboration);
                            }
                        }
                    }
                }
            }
        }
    }
}