/* $Id: StateDiagramGraphModel.java 18978 2011-01-24 18:25:30Z linus $
*****************************************************************************
* 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) 1996-2006 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.uml.diagram.state;
import java.beans.PropertyChangeEvent;
import java.beans.VetoableChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import org.argouml.kernel.ProjectManager;
import org.argouml.model.Model;
import org.argouml.uml.CommentEdge;
import org.argouml.uml.diagram.UMLMutableGraphSupport;
import org.tigris.gef.presentation.Fig;
/**
* This class defines a bridge between the UML meta-model representation of the
* design and the GraphModel interface used by GEF. This class handles UML
* Statemachine Diagrams, and is also used for Activity diagrams.
*/
public class StateDiagramGraphModel extends UMLMutableGraphSupport implements
VetoableChangeListener {
/**
* Logger.
*/
private static final Logger LOG =
Logger.getLogger(StateDiagramGraphModel.class);
/**
* The statemachine we are diagramming.
*/
private Object machine;
////////////////////////////////////////////////////////////////
// accessors
/**
* @return the statemachine of this diagram
*/
public Object getMachine() {
return machine;
}
/**
* @param sm the statemachine of this diagram
*/
public void setMachine(Object sm) {
if (!Model.getFacade().isAStateMachine(sm)) {
throw new IllegalArgumentException();
}
if (sm != null) {
machine = sm;
}
}
////////////////////////////////////////////////////////////////
// GraphModel implementation
/*
* @see org.tigris.gef.graph.GraphModel#getPorts(java.lang.Object)
*/
public List getPorts(Object nodeOrEdge) {
List res = new ArrayList();
if (Model.getFacade().isAState(nodeOrEdge)) {
res.add(nodeOrEdge);
}
if (Model.getFacade().isAPseudostate(nodeOrEdge)) {
res.add(nodeOrEdge);
}
return res;
}
/*
* @see org.tigris.gef.graph.BaseGraphModel#getOwner(java.lang.Object)
*/
public Object getOwner(Object port) {
return port;
}
/*
* @see org.tigris.gef.graph.GraphModel#getInEdges(java.lang.Object)
*/
public List getInEdges(Object port) {
if (Model.getFacade().isAStateVertex(port)) {
return new ArrayList(Model.getFacade().getIncomings(port));
}
LOG.debug("TODO: getInEdges of MState");
return Collections.EMPTY_LIST;
}
/*
* @see org.tigris.gef.graph.GraphModel#getOutEdges(java.lang.Object)
*/
public List getOutEdges(Object port) {
if (Model.getFacade().isAStateVertex(port)) {
return new ArrayList(Model.getFacade().getOutgoings(port));
}
LOG.debug("TODO: getOutEdges of MState");
return Collections.EMPTY_LIST;
}
////////////////////////////////////////////////////////////////
// MutableGraphModel implementation
/*
* @see org.tigris.gef.graph.MutableGraphModel#canAddNode(java.lang.Object)
*/
public boolean canAddNode(Object node) {
if (node == null
|| !Model.getFacade().isAModelElement(node)
|| containsNode(node)) {
return false;
}
if (Model.getFacade().isAComment(node)) {
return true;
}
if (Model.getFacade().isAStateVertex(node)
|| Model.getFacade().isAPartition(node)) {
/*
* The next solves issue 3665: Do not allow addition of an element
* to a statemachine that is contained by a statemachine other than
* the one represented by this diagram.
*/
Object nodeMachine =
Model.getStateMachinesHelper().getStateMachine(node);
if (nodeMachine == null || nodeMachine == getMachine()) {
return true;
}
}
return false;
}
/*
* @see org.tigris.gef.graph.MutableGraphModel#canAddEdge(java.lang.Object)
*/
public boolean canAddEdge(Object edge) {
if (super.canAddEdge(edge)) {
return true;
}
if (edge == null) {
return false;
}
if (containsEdge(edge)) {
return false;
}
Object end0 = null;
Object end1 = null;
if (Model.getFacade().isATransition(edge)) {
end0 = Model.getFacade().getSource(edge);
end1 = Model.getFacade().getTarget(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.error("Edge rejected. Its ends are not attached to anything");
return false;
}
if (!containsNode(end0)
&& !containsEdge(end0)) {
LOG.error("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.error("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)
*/
public void addNode(Object node) {
LOG.debug("adding statechart/activity diagram node: " + node);
if (!canAddNode(node)) {
return;
}
if (containsNode(node)) {
return;
}
getNodes().add(node);
if (Model.getFacade().isAStateVertex(node)) {
Object top = Model.getStateMachinesHelper().getTop(getMachine());
Model.getStateMachinesHelper().addSubvertex(top, node);
}
fireNodeAdded(node);
}
/*
* @see org.tigris.gef.graph.MutableGraphModel#addEdge(java.lang.Object)
*/
public void addEdge(Object edge) {
LOG.debug("adding statechart/activity diagram edge!!!!!!");
if (edge == null) {
throw new IllegalArgumentException("Cannot add a null edge");
}
if (getDestPort(edge) == null || getSourcePort(edge) == null) {
throw new IllegalArgumentException(
"The source and dest port should be provided on an edge");
}
if (LOG.isInfoEnabled()) {
LOG.info("Adding an edge of type "
+ edge.getClass().getName()
+ " to class diagram.");
}
if (!canAddEdge(edge)) {
LOG.info("Attempt to add edge rejected");
return;
}
getEdges().add(edge);
fireEdgeAdded(edge);
}
/*
* @see org.tigris.gef.graph.MutableGraphModel#addNodeRelatedEdges(java.lang.Object)
*/
public void addNodeRelatedEdges(Object node) {
super.addNodeRelatedEdges(node);
if (Model.getFacade().isAStateVertex(node)) {
Collection transen =
new ArrayList(Model.getFacade().getOutgoings(node));
transen.addAll(Model.getFacade().getIncomings(node));
Iterator iter = transen.iterator();
while (iter.hasNext()) {
Object dep = /* (MTransition) */iter.next();
if (canAddEdge(dep)) {
addEdge(dep);
}
}
}
}
/*
* @see org.tigris.gef.graph.MutableGraphModel#canConnect(java.lang.Object,
* java.lang.Object)
*/
public boolean canConnect(Object fromPort, Object toPort) {
if (!(Model.getFacade().isAStateVertex(fromPort))) {
LOG.error("internal error not from sv");
return false;
}
if (!(Model.getFacade().isAStateVertex(toPort))) {
LOG.error("internal error not to sv");
return false;
}
if (Model.getFacade().isAFinalState(fromPort)) {
return false;
}
if (Model.getFacade().isAPseudostate(toPort)) {
if ((Model.getPseudostateKind().getInitial()).equals(
Model.getFacade().getKind(toPort))) {
return false;
}
}
return true;
}
/*
* @see org.tigris.gef.graph.MutableGraphModel#connect(java.lang.Object,
* java.lang.Object, java.lang.Class)
*/
public Object connect(Object fromPort, Object toPort,
Object edgeClass) {
if (Model.getFacade().isAFinalState(fromPort)) {
return null;
}
if (Model.getFacade().isAPseudostate(toPort)
&& Model.getPseudostateKind().getInitial().equals(
Model.getFacade().getKind(toPort))) {
return null;
}
if (Model.getMetaTypes().getTransition().equals(edgeClass)) {
Object tr = null;
tr =
Model.getStateMachinesFactory()
.buildTransition(fromPort, toPort);
if (canAddEdge(tr)) {
addEdge(tr);
} else {
ProjectManager.getManager().getCurrentProject().moveToTrash(tr);
tr = null;
}
return tr;
} else if (edgeClass == CommentEdge.class) {
try {
Object connection =
buildConnection(
edgeClass, fromPort, null, toPort, null, null,
ProjectManager.getManager().getCurrentProject()
.getModel());
addEdge(connection);
return connection;
} catch (Exception ex) {
LOG.error("buildConnection() failed", ex);
}
return null;
} else {
LOG.debug("wrong kind of edge in StateDiagram connect3 "
+ edgeClass);
return null;
}
}
////////////////////////////////////////////////////////////////
// VetoableChangeListener implementation
/*
* @see java.beans.VetoableChangeListener#vetoableChange(java.beans.PropertyChangeEvent)
*/
public void vetoableChange(PropertyChangeEvent pce) {
//throws PropertyVetoException
if ("ownedElement".equals(pce.getPropertyName())) {
Collection oldOwned = (Collection) pce.getOldValue();
Object eo = /* (MElementImport) */pce.getNewValue();
Object me = Model.getFacade().getModelElement(eo);
if (oldOwned.contains(eo)) {
LOG.debug("model removed " + me);
if (Model.getFacade().isAState(me)) {
removeNode(me);
}
if (Model.getFacade().isAPseudostate(me)) {
removeNode(me);
}
if (Model.getFacade().isATransition(me)) {
removeEdge(me);
}
} else {
LOG.debug("model added " + me);
}
}
}
/**
* The UID.
*/
static final long serialVersionUID = -8056507319026044174L;
/**
* @param newNode
* this is the new node that one of the ends is dragged to.
* @param oldNode
* this is the existing node that is already connected.
* @param edge
* this is the edge that is being dragged/rerouted
* @return true if a transition is being rerouted between two states.
*/
public boolean canChangeConnectedNode(Object newNode, Object oldNode,
Object edge) {
// prevent no changes...
if (newNode == oldNode) {
return false;
}
// check parameter types:
if (!(Model.getFacade().isAState(newNode)
|| Model.getFacade().isAState(oldNode)
|| Model.getFacade().isATransition(edge))) {
return false;
}
// it's not allowed to move a transition
// so that it will go from a composite to its substate
// nor vice versa. See issue 2865.
Object otherSideNode = Model.getFacade().getSource(edge);
if (otherSideNode == oldNode) {
otherSideNode = Model.getFacade().getTarget(edge);
}
if (Model.getFacade().isACompositeState(newNode)
&& Model.getStateMachinesHelper().getAllSubStates(newNode)
.contains(otherSideNode)) {
return false;
}
return true;
}
/**
* Reroutes the connection to the old node to be connected to the new node.
*
* @param newNode
* this is the new node that one of the ends is dragged to.
* @param oldNode
* this is the existing node that is already connected.
* @param edge
* this is the edge that is being dragged/rerouted
* @param isSource
* tells us which end is being rerouted.
*/
public void changeConnectedNode(Object newNode, Object oldNode,
Object edge, boolean isSource) {
if (isSource) {
Model.getStateMachinesHelper().setSource(edge, newNode);
} else {
Model.getCommonBehaviorHelper().setTarget(edge, newNode);
}
}
/*
* @see org.argouml.uml.diagram.UMLMutableGraphSupport#isRemoveFromDiagramAllowed(Collection)
*/
public boolean isRemoveFromDiagramAllowed(Collection figs) {
/* If nothing is selected, then not allowed to remove it. */
if (figs.isEmpty()) {
return false;
}
Iterator i = figs.iterator();
while (i.hasNext()) {
Object obj = i.next();
if (!(obj instanceof Fig)) {
return false;
}
Object uml = ((Fig) obj).getOwner();
/* If a UML object is found, you can not remove selected elms. */
if (uml != null) {
return false;
}
}
/* If only Figs without owner are selected, then you can remove them! */
return true;
}
} /* end class StateDiagramGraphModel */