SequenceDiagramGraphModel.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) 1996-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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.util.Collection;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.argouml.kernel.ProjectManager;
import org.argouml.model.DeleteInstanceEvent;
import org.argouml.model.Model;
import org.argouml.uml.CommentEdge;
import org.argouml.uml.diagram.UMLMutableGraphSupport;
import org.tigris.gef.base.Editor;
import org.tigris.gef.base.Globals;
import org.tigris.gef.base.Mode;
import org.tigris.gef.base.ModeManager;
import org.tigris.gef.graph.MutableGraphModel;

/**
 * The sequence graph model is the bridge between the UML meta-model
 * representation of the design and the graph model of GEF.
 * @author 5eichler@informatik.uni-hamburg.de
 * @author penyaskito
 */
class SequenceDiagramGraphModel extends UMLMutableGraphSupport implements
        VetoableChangeListener, PropertyChangeListener, MutableGraphModel {

    /**
     * Logger.
     */
    private static final Logger LOG =
        Logger.getLogger(SequenceDiagramGraphModel.class.getName());

    /**
     * The collaboration this sequence diagram belongs too.
     */
    private Object collaboration;

    /**
     * The interaction that is shown on the sequence diagram.
     */
    private Object interaction;


    /**
     * Default constructor. Constructs a model and a collaboration in
     * the root of the current project.
     */
    public SequenceDiagramGraphModel() {
        super();
        // TODO: Auto-generated constructor stub
    }

    /**
     * @param element The Fig that we want his available ports.
     * @return The available ports for a given element
     * @see org.tigris.gef.graph.GraphModel#getPorts(java.lang.Object)
     */
    public List getPorts(Object element) {
        List ports = new LinkedList();
        // if is a classifier role, it must return all the related messages.
        if (Model.getFacade().isAClassifierRole(element)) {
            ports.addAll(Model.getFacade().getReceivedMessages(element));
            ports.addAll(Model.getFacade().getSentMessages(element));
        }
        // if is a message, it must return the sender
        // and the receiver of the message.
        else if (Model.getFacade().isAMessage(element)) {
            ports.add(Model.getFacade().getSender(element));
            ports.add(Model.getFacade().getReceiver(element));
        }
        return ports;
    }

    /**
     * @param element
     * @return The outcoming edges.
     * @see org.tigris.gef.graph.GraphModel#getInEdges(java.lang.Object)
     */
    public List getInEdges(Object element) {
        List ports = new LinkedList();
        // If is a classifier role, it must return the sent messages.
        // In other cases, returns an empty list.
        // TODO: Must be the incoming messages or the sent ones?
        if (Model.getFacade().isAClassifierRole(element)) {
            ports.addAll(Model.getFacade().getSentMessages(element));
        }
        return ports;
    }

    /**
     * @param element
     * @return The incoming edges.
     * @see org.tigris.gef.graph.GraphModel#getOutEdges(java.lang.Object)
     */
    public List getOutEdges(Object element) {
        List ports = new LinkedList();
        // If is a classifier role, it must return the received messages.
        // In other cases, returns an empty list.
        // TODO: Must be the outgoing messages or the received ones?
        if (Model.getFacade().isAClassifierRole(element)) {
            ports.addAll(Model.getFacade().getReceivedMessages(element));
        }
        return ports;
    }

    /**
     * @param port
     * @return
     * @see org.tigris.gef.graph.BaseGraphModel#getOwner(java.lang.Object)
     */
    public Object getOwner(Object port) {
        return port;
    }

    /**
     * Gets the collaboration that is shown on the sequence diagram.<p>
     *
     * @return the collaboration of the diagram.
     */
    public Object getCollaboration() {
        if (collaboration == null) {
            LOG.log(Level.FINE, "The collaboration is null so creating a new collaboration");
            collaboration =
                Model.getCollaborationsFactory().buildCollaboration(
                        ProjectManager.getManager().getCurrentProject()
                        .getRoot());
        }

        return collaboration;
    }

    /**
     * Sets the collaboration that is shown at the sequence diagram.
     * @param c the collaboration
     */
    public void setCollaboration(Object c) {
        LOG.log(Level.FINE, "Setting the collaboration of sequence diagram to {0}", c);
        collaboration = c;
        Collection interactions = Model.getFacade().getInteractions(c);
        if (!interactions.isEmpty()) {
            interaction = interactions.iterator().next();
        }
    }

    /**
     * Gets the interaction that is shown on the sequence diagram.
     * @return the interaction of the diagram.
     */
    private Object getInteraction() {
        if (interaction == null) {
            interaction =
                Model.getCollaborationsFactory().buildInteraction(
                    collaboration);
            LOG.log(Level.FINE, "Interaction built.");
            Model.getPump().addModelEventListener(this, interaction);
        }
        return interaction;
    }

    /**
     * In UML1.4 the sequence diagram is owned by a collaboration.
     * In UML2 it is owned by an Interaction (which might itself be owned by a
     * collaboration or some other namespace)
     * @return the owner of the sequence diagram
     */
    public Object getOwner() {
        if (Model.getFacade().getUmlVersion().charAt(0) == '1') {
            return getCollaboration();
        } else {
            return getInteraction();
        }
    }

    /*
     * @see org.tigris.gef.graph.MutableGraphModel#canAddNode(java.lang.Object)
     */
    @Override
    public boolean canAddNode(Object node) {
        if (node == null) {
            return false;
        }
        if (getNodes().contains(node)) {
            return false;
        }
        if (Model.getFacade().isAComment(node)) {
            // Comments from anywhere in the model are allowed
            return true;
        }
        return Model.getFacade().isAModelElement(node)
                // All other types of elements must be in this namespace
                && Model.getFacade().getNamespace(node) == getCollaboration();
    }

    /*
     * @see org.tigris.gef.graph.MutableGraphModel#canAddEdge(java.lang.Object)
     */
    @Override
    public boolean canAddEdge(Object edge) {
        if (edge == null) {
            return false;
        }

        if (getEdges().contains(edge)) {
            return false;
        }

        Object end0 = null;
        Object end1 = null;

        if (Model.getFacade().isAMessage(edge)) {
            end0 = Model.getFacade().getSender(edge);
            end1 = Model.getFacade().getReceiver(edge);
        } else if (edge instanceof CommentEdge) {
            end0 = ((CommentEdge) edge).getSource();
            end1 = ((CommentEdge) edge).getDestination();
        } else {
            return false;
        }

        // Both ends must be defined and nodes that are on the graph already.
        if (end0 == null || end1 == null) {
            LOG.log(Level.SEVERE, "Edge rejected. Its ends are not attached to anything");
            return false;
        }

        if (!containsNode(end0) && !containsEdge(end0)) {
            LOG.log(Level.SEVERE, "Edge rejected. Its source end is attached to "
                    + end0
                    + " but this is not in the graph model");
            return false;
        }
        if (!containsNode(end1) && !containsEdge(end1)) {
            LOG.log(Level.SEVERE, "Edge rejected. Its destination end is attached to "
                    + end1
                    + " but this is not in the graph model");
            return false;
        }

        return true;
    }

    /*
     * @see org.tigris.gef.graph.MutableGraphModel#addNode(java.lang.Object)
     */
    @Override
    public void addNode(Object node) {
        if (canAddNode(node)) {
            getNodes().add(node);
            fireNodeAdded(node);
        }
    }


    public void vetoableChange(PropertyChangeEvent pce)
        throws PropertyVetoException {
        // TODO: Auto-generated method stub

    }

    /**
     * Look for delete events of the interaction that this diagram
     * represents. Null our interaction reference if detected.
     * @param evt the property change event
     */
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt instanceof DeleteInstanceEvent
                && evt.getSource() == interaction) {
            Model.getPump().removeModelEventListener(this, interaction);
            interaction = null;
        }
    }

    /**
     * Creates a link based on the given from and toPort. The fromPort
     * should always point to a MessageCoordinates instance. The toPort
     * can point to a MessageCoordinates instance or to a Object
     * instance. On a sequence diagram you can only draw Messages. So
     * other edgeClasses then links are not supported.
     * {@inheritDoc}
     * @see org.tigris.gef.graph.MutableGraphModel#connect(
     *          Object, Object, Class)
     */
    @Override
    public Object connect(Object fromPort, Object toPort, Object edgeType) {
        if (!canConnect(fromPort, toPort, edgeType)) {
            return null;
        }
        if (edgeType == CommentEdge.class) {
            return super.connect(fromPort, toPort, edgeType);
        }
        Editor curEditor = Globals.curEditor();
        ModeManager modeManager = curEditor.getModeManager();
        Mode mode = modeManager.top();
        Hashtable args = mode.getArgs();
        Object actionType = args.get("action");
        return connectMessage(fromPort, toPort, actionType);
    }

    /**
     * Creates a link based on the given from and toPort. The fromPort
     * should always point to a MessageCoordinates instance. The toPort
     * can point to a MessageCoordinates instance or to a Object
     * instance. On a sequence diagram you can only draw Messages. So
     * other edgeClasses then links are not supported.
     *
     * @see org.tigris.gef.graph.MutableGraphModel#connect(
     *          Object, Object, Class)
     */
    public Object connectMessage(Object fromPort, Object toPort, Object messageSort) {
        // TODO: Lets move this behind the model interface
        if (Model.getFacade().getUmlVersion().charAt(0) == '1') {
            return createMessage1(fromPort, toPort, messageSort);
        } else {
            return createMessage2(fromPort, toPort, messageSort);
        }
    }

    private Object createMessage1(Object fromPort, Object toPort, Object messageSort) {
        Object edge = null;
        Object action = null;
        if (Model.getMessageSort().getSynchCall().equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createCallAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, false);
        } else if (Model.getMessageSort().getASynchCall().equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createCallAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, true);
        } else if (Model.getMessageSort().getCreateMessage()
                .equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createCreateAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, false);
        } else if (Model.getMessageSort().getReply()
                .equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createReturnAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, true);
        } else if (Model.getMessageSort().getDeleteMessage()
                .equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createDestroyAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, false);
        } else if (Model.getMessageSort().getASynchSignal()
                .equals(messageSort)) {
            action = Model.getCommonBehaviorFactory().createSendAction();
            Model.getCommonBehaviorHelper().setAsynchronous(action, true);
        } else if (Model.getMetaTypes().getTerminateAction()
                .equals(messageSort)) {
            // not implemented yet
        }
        if (fromPort != null && toPort != null) {
            Object associationRole =
                Model.getCollaborationsHelper().getAssociationRole(
                    fromPort,
                    toPort);
            if (associationRole == null) {
                associationRole =
                    Model.getCollaborationsFactory().buildAssociationRole(
                            fromPort, toPort);
            }

            Object message =
                Model.getCollaborationsFactory().buildMessage(
                    getInteraction(),
                    associationRole);
            if (action != null) {
                Model.getCollaborationsHelper().setAction(message, action);
                Model.getCoreHelper().setNamespace(action, getCollaboration());
            }
            Model.getCollaborationsHelper()
                .setSender(message, fromPort);
            Model.getCommonBehaviorHelper()
                .setReceiver(message, toPort);

            addEdge(message);
            edge = message;
        }
        if (edge == null) {
            LOG.log(Level.FINE, "Incorrect edge");
        }
        return edge;

    }

    private Object createMessage2(Object fromPort, Object toPort, Object messageSort) {
        if (fromPort != null && toPort != null) {

            Object message =
                Model.getCollaborationsFactory().buildMessage(
                    fromPort,
                    toPort);
            Model.getCollaborationsHelper().setMessageSort(message, messageSort);

            addEdge(message);
            return message;
        }
        LOG.log(Level.FINE, "Incorrect edge");
        return null;

    }



    /*
     * @see org.tigris.gef.graph.MutableGraphModel#addEdge(java.lang.Object)
     */
    @Override
    public void addEdge(Object edge) {
        if (canAddEdge(edge)) {
            getEdges().add(edge);
            fireEdgeAdded(edge);
        }
    }
}