/* $Id: UMLMutableGraphSupport.java 17850 2010-01-12 19:53:35Z 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: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2007 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; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.argouml.kernel.Project; import org.argouml.model.DiDiagram; import org.argouml.model.Model; import org.argouml.model.UmlException; import org.argouml.uml.CommentEdge; 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.MutableGraphSupport; /** * UMLMutableGraphSupport is a helper class which extends * MutableGraphSupport to provide additional helper and common methods * for UML Diagrams. * * @author mkl@tigris.org * @since November 14, 2002, 10:20 PM */ public abstract class UMLMutableGraphSupport extends MutableGraphSupport { /** * Logger. */ private static final Logger LOG = Logger.getLogger(UMLMutableGraphSupport.class); private DiDiagram diDiagram; /** * Contains all the nodes in the graphmodel/diagram. */ private List nodes = new ArrayList(); /** * Contains all the edges in the graphmodel/diagram. */ private List edges = new ArrayList(); /** * The owning namespace or "home model" of this diagram, not all * ModelElements in this graph are in the home model, but if they are added * and don't already have a model, they are placed in the "home model". * Also, elements from other models will have their FigNodes add a line to * say what their model is. */ private Object homeModel; /** * The project this graph model is in. */ private Project project; /** * Constructor. * * @see org.tigris.gef.graph.MutableGraphSupport */ public UMLMutableGraphSupport() { super(); } /** * Get all the nodes from the graphmodel/diagram. * * @see org.tigris.gef.graph.MutableGraphSupport#getNodes() * @return List of nodes in the graphmodel/diagram */ public List getNodes() { return nodes; } /** * Get all the edges from the graphmodel/diagram. * * @return List of edges in the graphmodel/diagram */ public List getEdges() { return edges; } /* * @see org.tigris.gef.graph.MutableGraphModel#containsNode(java.lang.Object) */ public boolean containsNode(Object node) { return nodes.contains(node); } /** * @param edge the candidate edge * @return true if it is contained */ public boolean constainsEdge(Object edge) { return edges.contains(edge); } /** * Remove a node from the diagram and notify GEF. * * @param node node to remove */ @Override public void removeNode(Object node) { if (!containsNode(node)) { return; } nodes.remove(node); fireNodeRemoved(node); } /** * Remove an edge from the graphmodel and notify GEF. * * @param edge edge to remove */ @Override public void removeEdge(Object edge) { if (!containsEdge(edge)) { return; } edges.remove(edge); fireEdgeRemoved(edge); } /** * Assume that anything can be connected to anything unless overridden * in a subclass. * * {@inheritDoc} */ public boolean canConnect(Object fromP, Object toP) { return true; } /** * The connect method without specifying a connection * type is unavailable in the ArgoUML implmentation. * * {@inheritDoc} */ public Object connect(Object fromPort, Object toPort) { throw new UnsupportedOperationException( "The connect method is not supported"); } /** * Get the namespace, also known as homemodel, which owns the diagram. * * @return the homemodel */ public Object getHomeModel() { return homeModel; } /** * Set the namespace or homemodel of the diagram. This will become the * default namespace for any model elements which are created on * the diagram. * * @param ns the namespace */ public void setHomeModel(Object ns) { if (!Model.getFacade().isANamespace(ns)) { throw new IllegalArgumentException(); } homeModel = ns; } /** * The connect method specifying a connection * type by class is unavailable in the ArgoUML implementation. * TODO: This should be unsupported. Use the 3 Object version * * {@inheritDoc} */ public Object connect(Object fromPort, Object toPort, Class edgeClass) { return connect(fromPort, toPort, (Object) edgeClass); } /** * Construct and add a new edge of the given kind and connect * the given ports. * * @param fromPort The originating port to connect * * @param toPort The destination port to connect * * @param edgeType The type of edge to create. This is one of the types * returned by the methods of * <code>org.argouml.model.MetaTypes</code> * * @return The type of edge created (the same as * <code>edgeClass</code> if we succeeded, * <code>null</code> otherwise) */ public Object connect(Object fromPort, Object toPort, Object edgeType) { // If this was an association then there will be relevant // information to fetch out of the mode arguments. If it // not an association then these will be passed forward // harmlessly as null. Editor curEditor = Globals.curEditor(); ModeManager modeManager = curEditor.getModeManager(); Mode mode = modeManager.top(); Dictionary args = mode.getArgs(); Object style = args.get("aggregation"); //MAggregationKind Boolean unidirectional = (Boolean) args.get("unidirectional"); Object model = getProject().getModel(); // Create the UML connection of the given type between the // given model elements. // default aggregation (none) Object connection = buildConnection( edgeType, fromPort, style, toPort, null, unidirectional, model); if (connection == null) { if (LOG.isDebugEnabled()) { LOG.debug("Cannot make a " + edgeType + " between a " + fromPort.getClass().getName() + " and a " + toPort.getClass().getName()); } return null; } addEdge(connection); if (LOG.isDebugEnabled()) { LOG.debug("Connection type" + edgeType + " made between a " + fromPort.getClass().getName() + " and a " + toPort.getClass().getName()); } return connection; } /** * Construct and add a new edge of the given kind and connect * the given ports. * * @param fromPort The originating port to connect * * @param toPort The destination port to connect * * @param edgeType An indicator of the edge type to create. * * @param styleAttributes key/value pairs from which to style the edge. * * @return The type of edge created (the same as * <code>edgeClass</code> if we succeeded, * <code>null</code> otherwise) */ public Object connect(Object fromPort, Object toPort, Object edgeType, Map styleAttributes) { return null; } /* * @see org.tigris.gef.graph.MutableGraphModel#canAddNode(java.lang.Object) */ public boolean canAddNode(Object node) { if (node == null) { return false; } if (Model.getFacade().isAComment(node)) { return true; } return false; } /** * Return the source end of an edge. * * @param edge The edge for which we want the source port. * * @return The source port for the edge, or <code>null</code> if the * edge given is of the wrong type or has no source defined. */ public Object getSourcePort(Object edge) { if (edge instanceof CommentEdge) { return ((CommentEdge) edge).getSource(); } else if (Model.getFacade().isARelationship(edge) || Model.getFacade().isATransition(edge) || Model.getFacade().isAAssociationEnd(edge)) { return Model.getUmlHelper().getSource(edge); } else if (Model.getFacade().isALink(edge)) { return Model.getCommonBehaviorHelper().getSource(edge); } // Don't know what to do otherwise LOG.error(this.getClass().toString() + ": getSourcePort(" + edge.toString() + ") - can't handle"); return null; } /** * Return the destination end of an edge. * * @param edge The edge for which we want the destination port. * * @return The destination port for the edge, or <code>null</code> if * the edge given is otf the wrong type or has no destination * defined. */ public Object getDestPort(Object edge) { if (edge instanceof CommentEdge) { return ((CommentEdge) edge).getDestination(); } else if (Model.getFacade().isAAssociation(edge)) { List conns = new ArrayList(Model.getFacade().getConnections(edge)); return conns.get(1); } else if (Model.getFacade().isARelationship(edge) || Model.getFacade().isATransition(edge) || Model.getFacade().isAAssociationEnd(edge)) { return Model.getUmlHelper().getDestination(edge); } else if (Model.getFacade().isALink(edge)) { return Model.getCommonBehaviorHelper().getDestination(edge); } // Don't know what to do otherwise LOG.error(this.getClass().toString() + ": getDestPort(" + edge.toString() + ") - can't handle"); return null; } /* * @see org.tigris.gef.graph.MutableGraphModel#canAddEdge(java.lang.Object) */ public boolean canAddEdge(Object edge) { if (edge instanceof CommentEdge) { CommentEdge ce = (CommentEdge) edge; return isConnectionValid(CommentEdge.class, ce.getSource(), ce.getDestination()); } else if (edge != null && Model.getUmlFactory().isConnectionType(edge)) { return isConnectionValid(edge.getClass(), Model.getUmlHelper().getSource(edge), Model.getUmlHelper().getDestination(edge)); } return false; } /* * @see org.tigris.gef.graph.MutableGraphModel#addNodeRelatedEdges(java.lang.Object) */ public void addNodeRelatedEdges(Object node) { if (Model.getFacade().isAModelElement(node)) { List specs = new ArrayList(Model.getFacade().getClientDependencies(node)); specs.addAll(Model.getFacade().getSupplierDependencies(node)); Iterator iter = specs.iterator(); while (iter.hasNext()) { Object dependency = iter.next(); if (canAddEdge(dependency)) { addEdge(dependency); // return; } } } // Commentlinks for comments. Iterate over all the comment links // to find the comment and annotated elements. Collection cmnt = new ArrayList(); if (Model.getFacade().isAComment(node)) { cmnt.addAll(Model.getFacade().getAnnotatedElements(node)); } // TODO: Comments are on Element in UML 2.x if (Model.getFacade().isAModelElement(node)) { cmnt.addAll(Model.getFacade().getComments(node)); } Iterator iter = cmnt.iterator(); while (iter.hasNext()) { Object ae = iter.next(); CommentEdge ce = new CommentEdge(node, ae); if (canAddEdge(ce)) { addEdge(ce); } } } /** * Create an edge of the given type and connect it to the * given nodes. * * @param edgeType the UML object type of the connection * @param fromElement the UML object for the "from" element * @param fromStyle the aggregationkind for the connection * in case of an association * @param toElement the UML object for the "to" element * @param toStyle the aggregationkind for the connection * in case of an association * @param unidirectional for association and associationrole * @param namespace the namespace to use if it can't be determined * @return the newly build connection (UML object) */ protected Object buildConnection( Object edgeType, Object fromElement, Object fromStyle, Object toElement, Object toStyle, Object unidirectional, Object namespace) { Object connection = null; if (edgeType == CommentEdge.class) { connection = buildCommentConnection(fromElement, toElement); } else { try { connection = Model.getUmlFactory().buildConnection( edgeType, fromElement, fromStyle, toElement, toStyle, unidirectional, namespace); LOG.info("Created " + connection + " between " + fromElement + " and " + toElement); } catch (UmlException ex) { // fail silently as we expect users to accidentally drop // on to wrong component } catch (IllegalArgumentException iae) { // idem, e.g. for a generalization with leaf/root object // TODO: but showing the message in the statusbar would help // TODO: IllegalArgumentException should not be used for // events we expect to happen. We need a different way of // catching well-formedness rules. LOG.warn("IllegalArgumentException caught", iae); } } return connection; } /** * Builds the model behind a connection between a comment and * the annotated modelelement. * * @param from The comment or annotated element. * @param to The comment or annotated element. * @return A commentEdge representing the model behind the connection * between a comment and an annotated modelelement. */ public CommentEdge buildCommentConnection(Object from, Object to) { if (from == null || to == null) { throw new IllegalArgumentException("Either fromNode == null " + "or toNode == null"); } Object comment = null; Object annotatedElement = null; if (Model.getFacade().isAComment(from)) { comment = from; annotatedElement = to; } else { if (Model.getFacade().isAComment(to)) { comment = to; annotatedElement = from; } else { return null; } } CommentEdge connection = new CommentEdge(from, to); Model.getCoreHelper().addAnnotatedElement(comment, annotatedElement); return connection; } /** * Checks if some type of edge is valid to connect two * types of node. * * @param edgeType the UML object type of the connection * @param fromElement the UML object type of the "from" * @param toElement the UML object type of the "to" * @return true if valid */ protected boolean isConnectionValid( Object edgeType, Object fromElement, Object toElement) { if (!nodes.contains(fromElement) || !nodes.contains(toElement)) { // The connection is not valid unless both nodes are // in this graph model. return false; } if (edgeType.equals(CommentEdge.class)) { return ((Model.getFacade().isAComment(fromElement) && Model.getFacade().isAModelElement(toElement)) || (Model.getFacade().isAComment(toElement) && Model.getFacade().isAModelElement(fromElement))); } return Model.getUmlFactory().isConnectionValid( edgeType, fromElement, toElement, true); } /** * Package scope. Only the factory is supposed to set this. * @param dd */ void setDiDiagram(DiDiagram dd) { diDiagram = dd; } /** * Get the object that represents this diagram * in the DiagramInterchangeModel. * * @return the Diagram Interchange Diagram. */ public DiDiagram getDiDiagram() { return diDiagram; } /** * Return true if the current targets may be removed from the diagram. * * @param figs a collection with the selected figs * @return true if the targets may be removed */ public boolean isRemoveFromDiagramAllowed(Collection figs) { return !figs.isEmpty(); } /** * Set the project that the graph model is inside. * @param p the project */ public void setProject(Project p) { project = p; } /** * Get the project that the graph model is inside. * @return the project */ public Project getProject() { return project; } }