001/* A graph model for basic ptolemy models. 002 003 Copyright (c) 1999-2016 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.vergil.actor; 029 030import java.util.Collections; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Set; 036 037import diva.graph.GraphEvent; 038import diva.graph.GraphUtilities; 039import diva.graph.modular.CompositeModel; 040import diva.graph.modular.CompositeNodeModel; 041import diva.graph.modular.EdgeModel; 042import diva.graph.modular.MutableEdgeModel; 043import diva.graph.modular.NodeModel; 044import diva.util.NullIterator; 045import ptolemy.actor.IOPort; 046import ptolemy.actor.IORelation; 047import ptolemy.data.BooleanToken; 048import ptolemy.data.IntToken; 049import ptolemy.data.Token; 050import ptolemy.data.expr.Parameter; 051import ptolemy.kernel.ComponentEntity; 052import ptolemy.kernel.ComponentPort; 053import ptolemy.kernel.ComponentRelation; 054import ptolemy.kernel.CompositeEntity; 055import ptolemy.kernel.Entity; 056import ptolemy.kernel.Port; 057import ptolemy.kernel.Relation; 058import ptolemy.kernel.util.Attribute; 059import ptolemy.kernel.util.ChangeListener; 060import ptolemy.kernel.util.ChangeRequest; 061import ptolemy.kernel.util.IllegalActionException; 062import ptolemy.kernel.util.InternalErrorException; 063import ptolemy.kernel.util.Locatable; 064import ptolemy.kernel.util.Location; 065import ptolemy.kernel.util.Nameable; 066import ptolemy.kernel.util.NamedObj; 067import ptolemy.moml.MoMLChangeRequest; 068import ptolemy.moml.Vertex; 069import ptolemy.vergil.basic.AbstractBasicGraphModel; 070import ptolemy.vergil.basic.NamedObjNodeModel; 071import ptolemy.vergil.kernel.Link; 072import ptolemy.vergil.toolbox.SnapConstraint; 073 074// NOTE: The inner classes here should be factored out as independent 075// classes, and the resulting NodeModel hierarchy should be carefully 076// thought out. This work has been started, with the NamedObjNodeModel 077// base class and the AttributeNodeModel derived class. The remaining 078// node models here and in the FSMGraphModel remain to be done. EAL. 079/////////////////////////////////////////////////////////////////// 080//// ActorGraphModel 081 082/** 083 This class represents one level of hierarchy of a Ptolemy II model. 084 The graph model represents attributes, ports, entities and relations 085 as nodes. Entities and attributes are represented in the model by the 086 icon that is used to visually depict them. Relations are represented 087 in the model by its vertices (which are visual elements that generally 088 exist in multiple places in a visual rendition). Ports represent 089 themselves in the model. 090 <p> 091 In the terminology of diva, the graph elements are "nodes" (icons, 092 vertices, and ports), and the "edges" link them. Edges 093 are represented in the model by instances of the Link class. 094 Edges may link a port and a vertex, or a port and another port. 095 For visual simplicity, both types of edges are represented by 096 an instance of the Link class. If an edge is placed between a port 097 and a vertex then the Link represents a Ptolemy II link between 098 the port and the vertex's Relation. However, if an edge is placed between 099 two ports, then it represents a relation (with no vertex) and links from 100 the relation to each port (in Ptolemy II, this is called a "connection"). 101 <p> 102 This model uses a ptolemy change listener to detect changes to the model 103 that do not originate from this model. These changes are propagated 104 as structure changed graph events to all graphListeners registered with this 105 model. This mechanism allows a graph visualization of a ptolemy model to 106 remain synchronized with the state of a mutating model. 107 108 @author Steve Neuendorffer, Contributor: Edward A. Lee, Bert Rodiers 109 @version $Id$ 110 @since Ptolemy II 2.0 111 @Pt.ProposedRating Yellow (neuendor) 112 @Pt.AcceptedRating Red (johnr) 113 */ 114public class ActorGraphModel extends AbstractBasicGraphModel { 115 /** Construct a new graph model whose root is the given composite entity. 116 * @param composite The top-level composite entity for the model. 117 */ 118 public ActorGraphModel(NamedObj composite) { 119 super(composite); 120 _linkSet = new HashSet<Link>(); 121 _update(); 122 } 123 124 /////////////////////////////////////////////////////////////////// 125 //// public methods //// 126 127 /** This implementation will delegate to the implementation in the parent 128 * class and will additionally update the model in case it is necessary. 129 * This is typically the case when a link is created to another link. 130 * If this existing link has a vertex as head or tail, 131 * we will connect with the vertex, otherwise we will 132 * remove the old link, create a new vertex, link the 133 * head and tail of the existing link with the 134 * vertex and link the new link with the vertex. 135 * It is possible to link with an existing link. 136 * If this existing link has a vertex as head or tail, 137 * we will connect with the vertex, otherwise we will 138 * remove the old link, create a new vertex, link the 139 * head and tail of the existing link with the 140 * vertex and link the new link with the vertex. 141 * In the latter case the parent class won't call _update 142 * as a result of an optimization, and hence we do it here. 143 * 144 * @param change The change that has been executed. 145 */ 146 @Override 147 public void changeExecuted(ChangeRequest change) { 148 super.changeExecuted(change); 149 150 // update the graph model if necessary. 151 if (_forceUpdate && _update()) { 152 _forceUpdate = false; 153 // Notify any graph listeners 154 // that the graph might have 155 // completely changed. 156 dispatchGraphEvent(new GraphEvent(this, 157 GraphEvent.STRUCTURE_CHANGED, getRoot())); 158 } 159 } 160 161 /** Disconnect an edge from its two endpoints and notify graph 162 * listeners with an EDGE_HEAD_CHANGED and an EDGE_TAIL_CHANGED 163 * event whose source is the given source. 164 * @param eventSource The source of the event that will be dispatched, 165 * e.g. the view that made this call. 166 * @param edge The edge. 167 */ 168 @Override 169 public void disconnectEdge(Object eventSource, Object edge) { 170 if (!(getEdgeModel(edge) instanceof MutableEdgeModel)) { 171 return; 172 } 173 174 MutableEdgeModel model = (MutableEdgeModel) getEdgeModel(edge); 175 Object head = model.getHead(edge); 176 Object tail = model.getTail(edge); 177 model.setTail(edge, null); 178 model.setHead(edge, null); 179 180 if (head != null) { 181 GraphEvent e = new GraphEvent(eventSource, 182 GraphEvent.EDGE_HEAD_CHANGED, edge, head); 183 dispatchGraphEvent(e); 184 } 185 186 if (tail != null) { 187 GraphEvent e = new GraphEvent(eventSource, 188 GraphEvent.EDGE_TAIL_CHANGED, edge, tail); 189 dispatchGraphEvent(e); 190 } 191 } 192 193 /** Return the model for the given composite object. 194 * In this class, return an instance of CompositePtolemyModel 195 * if the object is the root object of this graph model, and return 196 * an instance of IconModel if the object is a location contained 197 * by an entity. Otherwise return null. 198 * @param composite A composite object. 199 * @return A model of a composite node. 200 */ 201 @Override 202 public CompositeModel getCompositeModel(Object composite) { 203 CompositeModel result = super.getCompositeModel(composite); 204 205 if (result == null && composite instanceof Locatable 206 && ((Locatable) composite).getContainer() instanceof Entity) { 207 return _iconModel; 208 } 209 210 return result; 211 } 212 213 /** Return a MoML String that will delete the given edge from the 214 * Ptolemy model. 215 * @param edge The edge. 216 * @return A valid MoML string. 217 */ 218 @Override 219 public String getDeleteEdgeMoML(Object edge) { 220 // Note: the abstraction here is rather broken. Ideally this 221 // should look like getDeleteNodeMoML() 222 if (!(getEdgeModel(edge) instanceof LinkModel)) { 223 return ""; 224 } 225 226 LinkModel model = (LinkModel) getEdgeModel(edge); 227 return model.getDeleteEdgeMoML(edge); 228 } 229 230 /** Return a MoML String that will delete the given node from the 231 * Ptolemy model. 232 * @param node The node. 233 * @return A valid MoML string. 234 */ 235 @Override 236 public String getDeleteNodeMoML(Object node) { 237 if (!(getNodeModel(node) instanceof NamedObjNodeModel)) { 238 return ""; 239 } 240 241 NamedObjNodeModel model = (NamedObjNodeModel) getNodeModel(node); 242 return model.getDeleteNodeMoML(node); 243 } 244 245 /** Return the model for the given edge object. If the object is not 246 * an edge, then return null. 247 * @param edge An object which is assumed to be in this graph model. 248 * @return An instance of LinkModel if the object is a Link. 249 * Otherwise return null. 250 */ 251 @Override 252 public EdgeModel getEdgeModel(Object edge) { 253 if (edge instanceof Link) { 254 return _linkModel; 255 } else { 256 return null; 257 } 258 } 259 260 /** Return the model for edge objects that are instance of Link. 261 * This will return the same object as getEdgeModel() when the 262 * argument is a link. 263 * @return The model for links. 264 */ 265 public LinkModel getLinkModel() { 266 // FIXME: This design makes it impossible to have different 267 // models for different types of links. This method should 268 // be removed, and getEdgeModel() should be used instead. 269 return _linkModel; 270 } 271 272 /** Return the node model for the given object. If the object is not 273 * a node, then return null. The argument should be either an instance 274 * of Port or Vertex, or it implements Locatable. 275 * @param node An object which is assumed to be in this graph model. 276 * @return The node model for the specified node, or null if there 277 * is none. 278 */ 279 @Override 280 public NodeModel getNodeModel(Object node) { 281 if (node instanceof Port) { 282 return _portModel; 283 } else if (node instanceof Vertex) { 284 return _vertexModel; 285 } else if (node instanceof Locatable) { 286 Object container = ((Locatable) node).getContainer(); 287 288 if (container instanceof Port) { 289 return _externalPortModel; 290 } else if (container instanceof Entity) { 291 return _iconModel; 292 } 293 } 294 295 return super.getNodeModel(node); 296 } 297 298 /** Return the semantic object corresponding to the given node, edge, 299 * or composite. A "semantic object" is an object associated with 300 * a node in the graph. In this case, if the node is icon, the 301 * semantic object is an entity. If it is a vertex or a link, the 302 * semantic object is a relation. If it is a port, then the 303 * semantic object is the port itself. 304 * @param element A graph element. 305 * @return The semantic object associated with this element, or null 306 * if the object is not recognized. 307 */ 308 @Override 309 public Object getSemanticObject(Object element) { 310 if (element instanceof Vertex) { 311 return ((Vertex) element).getContainer(); 312 } else if (element instanceof Link) { 313 return ((Link) element).getRelation(); 314 } 315 316 return super.getSemanticObject(element); 317 } 318 319 /** Delete a node from its parent graph and notify 320 * graph listeners with a NODE_REMOVED event. 321 * @param eventSource The source of the event that will be dispatched, 322 * e.g. the view that made this call. 323 * @param node The node. 324 */ 325 @Override 326 public void removeNode(Object eventSource, Object node) { 327 if (!(getNodeModel(node) instanceof NamedObjNodeModel)) { 328 return; 329 } 330 331 NamedObjNodeModel model = (NamedObjNodeModel) getNodeModel(node); 332 333 model.removeNode(eventSource, node); 334 } 335 336 // FIXME: The following methods are probably inappropriate. 337 // They make it impossible to have customized models for 338 // particular links or icons. getLinkModel() and 339 // getNodeModel() should be sufficient. 340 // Big changes needed, however to make this work. 341 // The huge inner classes below should be factored out as 342 // separate classes. EAL 343 /** Get the icon model. 344 * @return The icon model. 345 */ 346 public IconModel getIconModel() { 347 return _iconModel; 348 } 349 350 /** Get the port model. 351 * @return The port model. 352 */ 353 public PortModel getPortModel() { 354 return _portModel; 355 } 356 357 /** Get the external port model. 358 * @return The external port model. 359 */ 360 public ExternalPortModel getExternalPortModel() { 361 return _externalPortModel; 362 } 363 364 /** Get the vertex model. 365 * @return The vertex model. 366 */ 367 public VertexModel getVertexModel() { 368 return _vertexModel; 369 } 370 371 // End of FIXME. 372 /////////////////////////////////////////////////////////////////// 373 //// protected methods //// 374 375 /** Get an unmodifiable copy of the link set. 376 * 377 * @return The link set. 378 */ 379 protected Set<?> _getLinkSet() { 380 return Collections.unmodifiableSet(_linkSet); 381 } 382 383 /** Remove a link from the link set. This function is not made synchronized. 384 * Concurrent modification on the link set should be avoided. 385 * 386 * @param link The link to be removed. 387 */ 388 protected void _removeLink(Link link) { 389 _linkSet.remove(link); 390 } 391 392 /** Update the graph model. This is called whenever a change request is 393 * executed. In this class the internal set of link objects is created 394 * to represent each of the links in the graph, and to remove any 395 * link objects that are incorrect (e.g., do not have both ends 396 * in the model). 397 * This method is usually called just before 398 * issuing a graph event. If this method returns false, then the graph 399 * event should not be issued, because further changes are necessary. 400 * @return True if the model was successfully updated, or false if 401 * further change requests were queued. 402 */ 403 @Override 404 protected boolean _update() { 405 // Go through all the links that currently exist, and remove 406 // any that don't have both ends in the model. 407 408 Iterator<Link> links = _linkSet.iterator(); 409 410 while (links.hasNext()) { 411 Link link = links.next(); 412 Relation relation = link.getRelation(); 413 414 // Undo needs this: Check that the relation hasn't been removed 415 if (relation == null || relation.getContainer() == null 416 || _isHidden(relation)) { 417 // NOTE: We used to not do the next three lines when 418 // relation == null, but this seems better. 419 // EAL 6/26/05. 420 link.setHead(null); 421 link.setTail(null); 422 links.remove(); 423 continue; 424 } 425 426 boolean headOK = GraphUtilities.isContainedNode(link.getHead(), 427 getRoot(), this); 428 boolean tailOK = GraphUtilities.isContainedNode(link.getTail(), 429 getRoot(), this); 430 431 // If the head or tail has been removed, then remove this link. 432 if (!(headOK && tailOK)) { 433 Object headObj = getSemanticObject(link.getHead()); 434 Object tailObj = getSemanticObject(link.getTail()); 435 link.setHead(null); 436 link.setTail(null); 437 links.remove(); 438 439 if (headObj instanceof Port && tailObj instanceof Port 440 && relation.getContainer() != null 441 && relation.linkedPortList().size() < 2) { 442 NamedObj container = getPtolemyModel(); 443 444 // remove the relation This should trigger removing the 445 // other link. This avoids turning a direct connection 446 // into a half connection with a diamond. 447 // Note that the source is NOT the graphmodel, so this 448 // will trigger the changerequest listener to 449 // redraw the graph again. 450 MoMLChangeRequest request = new MoMLChangeRequest(container, 451 container, "<deleteRelation name=\"" 452 + relation.getName(container) + "\"/>\n"); 453 request.setUndoable(true); 454 455 // Need to merge the undo for this request in with one that 456 // triggered it 457 request.setMergeWithPreviousUndo(true); 458 container.requestChange(request); 459 460 // If updating requires further updates to the model 461 // i.e. the above change request, then return false. 462 // this is so that rerendering doesn't happen until the 463 // graph model has reached a stable point. 464 // Note that there is a bit of a performance tradeoff 465 // here as to whether we queue a bunch of mutations in 466 // parallel (which may be redundant) or queue them 467 // serially (which may be slow). 468 return false; 469 } 470 } 471 } 472 473 // Now create Links for links that may be new 474 NamedObj ptolemyModel = getPtolemyModel(); 475 476 if (ptolemyModel instanceof CompositeEntity) { 477 Iterator<?> relations = ((CompositeEntity) ptolemyModel) 478 .relationList().iterator(); 479 480 while (relations.hasNext()) { 481 _updateLinks((ComponentRelation) relations.next()); 482 } 483 } 484 485 return super._update(); 486 } 487 488 /////////////////////////////////////////////////////////////////// 489 //// private methods //// 490 491 /** Return true if the relation has a _hide attribute indicating 492 * that it is hidden. 493 * @return True if the relation is hidden. 494 */ 495 private boolean _isHidden(Relation relation) { 496 Attribute hide = relation.getAttribute("_hide"); 497 if (hide != null) { 498 if (hide instanceof Parameter) { 499 Token token; 500 try { 501 token = ((Parameter) hide).getToken(); 502 if (token instanceof BooleanToken) { 503 if (((BooleanToken) token).booleanValue()) { 504 return true; 505 } 506 } 507 } catch (IllegalActionException e) { 508 throw new InternalErrorException(e); 509 } 510 } else { 511 // The mere presence of the attribute will hide 512 // the relation. 513 return true; 514 } 515 } 516 return false; 517 } 518 519 /** Make sure that there is a Link object representing every 520 * link connected to the given relation. Create links if necessary. 521 */ 522 private void _updateLinks(ComponentRelation relation) { 523 // If the relation is hidden, then skip it. 524 if (_isHidden(relation)) { 525 return; 526 } 527 // FIXME: This method is expensive for large graphs. 528 // It is called for each relation, it creates a new list 529 // of links for each relation, it then goes through the full 530 // list of all existing links, looking only at the ones 531 // associated with this relation. Ugh. 532 // Create a list of linked objects. 533 // We will remove objects from this list as we discover 534 // existing links to them, and then create links to any 535 // remaining objects in the list. 536 537 List<?> linkedObjects = relation.linkedObjectsList(); 538 int linkedObjectsCount = linkedObjects.size(); 539 540 for (Link link : new LinkedList<Link>(_linkSet)) { 541 542 // If this link matches a link in the linkedObjects list, 543 // then we remove that link from that list, since we don't 544 // have to manufacture that link. 545 Object tail = link.getTail(); 546 Object tailObj = getSemanticObject(tail); 547 Object head = link.getHead(); 548 Object headObj = getSemanticObject(head); 549 550 if (tailObj != relation && headObj != relation 551 && link.getRelation() != relation) { 552 // The link does not involve this relation. Skip it. 553 // NOTE: Used to skip it if the relation field of the link 554 // didn't match this relation. But we need to ignore 555 // that field for links between relations, since that 556 // field will be arbitrarily one of the relations, 557 // and we'll end up creating two links where there 558 // should be one. 559 // EAL 6/26/05 560 continue; 561 } 562 563 if (tailObj != relation && headObj != relation 564 && linkedObjectsCount > 2) { 565 // When the link is a direct link between two ports but the 566 // relation has more than 2 ends, the link is corrupted and 567 // should be deleted. This could happen as a result of model 568 // transformation of the model in the frame. 569 // tfeng (03/10/2009) 570 link.setHead(null); 571 link.setTail(null); 572 _linkSet.remove(link); 573 continue; 574 } 575 576 if (tailObj != null && linkedObjects.contains(tailObj)) { 577 // The tail is an object in the list. 578 linkedObjects.remove(tailObj); 579 } else if (tailObj != relation) { 580 // Unless the tail object is this relation, the link 581 // must be spurious. Remove the link. 582 link.setHead(null); 583 link.setTail(null); 584 _linkSet.remove(link); 585 } 586 587 if (headObj != null && linkedObjects.contains(headObj)) { 588 // The head is an object in the list. 589 linkedObjects.remove(headObj); 590 } else if (headObj != relation) { 591 // Unless the head object is this relation, the link 592 // must be spurious. Remove the link. 593 link.setHead(null); 594 link.setTail(null); 595 _linkSet.remove(link); 596 } 597 } 598 599 // Count the remaining linked objects, which are those 600 // for which there is no Link object. 601 int unlinkedPortCount = linkedObjects.size(); 602 603 // If there are no links left to create, then just return. 604 if (unlinkedPortCount == 0) { 605 return; 606 } 607 608 // Get the Root vertex. This is where we will manufacture links. 609 // The root vertex is the one with no linked vertices. 610 Vertex rootVertex = null; 611 Iterator<?> vertexes = relation.attributeList(Vertex.class).iterator(); 612 613 while (vertexes.hasNext()) { 614 Vertex v = (Vertex) vertexes.next(); 615 616 if (v.getLinkedVertex() == null) { 617 rootVertex = v; 618 } 619 } 620 621 // If there are no verticies, and the relation has exactly 622 // two connections, neither of which has been made yet, then 623 // create a link without a vertex for the relation. 624 if (rootVertex == null && linkedObjectsCount == 2 625 && unlinkedPortCount == 2 626 && linkedObjects.get(0) instanceof Port 627 && linkedObjects.get(1) instanceof Port) { 628 Port port1 = (Port) linkedObjects.get(0); 629 Port port2 = (Port) linkedObjects.get(1); 630 Object head = null; 631 Object tail = null; 632 633 if (port1.getContainer().equals(getRoot())) { 634 head = _getLocation(port1); 635 } else { 636 head = port1; 637 } 638 639 if (port2.getContainer().equals(getRoot())) { 640 tail = _getLocation(port2); 641 } else { 642 tail = port2; 643 } 644 645 Link link; 646 647 try { 648 link = new Link(); 649 _linkSet.add(link); 650 } catch (Exception e) { 651 throw new InternalErrorException("Failed to create " 652 + "new link, even though one does not " 653 + "already exist:" + e.getMessage()); 654 } 655 656 link.setRelation(relation); 657 link.setHead(head); 658 link.setTail(tail); 659 } else { 660 // A regular relation with a diamond. 661 // Create a vertex if one is not found. 662 if (rootVertex == null) { 663 try { 664 String name = relation.uniqueName("vertex"); 665 rootVertex = new Vertex(relation, name); 666 667 // Have to manually handle propagation, since 668 // the MoML parser is not involved. 669 // FIXME: This could cause a name collision! 670 // (Unlikely though since auto naming will take 671 // into account subclasses). 672 rootVertex.propagateExistence(); 673 } catch (Throwable throwable) { 674 throw new InternalErrorException(null, throwable, 675 "Failed to create " 676 + "new vertex, even though one does not " 677 + "already exist:" 678 + throwable.getMessage()); 679 } 680 } 681 682 // Create any required links for this relation. 683 Iterator<?> linkedObjectsIterator = linkedObjects.iterator(); 684 685 while (linkedObjectsIterator.hasNext()) { 686 Object portOrRelation = linkedObjectsIterator.next(); 687 688 // Set the head to the port or relation. More precisely: 689 // If it is a port belonging to the composite, then 690 // set the head to a Location contained by the port. 691 // If is a port belonging to an actor, then set 692 // the head to the port. 693 // If it is a relation, then set the head to the 694 // root vertex of the relation. 695 Object head = null; 696 697 if (portOrRelation instanceof Port) { 698 Port port = (Port) portOrRelation; 699 700 if (port.getContainer().equals(getRoot())) { 701 head = _getLocation(port); 702 } else { 703 head = port; 704 } 705 } else { 706 // Get the Root vertex of the other relation. 707 // The root vertex is the one with no linked vertices. 708 vertexes = ((Relation) portOrRelation) 709 .attributeList(Vertex.class).iterator(); 710 711 while (vertexes.hasNext()) { 712 Vertex v = (Vertex) vertexes.next(); 713 714 if (v.getLinkedVertex() == null) { 715 head = v; 716 } 717 } 718 } 719 720 Link link; 721 722 try { 723 link = new Link(); 724 _linkSet.add(link); 725 } catch (Exception e) { 726 throw new InternalErrorException("Failed to create " 727 + "new link, even though one does not " 728 + "already exist:" + e.getMessage()); 729 } 730 731 link.setRelation(relation); 732 link.setHead(head); 733 link.setTail(rootVertex); 734 } 735 } 736 } 737 738 /////////////////////////////////////////////////////////////////// 739 //// private variables //// 740 741 /** The models of the different types of nodes and edges. */ 742 private ExternalPortModel _externalPortModel = new ExternalPortModel(); 743 744 /** A flag to force calling update when a ChangeRequest has been executed. */ 745 private boolean _forceUpdate = false; 746 747 private IconModel _iconModel = new IconModel(); 748 749 private LinkModel _linkModel = new LinkModel(); 750 751 /** The set of all links in the model. */ 752 private Set<Link> _linkSet; 753 754 private PortModel _portModel = new PortModel(); 755 756 private VertexModel _vertexModel = new VertexModel(); 757 758 /////////////////////////////////////////////////////////////////// 759 //// inner classes //// 760 761 /** The model for ports that make external connections to this graph. 762 * These ports are always contained by the root of this graph model. 763 */ 764 public class ExternalPortModel extends NamedObjNodeModel { 765 /** Return a MoML String that will delete the given node from the 766 * Ptolemy model. The MoML assumes a context that is the container 767 * of the port. 768 * @param node The node. 769 * @return A valid MoML string. 770 */ 771 @Override 772 public String getDeleteNodeMoML(Object node) { 773 Locatable location = (Locatable) node; 774 ComponentPort port = (ComponentPort) location.getContainer(); 775 StringBuffer moml = new StringBuffer(); 776 moml.append("<deletePort name=\"" + port.getName() + "\"/>\n"); 777 return moml.toString(); 778 } 779 780 /** Return the graph parent of the given node. 781 * @param node The node, which is assumed to be a port contained in 782 * the root of this graph model. 783 * @return The root of this graph model. 784 */ 785 @Override 786 public Object getParent(Object node) { 787 return ((Locatable) node).getContainer().getContainer(); 788 } 789 790 /** Return an iterator over the edges coming into the given node. 791 * This method first ensures that there is a link 792 * object for every link. 793 * Then the iterator is constructed by 794 * removing any links that do not have the given node as head. 795 * @param node The node, which is assumed to be a port contained in 796 * the root of this graph model. 797 * @return An iterator of Link objects, all of which have 798 * the given node as their head. 799 */ 800 @Override 801 public Iterator inEdges(Object node) { 802 Locatable location = (Locatable) node; 803 //ComponentPort port = (ComponentPort) location.getContainer(); 804 805 // make sure that the links to relations that we are connected to 806 // are up to date. 807 // Go through all the links, creating a list of 808 // those we are connected to. 809 List<Link> portLinkList = new LinkedList<Link>(); 810 811 for (Link link : _linkSet) { 812 Object head = link.getHead(); 813 814 if (head != null && head.equals(location)) { 815 portLinkList.add(link); 816 } 817 } 818 819 return portLinkList.iterator(); 820 } 821 822 /** Return an iterator over the edges coming out of the given node. 823 * This iterator is constructed by looping over all the relations 824 * that the port is connected to, and ensuring that there is a link 825 * object for every link. Then the iterator is constructed by 826 * removing any links that do not have the given node as tail. 827 * @param node The node, which is assumed to be a port contained in 828 * the root of this graph model. 829 * @return An iterator of Link objects, all of which have their 830 * tail as the given node. 831 */ 832 @Override 833 public Iterator outEdges(Object node) { 834 Locatable location = (Locatable) node; 835 //ComponentPort port = (ComponentPort) location.getContainer(); 836 837 // make sure that the links to relations that we are connected to 838 // are up to date. 839 // Go through all the links, creating a list of 840 // those we are connected to. 841 List<Link> portLinkList = new LinkedList<Link>(); 842 843 for (Link link : _linkSet) { 844 Object tail = link.getTail(); 845 846 if (tail != null && tail.equals(location)) { 847 portLinkList.add(link); 848 } 849 } 850 851 return portLinkList.iterator(); 852 } 853 854 /** Remove the given edge from the model. 855 * @param eventSource The source of the event that will be dispatched, 856 * e.g. the view that made this call. 857 * @param node The node. 858 */ 859 @Override 860 public void removeNode(final Object eventSource, Object node) { 861 Locatable location = (Locatable) node; 862 ComponentPort port = (ComponentPort) location.getContainer(); 863 NamedObj container = port.getContainer(); 864 ; 865 866 StringBuffer moml = new StringBuffer(); 867 moml.append( 868 "<deletePort name=\"" + port.getName(container) + "\"/>\n"); 869 870 // Note: The source is NOT the graph model. 871 MoMLChangeRequest request = new MoMLChangeRequest(this, container, 872 moml.toString()); 873 request.setUndoable(true); 874 container.requestChange(request); 875 } 876 } 877 878 /** The model for an icon that contains ports. 879 */ 880 public static class IconModel extends NamedObjNodeModel 881 implements CompositeNodeModel { 882 // FindBugs suggests making this class static so as to decrease 883 // the size of instances and avoid dangling references. 884 885 /** Return a MoML String that will delete the given node from the 886 * Ptolemy model. The returned string assumes that the context is 887 * the container of the object with an icon. 888 * @param node The node. 889 * @return A valid MoML string. 890 */ 891 @Override 892 public String getDeleteNodeMoML(Object node) { 893 NamedObj deleteObj = ((Locatable) node).getContainer(); 894 String moml = "<deleteEntity name=\"" + deleteObj.getName() 895 + "\"/>\n"; 896 return moml; 897 } 898 899 /** Return the number of nodes contained in 900 * this graph or composite node. 901 * @param composite The composite, which is assumed to be an icon. 902 * @return The number of ports contained in the container of the icon. 903 */ 904 @Override 905 public int getNodeCount(Object composite) { 906 Locatable location = (Locatable) composite; 907 return ((ComponentEntity) location.getContainer()).portList() 908 .size(); 909 } 910 911 /** Return the graph parent of the given node. 912 * @param node The node, which is assumed to be an icon. 913 * @return The container of the Icon's container, which should be 914 * the root of the graph. 915 */ 916 @Override 917 public Object getParent(Object node) { 918 return ((Locatable) node).getContainer().getContainer(); 919 } 920 921 /** Return an iterator over the edges coming into the given node. 922 * @param node The node, which is assumed to be an icon. 923 * @return A NullIterator, since no edges are attached to icons. 924 */ 925 @Override 926 public Iterator inEdges(Object node) { 927 return new NullIterator(); 928 } 929 930 /** Provide an iterator over the nodes in the 931 * given graph or composite node. The nodes are ports, so if the 932 * container of the node is not an entity, then an empty iterator 933 * is returned. This iterator 934 * does not necessarily support removal operations. 935 * @param composite The composite, which is assumed to be an icon. 936 * @return An iterator over the ports contained in the container 937 * of the icon. 938 */ 939 @Override 940 public Iterator nodes(Object composite) { 941 Locatable location = (Locatable) composite; 942 Nameable container = location.getContainer(); 943 944 if (container instanceof ComponentEntity) { 945 ComponentEntity entity = (ComponentEntity) container; 946 return entity.portList().iterator(); 947 } else { 948 return new NullIterator(); 949 } 950 } 951 952 /** 953 * Provide an iterator over the nodes that should 954 * be rendered prior to the edges. This iterator 955 * does not necessarily support removal operations. 956 * In this base class, this returns the same iterator 957 * as the nodes(Object) method. 958 * @param composite The composite, which is assumed to be an icon. 959 * @return An iterator of nodes that should be rendered before 960 * the edges. 961 */ 962 @Override 963 public Iterator nodesBeforeEdges(Object composite) { 964 return nodes(composite); 965 } 966 967 /** 968 * Provide an iterator over the nodes that should 969 * be rendered after to the edges. This iterator 970 * does not necessarily support removal operations. 971 * In this base class, this returns an iterator over 972 * nothing. 973 * @param composite The composite, which is assumed to be an icon. 974 * @return An iterator of nodes that should be rendered after 975 * the edges. 976 */ 977 @Override 978 public Iterator nodesAfterEdges(Object composite) { 979 return new NullIterator(); 980 } 981 982 /** Return an iterator over the edges coming out of the given node. 983 * @param node The node, which is assumed to be an icon. 984 * @return A NullIterator, since no edges are attached to icons. 985 */ 986 @Override 987 public Iterator outEdges(Object node) { 988 return new NullIterator(); 989 } 990 991 /** Remove the given node from the model. The node is assumed 992 * to be an icon. 993 * @param eventSource The source of the event that will be dispatched, 994 * e.g. the view that made this call. 995 * @param node The node. 996 */ 997 @Override 998 public void removeNode(final Object eventSource, Object node) { 999 NamedObj deleteObj = ((Locatable) node).getContainer(); 1000 1001 if (!(deleteObj instanceof ComponentEntity)) { 1002 throw new InternalErrorException( 1003 "Attempt to remove a node that is not an Entity. " 1004 + "node = " + node); 1005 } 1006 1007 // Make the request in the context of the container. 1008 NamedObj container = deleteObj.getContainer(); 1009 ; 1010 1011 String moml = "<deleteEntity name=\"" + deleteObj.getName() 1012 + "\"/>\n"; 1013 1014 // Note: The source is NOT the graph model. 1015 MoMLChangeRequest request = new MoMLChangeRequest(this, container, 1016 moml); 1017 request.setUndoable(true); 1018 container.requestChange(request); 1019 } 1020 } 1021 1022 /** The model for links that connect two ports, or a port and a vertex. 1023 */ 1024 public class LinkModel implements MutableEdgeModel { 1025 /** Return true if the head of the given edge can be attached to the 1026 * given node. 1027 * @param edge The edge to attach, which is assumed to be a link. 1028 * @param node The node to attach to. 1029 * @return True if the node is a port or a vertex, or a location 1030 * representing a port. 1031 */ 1032 @Override 1033 public boolean acceptHead(Object edge, Object node) { 1034 if (node instanceof Port || node instanceof Vertex 1035 || node instanceof Locatable && ((Locatable) node) 1036 .getContainer() instanceof Port) { 1037 return true; 1038 } else { 1039 return false; 1040 } 1041 } 1042 1043 /** Return true if the tail of the given edge can be attached to the 1044 * given node. 1045 * @param edge The edge to attach, which is assumed to be a link. 1046 * @param node The node to attach to. 1047 * @return True if the node is a port or a vertex, or a location 1048 * representing a port. 1049 */ 1050 @Override 1051 public boolean acceptTail(Object edge, Object node) { 1052 if (node instanceof Port || node instanceof Vertex 1053 || node instanceof Locatable && ((Locatable) node) 1054 .getContainer() instanceof Port) { 1055 return true; 1056 } else { 1057 return false; 1058 } 1059 } 1060 1061 /** Generate the moml to add a vertex to an exist link. 1062 * @param moml The moml to add the vertex to the link. 1063 * @param failmoml The moml to undo these changed when 1064 * something goes wrong. 1065 * @param container The container. 1066 * @param oldLink The link that will be replace by two new once 1067 * and a vertex in between. 1068 * @param newRelationName The name of the new relation. 1069 * @param x The x coordinate of the location of the vertex. 1070 * @param y The y coordinate of the location of the vertex. 1071 */ 1072 public void addNewVertexToLink(final StringBuffer moml, 1073 final StringBuffer failmoml, final CompositeEntity container, 1074 Link oldLink, String newRelationName, double x, double y) { 1075 1076 final String vertexName = "vertex1"; 1077 ComponentRelation relation = oldLink.getRelation(); 1078 int width = IORelation.WIDTH_TO_INFER; 1079 if (relation instanceof IORelation) { 1080 Parameter widthPar = ((IORelation) relation).width; 1081 try { 1082 IntToken t = (IntToken) widthPar.getToken(); 1083 if (t != null) { 1084 width = t.intValue(); 1085 } 1086 } catch (IllegalActionException e) { 1087 // ignore the exception. If we can't request the 1088 // width, we'll use WIDTH_TO_INFER 1089 } 1090 } 1091 1092 // Create the relation. 1093 moml.append("<relation name=\"" + newRelationName + "\">\n"); 1094 moml.append( 1095 "<property name=\"width\" class=\"ptolemy.data.expr.Parameter\"" 1096 + " value=\"" + width + "\"></property>"); 1097 moml.append("<vertex name=\"" + vertexName + "\" value=\"{"); 1098 moml.append(x + ", " + y); 1099 moml.append("}\"/>\n"); 1100 1101 moml.append("</relation>"); 1102 1103 // We will remove the existing link, but before doing that 1104 // we need to retrieve the index to reconnect at the correct index 1105 1106 boolean headIsActorPort = oldLink.getHead() instanceof IOPort; 1107 boolean tailIsActorPort = oldLink.getTail() instanceof IOPort; 1108 1109 NamedObj oldHead = (NamedObj) oldLink.getHead(); 1110 NamedObj oldTail = (NamedObj) oldLink.getTail(); 1111 1112 _unlinkMoML(container, moml, oldHead, oldTail, relation); 1113 1114 NamedObj oldHeadSemantic = (NamedObj) getSemanticObject(oldHead); 1115 1116 if (oldHeadSemantic != null) { 1117 int headRelationIndex = oldHeadSemantic instanceof IOPort 1118 ? IOPort.getRelationIndex((IOPort) oldHeadSemantic, 1119 relation, headIsActorPort) 1120 : -1; 1121 _linkWithRelation(moml, failmoml, container, oldHeadSemantic, 1122 headRelationIndex, newRelationName); 1123 } 1124 1125 NamedObj oldTailSemantic = (NamedObj) getSemanticObject(oldTail); 1126 1127 if (oldTailSemantic != null) { 1128 int tailRelationIndex = oldTailSemantic instanceof IOPort 1129 ? IOPort.getRelationIndex((IOPort) oldTailSemantic, 1130 relation, tailIsActorPort) 1131 : -1; 1132 1133 _linkWithRelation(moml, failmoml, container, oldTailSemantic, 1134 tailRelationIndex, newRelationName); 1135 } 1136 } 1137 1138 /** Return a MoML String that will delete the given edge from the 1139 * Ptolemy model. 1140 * @param edge The edge. 1141 * @return A valid MoML string. 1142 */ 1143 public String getDeleteEdgeMoML(Object edge) { 1144 final Link link = (Link) edge; 1145 NamedObj linkHead = (NamedObj) link.getHead(); 1146 NamedObj linkTail = (NamedObj) link.getTail(); 1147 Relation linkRelation = link.getRelation(); 1148 1149 // This moml is parsed to execute the change 1150 StringBuffer moml = new StringBuffer(); 1151 1152 // Make the request in the context of the container. 1153 // JDK1.2.2 fails to compile the next line. 1154 NamedObj container = getPtolemyModel(); 1155 1156 // create moml to unlink any existing. 1157 try { 1158 _unlinkMoML(container, moml, linkHead, linkTail, linkRelation); 1159 } catch (Exception ex) { 1160 throw new RuntimeException(ex.getMessage()); 1161 } 1162 1163 return moml.toString(); 1164 } 1165 1166 /** Return the head node of the given edge. 1167 * @param edge The edge, which is assumed to be a link. 1168 * @return The node that is the head of the specified edge. 1169 * @see #setHead(Object, Object) 1170 */ 1171 @Override 1172 public Object getHead(Object edge) { 1173 return ((Link) edge).getHead(); 1174 } 1175 1176 /** Return the tail node of the specified edge. 1177 * @param edge The edge, which is assumed to be a link. 1178 * @return The node that is the tail of the specified edge. 1179 * @see #setTail(Object, Object) 1180 */ 1181 @Override 1182 public Object getTail(Object edge) { 1183 return ((Link) edge).getTail(); 1184 } 1185 1186 /** Return true if this edge is directed. 1187 * In this model, none of edges 1188 * are directed, so this always returns false. 1189 * @param edge The edge, which is assumed to be a link. 1190 * @return False. 1191 */ 1192 @Override 1193 public boolean isDirected(Object edge) { 1194 return false; 1195 } 1196 1197 /** Connect the given edge to the given head node. 1198 * This class queues a new change request with the ptolemy model 1199 * to make this modification. 1200 * @param edge The edge, which is assumed to be a link. 1201 * @param newLinkHead The new head for the edge, which is assumed to 1202 * be a location representing a port, a port or a vertex. 1203 * @see #getHead(Object) 1204 */ 1205 @Override 1206 public void setHead(final Object edge, final Object newLinkHead) { 1207 _setHeadOrTail(edge, newLinkHead, true); 1208 } 1209 1210 /** Connect the given edge to the given tail node. 1211 * This class queues a new change request with the ptolemy model 1212 * to make this modification. 1213 * @param edge The edge, which is assumed to be a link. 1214 * @param newLinkTail The new tail for the edge, which is 1215 * assumed to be a location representing a port, a port or a 1216 * vertex. 1217 * @see #getTail(Object) 1218 */ 1219 @Override 1220 public void setTail(final Object edge, final Object newLinkTail) { 1221 _setHeadOrTail(edge, newLinkTail, false); 1222 } 1223 1224 /////////////////////////////////////////////////////////////////// 1225 //// private methods //// 1226 1227 /** Get a location for a new relations between the ports denoted by 1228 * semanticHead and semanticTail. 1229 * @param semanticHead The head for the new relation. 1230 * @param semanticTail The tail for the new relation. 1231 * @param headIsActorPort A flag that specifies whether this is a 1232 * actor port of a actor or a stand-alone port. 1233 * @param tailIsActorPort A flag that specifies whether this is a 1234 * actor port of a actor or a stand-alone port. 1235 * @return The new location. 1236 */ 1237 private double[] _getNewLocation(NamedObj semanticHead, 1238 NamedObj semanticTail, boolean headIsActorPort, 1239 boolean tailIsActorPort) { 1240 double[] headLocation = _getLocation( 1241 headIsActorPort ? semanticHead.getContainer() 1242 : semanticHead).getLocation(); 1243 double[] tailLocation = _getLocation( 1244 tailIsActorPort ? semanticTail.getContainer() 1245 : semanticTail).getLocation(); 1246 double[] newLocation = new double[2]; 1247 newLocation[0] = (headLocation[0] + tailLocation[0]) / 2.0; 1248 newLocation[1] = (headLocation[1] + tailLocation[1]) / 2.0; 1249 newLocation = SnapConstraint.constrainPoint(newLocation); 1250 return newLocation; 1251 } 1252 1253 /** Append moml to the given buffer that connects a link with the 1254 * given head and tail. Names in the moml that is written will be 1255 * relative to the given container. This may require adding a 1256 * vertex to the ptolemy model. 1257 * If no vertex is added, then return null. 1258 * @param container The container composite actor. 1259 * @param moml The string buffer to write the MoML to. 1260 * @param failmoml The string buffer to write alternative 1261 * MoML to, to be used if the first MoML fails. 1262 * @param linkHead The head vertex or port. 1263 * @param linkTail The tail vertex or port. 1264 * @return The MoML to establish a link, or null if no vertex is added. 1265 */ 1266 private String _linkMoML(NamedObj container, StringBuffer moml, 1267 StringBuffer failmoml, NamedObj linkHead, NamedObj linkTail) 1268 throws Exception { 1269 if (linkHead != null && linkTail != null) { 1270 NamedObj head = (NamedObj) getSemanticObject(linkHead); 1271 NamedObj tail = (NamedObj) getSemanticObject(linkTail); 1272 1273 if (head instanceof ComponentPort 1274 && tail instanceof ComponentPort) { 1275 ComponentPort headPort = (ComponentPort) head; 1276 ComponentPort tailPort = (ComponentPort) tail; 1277 NamedObj ptolemyModel = getPtolemyModel(); 1278 1279 // Linking two ports with a new relation. 1280 String relationName = ptolemyModel.uniqueName("relation"); 1281 1282 // If the context is not the entity that we're editing, 1283 // then we need to set the context correctly. 1284 if (ptolemyModel != container) { 1285 String contextString = "<entity name=\"" 1286 + ptolemyModel.getName(container) + "\">\n"; 1287 moml.append(contextString); 1288 failmoml.append(contextString); 1289 } 1290 1291 // Note that we use no class so that we use the container's 1292 // factory method when this gets parsed 1293 moml.append("<relation name=\"" + relationName + "\"/>\n"); 1294 moml.append("<link port=\"" + headPort.getName(ptolemyModel) 1295 + "\" relation=\"" + relationName + "\"/>\n"); 1296 moml.append("<link port=\"" + tailPort.getName(ptolemyModel) 1297 + "\" relation=\"" + relationName + "\"/>\n"); 1298 1299 // Record moml so that we can blow away these 1300 // links in case we can't create them 1301 failmoml.append("<unlink port=\"" 1302 + headPort.getName(ptolemyModel) + "\" relation=\"" 1303 + relationName + "\"/>\n"); 1304 failmoml.append("<unlink port=\"" 1305 + tailPort.getName(ptolemyModel) + "\" relation=\"" 1306 + relationName + "\"/>\n"); 1307 failmoml.append("<deleteRelation name=\"" + relationName 1308 + "\"/>\n"); 1309 1310 // close the context 1311 if (ptolemyModel != container) { 1312 moml.append("</entity>"); 1313 failmoml.append("</entity>"); 1314 } 1315 1316 // Ugh this is ugly. 1317 if (ptolemyModel != container) { 1318 return ptolemyModel.getName(container) + "." 1319 + relationName; 1320 } else { 1321 return relationName; 1322 } 1323 } else if (head instanceof ComponentPort 1324 && linkTail instanceof Vertex) { 1325 // Linking a port to an existing relation. 1326 moml.append("<link port=\"" + head.getName(container) 1327 + "\" relation=\"" + tail.getName(container) 1328 + "\"/>\n"); 1329 return tail.getName(container); 1330 } else if (tail instanceof ComponentPort 1331 && linkHead instanceof Vertex) { 1332 // Linking a port to an existing relation. 1333 moml.append("<link port=\"" + tail.getName(container) 1334 + "\" relation=\"" + head.getName(container) 1335 + "\"/>\n"); 1336 return head.getName(container); 1337 } else if (linkHead instanceof Vertex 1338 && linkTail instanceof Vertex) { 1339 moml.append("<link relation1=\"" + tail.getName(container) 1340 + "\" relation2=\"" + head.getName(container) 1341 + "\"/>\n"); 1342 return head.getName(container); 1343 } else { 1344 throw new RuntimeException("Link failed: " + "Head = " 1345 + head + ", Tail = " + tail); 1346 } 1347 } else { 1348 // No Linking to do. 1349 return null; 1350 } 1351 } 1352 1353 /** Append moml to the given buffer that connects a relation with the 1354 * given semanticObject. If relationIndex equals -1 it will 1355 * be ignored, otherwise the relation will be connected at index relationIndex at 1356 * the semanticObject, in case it represents a port. 1357 * @param moml The string buffer to write the MoML to. 1358 * @param failmoml The string buffer to write alternative 1359 * MoML to, to be used if the first MoML fails. 1360 * @param container The container composite actor. 1361 * @param semanticObject The semantic object (relation or port). 1362 * @param relationIndex The index of the relation at the port. 1363 * @param relationName The name of the relation. 1364 */ 1365 private void _linkWithRelation(final StringBuffer moml, 1366 final StringBuffer failmoml, final CompositeEntity container, 1367 NamedObj semanticObject, int relationIndex, 1368 String relationName) { 1369 1370 if (semanticObject instanceof ComponentPort) { 1371 moml.append("<link port=\"" + semanticObject.getName(container) 1372 + "\" relation=\"" + relationName); 1373 if (relationIndex != -1) { 1374 moml.append("\" insertAt=\"" + relationIndex); 1375 } 1376 moml.append("\"/>\n"); 1377 1378 // Record moml so that we can blow away these 1379 // links in case we can't create them 1380 failmoml.append( 1381 "<unlink port=\"" + semanticObject.getName(container) 1382 + "\" relation=\"" + relationName + "\"/>\n"); 1383 } else if (semanticObject instanceof Relation) { 1384 moml.append( 1385 "<link relation1=\"" + semanticObject.getName(container) 1386 + "\" relation2=\"" + relationName + "\"/>\n"); 1387 failmoml.append("<unlink relation1=\"" 1388 + semanticObject.getName(container) + "\" relation2=\"" 1389 + relationName + "\"/>\n"); 1390 } else { 1391 throw new RuntimeException("Link failed: " + "Object = " 1392 + semanticObject + ", Relation = " + relationName); 1393 } 1394 } 1395 1396 /** Connect the given edge to the given head or tail node. 1397 * This class queues a new change request with the ptolemy model 1398 * to make this modification. 1399 * @param edge The edge, which is assumed to be a link. 1400 * @param newLinkHeadOrTail The new head or tail for the edge, 1401 * which is assumed to be a location representing a port, 1402 * a port or a vertex. 1403 * @param isHead True when newLinkHeadOrTail represents the head 1404 * @see #setHead(Object, Object) 1405 * @see #setTail(Object, Object) 1406 */ 1407 private void _setHeadOrTail(final Object edge, 1408 final Object newLinkHeadOrTail, final boolean isHead) { 1409 final Link link = (Link) edge; 1410 final NamedObj linkHead = (NamedObj) link.getHead(); 1411 final NamedObj linkTail = (NamedObj) link.getTail(); 1412 Relation linkRelation = link.getRelation(); 1413 1414 // This moml is parsed to execute the change 1415 final StringBuffer moml = new StringBuffer(); 1416 1417 // This moml is parsed in case the change fails. 1418 final StringBuffer failmoml = new StringBuffer(); 1419 moml.append("<group>\n"); 1420 failmoml.append("<group>\n"); 1421 1422 // Make the request in the context of the container. 1423 final CompositeEntity container = (CompositeEntity) getPtolemyModel(); 1424 1425 String relationName = ""; 1426 1427 // Flag specifying whether we have actually created any MoML. 1428 boolean appendedMoML = false; 1429 1430 try { 1431 // create moml to unlink any existing. 1432 appendedMoML = _unlinkMoML(container, moml, linkHead, linkTail, 1433 linkRelation); 1434 1435 // It is possible to link with an existing link. 1436 // If this existing link has a vertex as head or tail, 1437 // we will connect with the vertex, otherwise we will 1438 // remove the old link, create a new vertex, link the 1439 // head and tail of the existing link with the 1440 // vertex and link the new link with the vertex. 1441 if (newLinkHeadOrTail instanceof Link) { 1442 1443 Link oldLink = (Link) newLinkHeadOrTail; 1444 1445 NamedObj oldHead = (NamedObj) oldLink.getHead(); 1446 NamedObj oldTail = (NamedObj) oldLink.getTail(); 1447 1448 if (oldHead instanceof Vertex) { 1449 // Link the new link with oldHead 1450 // create moml to make the new links. 1451 if (isHead) { 1452 relationName = _linkMoML(container, moml, failmoml, 1453 oldHead, linkTail); 1454 } else { 1455 relationName = _linkMoML(container, moml, failmoml, 1456 linkHead, oldHead); 1457 } 1458 } else if (oldTail instanceof Vertex) { 1459 // Link the new link with oldTail 1460 // create moml to make the new links. 1461 if (isHead) { 1462 relationName = _linkMoML(container, moml, failmoml, 1463 oldTail, linkTail); 1464 } else { 1465 relationName = _linkMoML(container, moml, failmoml, 1466 linkHead, oldTail); 1467 } 1468 } else { 1469 // Remove the old link, create a new vertex, link the 1470 // head and tail of the existing link with the 1471 // vertex and link the new link with the vertex. 1472 1473 NamedObj oldHeadSemantic = (NamedObj) getSemanticObject( 1474 oldHead); 1475 NamedObj oldTailSemantic = (NamedObj) getSemanticObject( 1476 oldTail); 1477 1478 // In case the head is a port of an actor in the current composite 1479 // actor the head will be an IOPort, if it is a port of the current 1480 // composite actor it will be a Locatable 1481 boolean headIsActorPort = oldHeadSemantic != null 1482 ? oldLink.getHead() instanceof IOPort 1483 : linkTail instanceof IOPort; 1484 boolean tailIsActorPort = oldLink 1485 .getTail() instanceof IOPort; 1486 1487 final NamedObj toplevel = getPtolemyModel(); 1488 String newRelationName = toplevel 1489 .uniqueName("relation"); 1490 1491 double[] newLocation = _getNewLocation( 1492 oldHeadSemantic != null ? oldHeadSemantic 1493 : (NamedObj) getSemanticObject( 1494 linkTail), 1495 oldTailSemantic, headIsActorPort, 1496 tailIsActorPort); 1497 1498 relationName = newRelationName; 1499 1500 addNewVertexToLink(moml, failmoml, container, oldLink, 1501 newRelationName, newLocation[0], 1502 newLocation[1]); 1503 1504 if (isHead) { 1505 _linkWithRelation(moml, failmoml, container, 1506 (NamedObj) getSemanticObject(linkTail), -1, 1507 newRelationName); 1508 } else { 1509 _linkWithRelation(moml, failmoml, container, 1510 (NamedObj) getSemanticObject(linkHead), -1, 1511 newRelationName); 1512 } 1513 1514 failmoml.append("<deleteRelation name=\"" 1515 + newRelationName + "\"/>\n"); 1516 1517 appendedMoML = true; 1518 } 1519 } else { 1520 // create moml to make the new links. 1521 if (isHead) { 1522 relationName = _linkMoML(container, moml, failmoml, 1523 (NamedObj) newLinkHeadOrTail, linkTail); 1524 } else { 1525 relationName = _linkMoML(container, moml, failmoml, 1526 linkHead, (NamedObj) newLinkHeadOrTail); 1527 } 1528 } 1529 1530 // FIXME: Above can return an empty name, so the following 1531 // test is not quite right. 1532 appendedMoML = appendedMoML || relationName != null; 1533 } catch (Exception ex) { 1534 // The link is bad... remove it. 1535 _linkSet.remove(link); 1536 link.setHead(null); 1537 link.setTail(null); 1538 dispatchGraphEvent(new GraphEvent(ActorGraphModel.this, 1539 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1540 } 1541 1542 moml.append("</group>\n"); 1543 failmoml.append("</group>\n"); 1544 1545 final String relationNameToAdd = relationName; 1546 final boolean nonEmptyMoML = appendedMoML; 1547 1548 // Here the source IS the graph model, because we need to 1549 // handle the event dispatch specially: An event is only 1550 // dispatched if both the head and the tail are attached. 1551 // This rather obnoxious hack is here because edge creation 1552 // is tricky and we can't rerender the edge while we are dragging 1553 // it. 1554 MoMLChangeRequest request = new MoMLChangeRequest( 1555 ActorGraphModel.this, container, moml.toString()) { 1556 @Override 1557 protected void _execute() throws Exception { 1558 // If nonEmptyMoML is false, then the MoML code is empty. 1559 // Do not execute it, as this will put spurious empty 1560 // junk on the undo stack. 1561 if (nonEmptyMoML) { 1562 super._execute(); 1563 } 1564 1565 // It is possible to link with an existing link. 1566 // If this existing link has a vertex as head or tail, 1567 // we will connect with the vertex, otherwise we will 1568 // remove the old link, create a new vertex, link the 1569 // head and tail of the existing link with the 1570 // vertex and link the new link with the vertex. 1571 1572 if (!(newLinkHeadOrTail instanceof Link)) { 1573 if (isHead) { 1574 link.setHead(newLinkHeadOrTail); 1575 } else { 1576 link.setTail(newLinkHeadOrTail); 1577 } 1578 } else { 1579 // Make sure that the model is updated. We made structural 1580 // changes that impose an update of this ActorGraphModel. 1581 _forceUpdate = true; 1582 1583 // Set head/tail equal to newly created vertex. 1584 if (isHead) { 1585 link.setHead(_getLocation( 1586 container.getRelation(relationNameToAdd))); 1587 } else { 1588 Object relation = container 1589 .getRelation(relationNameToAdd); 1590 if (relation == null) { 1591 throw new NullPointerException( 1592 "Getting the relation \"" 1593 + relationNameToAdd + "\" in " 1594 + container.getFullName() 1595 + " returned null?"); 1596 } 1597 } 1598 } 1599 1600 if (relationNameToAdd != null) { 1601 ComponentRelation relation = container 1602 .getRelation(relationNameToAdd); 1603 1604 if (relation == null) { 1605 throw new InternalErrorException( 1606 "Tried to find relation with name " 1607 + relationNameToAdd + " in context " 1608 + container); 1609 } 1610 1611 link.setRelation(relation); 1612 } else { 1613 link.setRelation(null); 1614 } 1615 } 1616 }; 1617 1618 // Handle what happens if the mutation fails. 1619 request.addChangeListener( 1620 new LinkChangeListener(link, container, failmoml)); 1621 1622 request.setUndoable(true); 1623 container.requestChange(request); 1624 } 1625 1626 /** Append moml to the given buffer that disconnects a link with the 1627 * given head, tail, and relation. Names in the returned moml will be 1628 * relative to the given container. If either linkHead or linkTail 1629 * is null, then nothing will be appended to the moml buffer. 1630 * @return True if any MoML is appended to the moml argument. 1631 */ 1632 private boolean _unlinkMoML(NamedObj container, StringBuffer moml, 1633 NamedObj linkHead, NamedObj linkTail, Relation relation) { 1634 // If the link is already connected, then create a bit of MoML 1635 // to unlink the link. 1636 if (linkHead != null && linkTail != null) { 1637 NamedObj head = (NamedObj) getSemanticObject(linkHead); 1638 NamedObj tail = (NamedObj) getSemanticObject(linkTail); 1639 1640 if (head instanceof ComponentPort 1641 && tail instanceof ComponentPort) { 1642 ComponentPort headPort = (ComponentPort) head; 1643 ComponentPort tailPort = (ComponentPort) tail; 1644 1645 // Unlinking two ports with an anonymous relation. 1646 moml.append("<unlink port=\"" + headPort.getName(container) 1647 + "\" relation=\"" + relation.getName(container) 1648 + "\"/>\n"); 1649 moml.append("<unlink port=\"" + tailPort.getName(container) 1650 + "\" relation=\"" + relation.getName(container) 1651 + "\"/>\n"); 1652 moml.append("<deleteRelation name=\"" 1653 + relation.getName(container) + "\"/>\n"); 1654 } else if (head instanceof ComponentPort 1655 && linkTail instanceof Vertex) { 1656 // Unlinking a port from an existing relation. 1657 moml.append("<unlink port=\"" + head.getName(container) 1658 + "\" relation=\"" + tail.getName(container) 1659 + "\"/>\n"); 1660 } else if (tail instanceof ComponentPort 1661 && linkHead instanceof Vertex) { 1662 // Unlinking a port from an existing relation. 1663 moml.append("<unlink port=\"" + tail.getName(container) 1664 + "\" relation=\"" + head.getName(container) 1665 + "\"/>\n"); 1666 } else if (linkHead instanceof Vertex 1667 && linkTail instanceof Vertex) { 1668 moml.append("<unlink relation1=\"" + tail.getName(container) 1669 + "\" relation2=\"" + head.getName(container) 1670 + "\"/>\n"); 1671 } else { 1672 throw new RuntimeException("Unlink failed: " + "Head = " 1673 + head + ", Tail = " + tail); 1674 } 1675 1676 return true; 1677 } else { 1678 // No unlinking to do. 1679 return false; 1680 } 1681 } 1682 1683 /** This change listener is responsible for dispatching graph events 1684 * when an edge is moved. It works the same for heads and tails. 1685 */ 1686 public class LinkChangeListener implements ChangeListener { 1687 /** Construct a link change listener. 1688 * @param link The link. 1689 * @param container The container. 1690 * @param failMoML MoML that cleans up the model if the 1691 * change request fails. 1692 */ 1693 public LinkChangeListener(Link link, CompositeEntity container, 1694 StringBuffer failMoML) { 1695 _link = link; 1696 _container = container; 1697 _failMoML = failMoML; 1698 } 1699 1700 /** Handled a failed change request. 1701 * @param change The change request. 1702 * @param exception The exception. 1703 */ 1704 @Override 1705 public void changeFailed(ChangeRequest change, 1706 Exception exception) { 1707 // If we fail here, then we remove the link entirely. 1708 _linkSet.remove(_link); 1709 _link.setHead(null); 1710 _link.setTail(null); 1711 _link.setRelation(null); 1712 1713 // and queue a new change request to clean up the model 1714 // Note: JDK1.2.2 requires that this variable not be 1715 // called request or we get a compile error. 1716 // Note the source is NOT the graph model 1717 MoMLChangeRequest changeRequest = new MoMLChangeRequest(this, 1718 _container, _failMoML.toString()); 1719 1720 // fail moml not undoable 1721 _container.requestChange(changeRequest); 1722 } 1723 1724 /** Called after the change has been executed. 1725 * @param change The change request. 1726 */ 1727 @Override 1728 public void changeExecuted(ChangeRequest change) { 1729 // modification to the linkset HAS to occur in the swing 1730 // thread. 1731 if (GraphUtilities.isPartiallyContainedEdge(_link, getRoot(), 1732 ActorGraphModel.this)) { 1733 _linkSet.add(_link); 1734 } else { 1735 _linkSet.remove(_link); 1736 } 1737 1738 // Note that there is no GraphEvent dispatched here 1739 // if the edge is not fully connected. This is to 1740 // prevent rerendering while 1741 // an edge is being created. 1742 if (_link.getHead() != null && _link.getTail() != null) { 1743 dispatchGraphEvent(new GraphEvent(ActorGraphModel.this, 1744 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1745 } 1746 } 1747 1748 private Link _link; 1749 1750 private CompositeEntity _container; 1751 1752 private StringBuffer _failMoML; 1753 } 1754 } 1755 1756 /** The model for ports that are contained in icons in this graph. 1757 */ 1758 public class PortModel extends NamedObjNodeModel { 1759 /** Return a MoML String that will delete the given node from the 1760 * Ptolemy model. This assumes that the context is the container 1761 * of the port. 1762 * @param node The node. 1763 * @return A valid MoML string. 1764 */ 1765 @Override 1766 public String getDeleteNodeMoML(Object node) { 1767 NamedObj deleteObj = ((Locatable) node).getContainer(); 1768 NamedObj container = deleteObj.getContainer(); 1769 ; 1770 1771 String moml = "<deletePort name=\"" + deleteObj.getName(container) 1772 + "\"/>\n"; 1773 return moml; 1774 } 1775 1776 /** Return the graph parent of the given node. 1777 * @param node The node, which is assumed to be a port. 1778 * @return The (presumably unique) icon contained in the port's 1779 * container. 1780 */ 1781 @Override 1782 public Object getParent(Object node) { 1783 ComponentPort port = (ComponentPort) node; 1784 Entity entity = (Entity) port.getContainer(); 1785 1786 if (entity == null) { 1787 return null; 1788 } 1789 1790 List<?> locationList = entity.attributeList(Locatable.class); 1791 1792 if (locationList.size() > 0) { 1793 return locationList.get(0); 1794 } else { 1795 try { 1796 // NOTE: We need the location right away, so we go ahead 1797 // and create it and handle the propagation locally. 1798 Location location = new Location(entity, "_location"); 1799 location.propagateExistence(); 1800 return location; 1801 } catch (Exception e) { 1802 throw new InternalErrorException("Failed to create " 1803 + "location, even though one does not exist:" 1804 + e.getMessage()); 1805 } 1806 } 1807 } 1808 1809 /** Return an iterator over the edges coming into the given node. 1810 * This method first ensures that there is a link 1811 * object for every link. Then the iterator is constructed by 1812 * removing any links that do not have the given node as head. 1813 * @param node The node, which is assumed to be a port contained in 1814 * the root of this graph model. 1815 * @return An iterator of Link objects, all of which have their 1816 * head as the given node. 1817 */ 1818 @Override 1819 public Iterator inEdges(Object node) { 1820 ComponentPort port = (ComponentPort) node; 1821 1822 // Go through all the links, creating a list of 1823 // those we are connected to. 1824 List<Link> portLinkList = new LinkedList<Link>(); 1825 1826 for (Link link : _linkSet) { 1827 Object head = link.getHead(); 1828 1829 if (head != null && head.equals(port)) { 1830 portLinkList.add(link); 1831 } 1832 } 1833 1834 return portLinkList.iterator(); 1835 } 1836 1837 /** Return an iterator over the edges coming out of the given node. 1838 * This iterator is constructed by looping over all the relations 1839 * that the port is connected to, and ensuring that there is a link 1840 * object for every link. Then the iterator is constructed by 1841 * removing any links that do not have the given node as tail. 1842 * @param node The node, which is assumed to be a port contained in 1843 * the root of this graph model. 1844 * @return An iterator of Link objects, all of which have their 1845 * tail as the given node. 1846 */ 1847 @Override 1848 public Iterator outEdges(Object node) { 1849 ComponentPort port = (ComponentPort) node; 1850 1851 // Go through all the links, creating a list of 1852 // those we are connected to. 1853 List<Link> portLinkList = new LinkedList<Link>(); 1854 for (Link link : _linkSet) { 1855 Object tail = link.getTail(); 1856 1857 if (tail != null && tail.equals(port)) { 1858 portLinkList.add(link); 1859 } 1860 } 1861 1862 return portLinkList.iterator(); 1863 } 1864 1865 /** Remove the given node from the model. The node is assumed 1866 * to be a port. 1867 * This class queues a new change request with the ptolemy model 1868 * to make this modification. 1869 * @param eventSource The source of the event that will be dispatched, 1870 * e.g. the view that made this call. 1871 * @param node The node. 1872 */ 1873 @Override 1874 public void removeNode(final Object eventSource, Object node) { 1875 ComponentPort port = (ComponentPort) node; 1876 NamedObj container = port.getContainer(); 1877 1878 // Delete the port. 1879 String moml = "<deletePort name=\"" + port.getName() + "\"/>\n"; 1880 1881 // Note: The source is NOT the graph model. 1882 MoMLChangeRequest request = new MoMLChangeRequest(this, container, 1883 moml); 1884 request.setUndoable(true); 1885 container.requestChange(request); 1886 } 1887 } 1888 1889 /** The model for vertexes that are contained within the relations of the 1890 * ptolemy model. 1891 */ 1892 public class VertexModel extends NamedObjNodeModel { 1893 /** Return a MoML String that will delete the given node from the 1894 * Ptolemy model. This assumes that the context is the container 1895 * of the vertex. 1896 * @param node The node. 1897 * @return A valid MoML string. 1898 */ 1899 @Override 1900 public String getDeleteNodeMoML(Object node) { 1901 ComponentRelation deleteObj = (ComponentRelation) ((Vertex) node) 1902 .getContainer(); 1903 String moml = "<deleteRelation name=\"" + deleteObj.getName() 1904 + "\"/>\n"; 1905 return moml; 1906 } 1907 1908 /** Return the graph parent of the given node. 1909 * @param node The node, which is assumed to be a Vertex. 1910 * @return The container of the vertex's container, which is 1911 * presumably the root of the graph model. 1912 */ 1913 @Override 1914 public Object getParent(Object node) { 1915 // Undo: If we use automatic layout, then we need to check to 1916 // see if the container is null here. 1917 if (((Vertex) node).getContainer() == null) { 1918 return null; 1919 } 1920 1921 return ((Vertex) node).getContainer().getContainer(); 1922 } 1923 1924 /** Return an iterator over the edges coming into the given node. 1925 * This method ensures that there is a link object for 1926 * every link to the relation contained by the vertex. 1927 * Then the iterator is constructed by 1928 * removing any links that do not have the given node as head. 1929 * @param node The node, which is assumed to be a vertex contained in 1930 * a relation. 1931 * @return An iterator of Link objects, all of which have their 1932 * head as the given node. 1933 */ 1934 @Override 1935 public Iterator inEdges(Object node) { 1936 Vertex vertex = (Vertex) node; 1937 1938 // Go through all the links, creating a list of 1939 // those we are connected to. 1940 List<Link> vertexLinkList = new LinkedList<Link>(); 1941 1942 for (Link link : _linkSet) { 1943 Object head = link.getHead(); 1944 1945 if (head != null && head.equals(vertex)) { 1946 vertexLinkList.add(link); 1947 } 1948 } 1949 1950 return vertexLinkList.iterator(); 1951 } 1952 1953 /** Return an iterator over the edges coming into the given node. 1954 * This method ensures that there is a link object for 1955 * every link to the relation contained by the vertex. 1956 * Then the iterator is constructed by 1957 * removing any links that do not have the given node as head. 1958 * @param node The node, which is assumed to be a vertex contained in 1959 * a relation. 1960 * @return An iterator of Link objects, all of which have their 1961 * tail as the given node. 1962 */ 1963 @Override 1964 public Iterator outEdges(Object node) { 1965 Vertex vertex = (Vertex) node; 1966 1967 // Go through all the links, creating a list of 1968 // those we are connected to. 1969 List<Link> vertexLinkList = new LinkedList<Link>(); 1970 1971 for (Link link : _linkSet) { 1972 Object tail = link.getTail(); 1973 1974 if (tail != null && tail.equals(vertex)) { 1975 vertexLinkList.add(link); 1976 } 1977 } 1978 1979 return vertexLinkList.iterator(); 1980 } 1981 1982 /** Remove the given node from the model. The node is assumed 1983 * to be a vertex contained by a relation. 1984 * This class queues a new change request with the ptolemy model 1985 * to make this modification. 1986 * @param eventSource The source of the event that will be dispatched, 1987 * e.g. the view that made this call. 1988 * @param node The node. 1989 */ 1990 @Override 1991 public void removeNode(final Object eventSource, Object node) { 1992 ComponentRelation relation = (ComponentRelation) ((Vertex) node) 1993 .getContainer(); 1994 NamedObj container = relation.getContainer(); 1995 1996 // Delete the relation. 1997 String moml = "<deleteRelation name=\"" + relation.getName() 1998 + "\"/>\n"; 1999 2000 // Note: The source is NOT the graph mode. 2001 MoMLChangeRequest request = new MoMLChangeRequest(this, container, 2002 moml); 2003 request.setUndoable(true); 2004 container.requestChange(request); 2005 } 2006 } 2007}