/* $Id: ClassDiagramGraphModel.java 18730 2010-09-11 03:00:44Z bobtarling $ ******************************************************************************* * Copyright (c) 2010 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: * Jason Robbins - initial implementation * <see source control change log for other early contributors> * ******************************************************************************* */ // 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.static_structure; import java.beans.PropertyChangeEvent; import java.beans.VetoableChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.argouml.model.Model; import org.argouml.uml.CommentEdge; import org.argouml.uml.diagram.UMLMutableGraphSupport; /** * This class defines a bridge between the UML meta-model * representation of the design and the GraphModel interface used by * GEF. This class handles only UML Class digrams. * * @author jrobbins */ public class ClassDiagramGraphModel extends UMLMutableGraphSupport implements VetoableChangeListener { /** * Logger. */ private static final Logger LOG = Logger.getLogger(ClassDiagramGraphModel.class); //////////////////////////////////////////////////////////////// // GraphModel implementation /* * @see org.tigris.gef.graph.GraphModel#getPorts(java.lang.Object) */ public List<Object> getPorts(Object nodeOrEdge) { List<Object> res = new ArrayList<Object>(); if (Model.getFacade().isAClassifier(nodeOrEdge)) { res.add(nodeOrEdge); } if (Model.getFacade().isAInstance(nodeOrEdge)) { res.add(nodeOrEdge); } if (Model.getFacade().isAModel(nodeOrEdge)) { res.add(nodeOrEdge); } if (Model.getFacade().isAStereotype(nodeOrEdge)) { res.add(nodeOrEdge); } if (Model.getFacade().isASignal(nodeOrEdge)) { res.add(nodeOrEdge); } return res; } /* * @see org.tigris.gef.graph.BaseGraphModel#getOwner(java.lang.Object) */ public Object getOwner(Object port) { return port; } /** * Return all edges going to given port (read Model Element). * * @param port * model element to query * @return list of incoming connections */ public List getInEdges(Object port) { List<Object> edges = new ArrayList<Object>(); // top of the hierarchy is ME: if (Model.getFacade().isAModelElement(port)) { Iterator it = Model.getFacade().getSupplierDependencies(port).iterator(); while (it.hasNext()) { edges.add(it.next()); } } // then Generalizable Element if (Model.getFacade().isAGeneralizableElement(port)) { Iterator it = Model.getFacade().getSpecializations(port).iterator(); while (it.hasNext()) { edges.add(it.next()); } } // then Classifier & Package if (Model.getFacade().isAClassifier(port) || Model.getFacade().isAPackage(port)) { Iterator it = Model.getFacade().getAssociationEnds(port).iterator(); while (it.hasNext()) { Object nextAssocEnd = it.next(); // navigable.... only want incoming if (Model.getFacade().isNavigable(nextAssocEnd)) { edges.add(nextAssocEnd); } } } if (Model.getFacade().isAInstance(port)) { Iterator it = Model.getFacade().getLinkEnds(port).iterator(); while (it.hasNext()) { edges.add(it.next()); } } return edges; } /* * @see org.tigris.gef.graph.GraphModel#getOutEdges(java.lang.Object) */ public List getOutEdges(Object port) { List<Object> edges = new ArrayList<Object>(); // top of the hierarchy is ME: if (Model.getFacade().isAModelElement(port)) { Iterator it = Model.getFacade().getClientDependencies(port).iterator(); while (it.hasNext()) { edges.add(it.next()); } } // then Generalizable Element if (Model.getFacade().isAGeneralizableElement(port)) { Iterator it = Model.getFacade().getGeneralizations(port).iterator(); while (it.hasNext()) { edges.add(it.next()); } } // then Classifier if (Model.getFacade().isAClassifier(port)) { Iterator it = Model.getFacade().getAssociationEnds(port).iterator(); while (it.hasNext()) { Object thisEnd = it.next(); Object assoc = Model.getFacade().getAssociation(thisEnd); if (assoc != null) { Iterator it2 = Model.getFacade().getAssociationEnds(assoc).iterator(); while (it2.hasNext()) { Object nextAssocEnd = it2.next(); if (!thisEnd.equals(nextAssocEnd) && Model.getFacade().isNavigable( nextAssocEnd)) { edges.add(nextAssocEnd); } } } } } return edges; } //////////////////////////////////////////////////////////////// // MutableGraphModel implementation /* * @see org.tigris.gef.graph.MutableGraphModel#canAddNode(java.lang.Object) */ @Override public boolean canAddNode(Object node) { if (Model.getFacade().isAAssociation(node) && !Model.getFacade().isANaryAssociation(node)) { // A binary association is not a node so reject. LOG.debug("A binary association cannot be added as a node"); return false; } if (super.canAddNode(node) && !containsNode(node)) { return true; } if (containsNode(node)) { LOG.error("Addition of node of type " + node.getClass().getName() + " rejected because its already in the graph model"); return false; } if (Model.getFacade().isAAssociation(node)) { // N.B. A node which is an Association is either a n-ary association // or the Class part of an AssociationClass Collection ends = Model.getFacade().getConnections(node); Iterator iter = ends.iterator(); while (iter.hasNext()) { Object classifier = Model.getFacade().getClassifier(iter.next()); if (!containsNode(classifier)) { LOG.error("Addition of node of type " + node.getClass().getName() + " rejected because it is connected to a " + "classifier that is not in the diagram"); return false; } } return true; } // TODO: This logic may well be worth moving into the model component. // Provide a similar grid to the connectionsGrid if (Model.getFacade().isAModel(node)) { return false; // issue 3774 } if (Model.getFacade().isAClassifierRole(node)) { return false; } return Model.getFacade().isAClassifier(node) || Model.getFacade().isAPackage(node) || Model.getFacade().isAStereotype(node) || Model.getFacade().isASignal(node) || Model.getFacade().isAInstance(node); } /* * @see org.tigris.gef.graph.MutableGraphModel#canAddEdge(java.lang.Object) */ @Override public boolean canAddEdge(Object edge) { if (edge == null) { return false; } if (containsEdge(edge)) { return false; } Object sourceModelElement = null; Object destModelElement = null; if (Model.getFacade().isAAssociation(edge)) { Collection conns = Model.getFacade().getConnections(edge); if (conns.size() < 2) { LOG.error("Association rejected. Must have at least 2 ends"); return false; } Iterator iter = conns.iterator(); Object associationEnd0 = iter.next(); Object associationEnd1 = iter.next(); if (associationEnd0 == null || associationEnd1 == null) { LOG.error("Association rejected. An end is null"); return false; } sourceModelElement = Model.getFacade().getType(associationEnd0); destModelElement = Model.getFacade().getType(associationEnd1); } else if (Model.getFacade().isAAssociationEnd(edge)) { sourceModelElement = Model.getFacade().getAssociation(edge); destModelElement = Model.getFacade().getType(edge); if (sourceModelElement == null || destModelElement == null) { LOG.error("Association end rejected. An end is null"); return false; } if (!containsEdge(sourceModelElement) && !containsNode(sourceModelElement)) { LOG.error("Association end rejected. The source model element (" + sourceModelElement.getClass().getName() + ") must be on the diagram"); return false; } if (!containsNode(destModelElement)) { LOG.error("Association end rejected. " + "The destination model element must be " + "on the diagram."); return false; } } else if (Model.getFacade().isAGeneralization(edge)) { sourceModelElement = Model.getFacade().getSpecific(edge); destModelElement = Model.getFacade().getGeneral(edge); } else if (Model.getFacade().isADependency(edge)) { Collection clients = Model.getFacade().getClients(edge); Collection suppliers = Model.getFacade().getSuppliers(edge); if (clients == null || suppliers == null) { return false; } sourceModelElement = clients.iterator().next(); destModelElement = suppliers.iterator().next(); } else if (Model.getFacade().isALink(edge)) { Collection roles = Model.getFacade().getConnections(edge); if (roles.size() < 2) { return false; } Iterator iter = roles.iterator(); Object linkEnd0 = iter.next(); Object linkEnd1 = iter.next(); if (linkEnd0 == null || linkEnd1 == null) { return false; } sourceModelElement = Model.getFacade().getInstance(linkEnd0); destModelElement = Model.getFacade().getInstance(linkEnd1); } else if (edge instanceof CommentEdge) { sourceModelElement = ((CommentEdge) edge).getSource(); destModelElement = ((CommentEdge) edge).getDestination(); } else if (Model.getFacade().isADirectedRelationship(edge)) { Collection sources = Model.getFacade().getSources(edge); Collection targets = Model.getFacade().getTargets(edge); if (sources.size() == 1 && targets.size() == 1) { sourceModelElement = sources.iterator().next(); destModelElement = targets.iterator().next(); } else { LOG.error("Edge rejected. More than one source or target for a DirectedRelationship"); return false; } } else { return false; } if (sourceModelElement == null || destModelElement == null) { LOG.error("Edge rejected. Its ends are not attached to anything"); return false; } if (!containsNode(sourceModelElement) && !containsEdge(sourceModelElement)) { LOG.error("Edge rejected. Its source end is attached to " + sourceModelElement + " but this is not in the graph model"); return false; } if (!containsNode(destModelElement) && !containsEdge(destModelElement)) { LOG.error("Edge rejected. Its destination end is attached to " + destModelElement + " 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)) { return; } getNodes().add(node); // TODO: This is probably an undesirable side effect unless the user // confirms it. Placing an element on a second diagram is going to // potentially change its namespace. - tfm 20061208 if (Model.getFacade().isAModelElement(node) && Model.getFacade().getNamespace(node) == null) { Model.getCoreHelper().addOwnedElement(getHomeModel(), node); } fireNodeAdded(node); } /** * Add the given edge to the graph, if of the correct type. * Throws IllegalArgumentException if edge is null or either of its * ends are null. * * @param edge the edge to be added */ @Override public void addEdge(Object 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); // TODO: assumes public // TODO: This is probably an undesirable side effect unless the user // confirms it. Placing an element on a second diagram is going to // potentially change its namespace. - tfm 20061208 if (Model.getFacade().isAModelElement(edge) && Model.getFacade().getNamespace(edge) == null && !Model.getFacade().isAAssociationEnd(edge)) { Model.getCoreHelper().addOwnedElement(getHomeModel(), edge); } fireEdgeAdded(edge); } /** * Add the edges from the given node. For example, this method lets you add * an existing association between two figclassifiers. * * @param node * the model element to query for connections */ @Override public void addNodeRelatedEdges(Object node) { super.addNodeRelatedEdges(node); if (Model.getFacade().isAClassifier(node)) { Collection ends = Model.getFacade().getAssociationEnds(node); Iterator iter = ends.iterator(); while (iter.hasNext()) { final Object associationEnd = iter.next(); Object association = Model.getFacade().getAssociation(associationEnd); if (Model.getFacade().isANaryAssociation(association) && canAddEdge(associationEnd)) { addEdge(associationEnd); } else if (canAddEdge(association)){ addEdge(association); } } } if (Model.getFacade().isAGeneralizableElement(node)) { Collection generalizations = Model.getFacade().getGeneralizations(node); Iterator iter = generalizations.iterator(); while (iter.hasNext()) { Object generalization = iter.next(); if (canAddEdge(generalization)) { addEdge(generalization); // return; } } Collection specializations = Model.getFacade().getSpecializations(node); iter = specializations.iterator(); while (iter.hasNext()) { Object specialization = iter.next(); if (canAddEdge(specialization)) { addEdge(specialization); // return; } } } if (Model.getFacade().isAAssociation(node)) { Collection ends = Model.getFacade().getConnections(node); Iterator iter = ends.iterator(); while (iter.hasNext()) { Object associationEnd = iter.next(); if (canAddEdge(associationEnd)) { addEdge(associationEnd); } } } } //////////////////////////////////////////////////////////////// // VetoableChangeListener implementation /* * @see java.beans.VetoableChangeListener#vetoableChange(java.beans.PropertyChangeEvent) */ public void vetoableChange(PropertyChangeEvent pce) { //throws PropertyVetoException if ("ownedElement".equals(pce.getPropertyName())) { List oldOwned = (List) pce.getOldValue(); Object elementImport = pce.getNewValue(); Object modelElement = Model.getFacade().getModelElement(elementImport); //MModelElement modelElement = elementImport.getModelElement(); if (oldOwned.contains(elementImport)) { LOG.debug("model removed " + modelElement); if (Model.getFacade().isAClassifier(modelElement)) { removeNode(modelElement); } if (Model.getFacade().isAPackage(modelElement)) { removeNode(modelElement); } if (Model.getFacade().isAAssociation(modelElement)) { removeEdge(modelElement); } if (Model.getFacade().isADependency(modelElement)) { removeEdge(modelElement); } if (Model.getFacade().isAGeneralization(modelElement)) { removeEdge(modelElement); } } else { LOG.debug("model added " + modelElement); } } } /** * The UID. */ static final long serialVersionUID = -2638688086415040146L; /** * When rerouting an edge, this is the first method to * be called by SelectionRerouteEdge, in order to determine * whether the graphmodel will allow the change.<p> * * Restricted to class-association changes for now. * * @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 whether or not the rerouting is allowed */ @Override public boolean canChangeConnectedNode(Object newNode, Object oldNode, Object edge) { // prevent no changes... if (newNode == oldNode) { return false; } // check parameter types: if (!(Model.getFacade().isAClass(newNode) || Model.getFacade().isAClass(oldNode) || Model.getFacade().isAAssociation(edge))) { return false; } return true; } /** * Reroutes the connection to the old node to be connected to * the new node. * * delegates to rerouteXXX(,,,) for each of the 4 possible edges in * a class diagram: Association, Dependency, Generalization, Link. * TODO: This should probably be in superclass. Many Figs can be on * several diagram types. * * @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. */ @Override public void changeConnectedNode(Object newNode, Object oldNode, Object edge, boolean isSource) { if (Model.getFacade().isAAssociation(edge)) { rerouteAssociation(newNode, oldNode, edge, isSource); } else if (Model.getFacade().isAGeneralization(edge)) { rerouteGeneralization(newNode, oldNode, edge, isSource); } else if (Model.getFacade().isADependency(edge)) { rerouteDependency(newNode, oldNode, edge, isSource); } else if (Model.getFacade().isALink(edge)) { rerouteLink(newNode, oldNode, edge, isSource); } } /** * Helper method for changeConnectedNode. * TODO: This should probably be in superclass. Associations can be on * several diagram types. */ private void rerouteAssociation(Object newNode, Object oldNode, Object edge, boolean isSource) { // check param types: only some connections are legal uml connections: if (!(Model.getFacade().isAClassifier(newNode)) || !(Model.getFacade().isAClassifier(oldNode))) { return; } // can't have a connection between 2 interfaces: // get the 'other' end type Object otherNode = null; if (isSource) { otherNode = Model.getCoreHelper().getDestination(edge); } else { otherNode = Model.getCoreHelper().getSource(edge); } if (Model.getFacade().isAInterface(newNode) && Model.getFacade().isAInterface(otherNode)) { return; } // cast the params Object edgeAssoc = edge; Object theEnd = null; Object theOtherEnd = null; Collection connections = Model.getFacade().getConnections(edgeAssoc); Iterator iter = connections.iterator(); if (isSource) { // rerouting the source: theEnd = iter.next(); theOtherEnd = iter.next(); } else { // rerouting the destination: theOtherEnd = iter.next(); theEnd = iter.next(); } //set the new end type! Model.getCoreHelper().setType(theEnd, newNode); } /** * helper method for changeConnectedNode.<p> * * empty at the moment */ private void rerouteGeneralization(Object newNode, Object oldNode, Object edge, boolean isSource) { // empty at the moment } /** * Helper method for changeConnectedNode.<p> * * empty at the moment */ private void rerouteDependency(Object newNode, Object oldNode, Object edge, boolean isSource) { // empty at the moment } /** * helper method for changeConnectedNode.<p> * * Empty at the moment */ private void rerouteLink(Object newNode, Object oldNode, Object edge, boolean isSource) { // empty at the moment } }