001/* A graph model for ptolemy fsm 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.modal; 029 030import java.util.HashSet; 031import java.util.Iterator; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Set; 035 036import diva.graph.GraphEvent; 037import diva.graph.GraphUtilities; 038import diva.graph.modular.EdgeModel; 039import diva.graph.modular.MutableEdgeModel; 040import diva.graph.modular.NodeModel; 041import diva.util.NullIterator; 042import ptolemy.actor.TypedActor; 043import ptolemy.domains.modal.kernel.State; 044import ptolemy.domains.modal.kernel.Transition; 045import ptolemy.kernel.ComponentEntity; 046import ptolemy.kernel.ComponentPort; 047import ptolemy.kernel.ComponentRelation; 048import ptolemy.kernel.CompositeEntity; 049import ptolemy.kernel.Port; 050import ptolemy.kernel.Relation; 051import ptolemy.kernel.util.ChangeListener; 052import ptolemy.kernel.util.ChangeRequest; 053import ptolemy.kernel.util.Flowable; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.InternalErrorException; 056import ptolemy.kernel.util.Locatable; 057import ptolemy.kernel.util.NamedObj; 058import ptolemy.moml.MoMLChangeRequest; 059import ptolemy.vergil.basic.AbstractBasicGraphModel; 060import ptolemy.vergil.basic.NamedObjNodeModel; 061import ptolemy.vergil.kernel.Link; 062 063/////////////////////////////////////////////////////////////////// 064//// FSMGraphModel 065 066/** 067 A graph model for graphically manipulating ptolemy FSM models. 068 069 @author Steve Neuendorffer 070 @version $Id$ 071 @since Ptolemy II 8.0 072 @Pt.ProposedRating Yellow (neuendor) 073 @Pt.AcceptedRating Red (johnr) 074 */ 075public class FSMGraphModel extends AbstractBasicGraphModel { 076 /** Construct a new graph model whose root is the given composite entity. 077 * @param composite The top-level composite entity for the model. 078 */ 079 public FSMGraphModel(CompositeEntity composite) { 080 super(composite); 081 _linkSet = new HashSet(); 082 _update(); 083 } 084 085 /////////////////////////////////////////////////////////////////// 086 //// public methods //// 087 088 /** Disconnect an edge from its two endpoints and notify graph 089 * listeners with an EDGE_HEAD_CHANGED and an EDGE_TAIL_CHANGED 090 * event whose source is the given source. 091 * @param eventSource The source of the event that will be dispatched, 092 * e.g. the view that made this call. 093 * @param edge The edge. 094 */ 095 @Override 096 public void disconnectEdge(Object eventSource, Object edge) { 097 if (!(getEdgeModel(edge) instanceof ArcModel)) { 098 return; 099 } 100 101 ArcModel model = (ArcModel) getEdgeModel(edge); 102 Object head = model.getHead(edge); 103 Object tail = model.getTail(edge); 104 model.removeEdge(edge); 105 106 if (head != null) { 107 GraphEvent e = new GraphEvent(eventSource, 108 GraphEvent.EDGE_HEAD_CHANGED, edge, head); 109 dispatchGraphEvent(e); 110 } 111 112 if (tail != null) { 113 GraphEvent e = new GraphEvent(eventSource, 114 GraphEvent.EDGE_TAIL_CHANGED, edge, tail); 115 dispatchGraphEvent(e); 116 } 117 } 118 119 /** Return a MoML String that will delete the given edge from the 120 * Ptolemy model. 121 * @param edge The edge. 122 * @return A valid MoML string. 123 */ 124 @Override 125 public String getDeleteEdgeMoML(Object edge) { 126 // Note: the abstraction here is rather broken. Ideally this 127 // should look like getDeleteNodeMoML() 128 if (!(getEdgeModel(edge) instanceof ArcModel)) { 129 return ""; 130 } 131 132 ArcModel model = (ArcModel) getEdgeModel(edge); 133 return model.getDeleteEdgeMoML(edge); 134 } 135 136 /** Return a MoML String that will delete the given node from the 137 * Ptolemy model. 138 * @param node The node. 139 * @return A valid MoML string. 140 */ 141 @Override 142 public String getDeleteNodeMoML(Object node) { 143 if (!(getNodeModel(node) instanceof NamedObjNodeModel)) { 144 return ""; 145 } 146 147 NamedObjNodeModel model = (NamedObjNodeModel) getNodeModel(node); 148 return model.getDeleteNodeMoML(node); 149 } 150 151 /** Return the model for the given edge object. If the object is not 152 * an edge, then return null. 153 * @param edge An object which is assumed to be in this graph model. 154 * @return An instance of ArcModel if the object is an Arc. 155 * Otherwise return null. 156 */ 157 @Override 158 public EdgeModel getEdgeModel(Object edge) { 159 if (edge instanceof Link) { 160 return _arcModel; 161 } else { 162 return null; 163 } 164 } 165 166 /** Return the node model for the given object. If the object is not 167 * a node, then return null. 168 * @param node An object which is assumed to be in this graph model. 169 * @return The node model for the specified node, or null if there 170 * is none. 171 */ 172 @Override 173 public NodeModel getNodeModel(Object node) { 174 if (node instanceof Locatable) { 175 Object container = ((Locatable) node).getContainer(); 176 177 if (container instanceof ComponentEntity) { 178 return _stateModel; 179 } else if (container instanceof ComponentPort) { 180 return _portModel; 181 } 182 } 183 184 return super.getNodeModel(node); 185 } 186 187 /** Return the semantic object corresponding to the given node, edge, 188 * or composite. A "semantic object" is an object associated with 189 * a node in the graph. In this case, if the node is icon, the 190 * semantic object is the entity containing the icon. If it is 191 * an arc, then the semantic object is the arc's relation. 192 * @param element A graph element. 193 * @return The semantic object associated with this element, or null 194 * if the object is not recognized. 195 */ 196 @Override 197 public Object getSemanticObject(Object element) { 198 if (element instanceof Link) { 199 return ((Link) element).getRelation(); 200 } 201 202 return super.getSemanticObject(element); 203 } 204 205 /** Delete a node from its parent graph and notify 206 * graph listeners with a NODE_REMOVED event. 207 * @param eventSource The source of the event that will be dispatched, 208 * e.g. the view that made this call. 209 * @param node The node to be removed. 210 */ 211 @Override 212 public void removeNode(Object eventSource, Object node) { 213 if (!(getNodeModel(node) instanceof NamedObjNodeModel)) { 214 return; 215 } 216 217 NamedObjNodeModel model = (NamedObjNodeModel) getNodeModel(node); 218 model.removeNode(eventSource, node); 219 } 220 221 // FIXME: The following methods are probably inappropriate. 222 // They make it impossible to have customized models for 223 // particular links or icons. getLinkModel() and 224 // getNodeModel() should be sufficient. 225 // Big changes needed, however to make this work. 226 // The huge inner classes below should be factored out as 227 // separate classes. EAL 228 229 /** Get the port model. 230 * @return The port model. 231 */ 232 public PortModel getPortModel() { 233 return _portModel; 234 } 235 236 /** Get the state model. 237 * @return The state model. 238 */ 239 public StateModel getStateModel() { 240 return _stateModel; 241 } 242 243 /** Get the arc model. 244 * @return The acr model. 245 */ 246 public ArcModel getArcModel() { 247 return _arcModel; 248 } 249 250 // End of FIXME. 251 /////////////////////////////////////////////////////////////////// 252 //// protected methods //// 253 254 /** Update the graph model. This is called whenever a change request is 255 * executed. In this class the internal set of link objects is 256 * verified to be correct. 257 */ 258 @Override 259 protected boolean _update() { 260 // Go through all the links that currently exist, and remove 261 // any that don't have both ends in the model. 262 Iterator links = _linkSet.iterator(); 263 264 while (links.hasNext()) { 265 Link link = (Link) links.next(); 266 Relation relation = link.getRelation(); 267 268 if (relation == null) { 269 continue; 270 } 271 272 // Check that the relation hasn't been removed. 273 // If we do not do this, then redo will thrown an exception 274 // if we open ct/demo/BouncingBall/BouncingBall.xml, look 275 // inside the Ball Model composite actor, 276 // delete the stop actor and then do undo and then redo. 277 if (relation.getContainer() == null) { 278 link.setHead(null); 279 link.setTail(null); 280 links.remove(); 281 continue; 282 } 283 284 boolean headOK = GraphUtilities.isContainedNode(link.getHead(), 285 getRoot(), this); 286 boolean tailOK = GraphUtilities.isContainedNode(link.getTail(), 287 getRoot(), this); 288 289 // If the head or tail has been removed, then remove this link. 290 if (!(headOK && tailOK)) { 291 //Object headObj = getSemanticObject(link.getHead()); 292 //Object tailObj = getSemanticObject(link.getTail()); 293 link.setHead(null); 294 link.setTail(null); 295 link.setRelation(null); 296 links.remove(); 297 298 NamedObj container = getPtolemyModel(); 299 300 // remove the relation This should trigger removing the 301 // other link. This will only happen when we've deleted 302 // the state at one end of the model. 303 // Note that the source is NOT the graph model, so this 304 // will trigger the ChangeRequest listener to 305 // redraw the graph again. 306 MoMLChangeRequest request = new MoMLChangeRequest(container, 307 container, "<deleteRelation name=\"" 308 + relation.getName(container) + "\"/>\n"); 309 310 // Need to merge the undo for this request in with one that 311 // triggered it 312 request.setMergeWithPreviousUndo(true); 313 request.setUndoable(true); 314 container.requestChange(request); 315 return false; 316 } 317 } 318 319 // Now create Links for links that may be new 320 Iterator relations = ((CompositeEntity) getPtolemyModel()) 321 .relationList().iterator(); 322 323 while (relations.hasNext()) { 324 _updateLinks((ComponentRelation) relations.next()); 325 } 326 327 return super._update(); 328 } 329 330 /////////////////////////////////////////////////////////////////// 331 //// private methods //// 332 // Check to make sure that there is an Link object representing 333 // the given relation. 334 private void _updateLinks(ComponentRelation relation) { 335 Iterator links = _linkSet.iterator(); 336 Link foundLink = null; 337 338 while (links.hasNext()) { 339 Link link = (Link) links.next(); 340 341 // only consider links that are associated with this relation. 342 if (link.getRelation() == relation) { 343 foundLink = link; 344 break; 345 } 346 } 347 348 // A link exists, so there is nothing to do. 349 if (foundLink != null) { 350 return; 351 } 352 353 List linkedPortList = relation.linkedPortList(); 354 355 if (linkedPortList.size() != 2) { 356 // Do nothing... somebody else should take care of removing this, 357 // because we have no way of representing it in this editor. 358 return; 359 } 360 361 Port port1 = (Port) linkedPortList.get(0); 362 Locatable location1 = _getLocation(port1.getContainer()); 363 Port port2 = (Port) linkedPortList.get(1); 364 Locatable location2 = _getLocation(port2.getContainer()); 365 366 Link link; 367 368 try { 369 link = new Link(); 370 } catch (Exception e) { 371 throw new InternalErrorException( 372 "Failed to create " + "new link, even though one does not " 373 + "already exist:" + e.getMessage()); 374 } 375 376 link.setRelation(relation); 377 378 // We have to get the direction of the arc correct. 379 if (((Flowable) port1.getContainer()).getIncomingPort().equals(port1)) { 380 link.setHead(location1); 381 link.setTail(location2); 382 } else { 383 link.setHead(location2); 384 link.setTail(location1); 385 } 386 387 _linkSet.add(link); 388 } 389 390 /////////////////////////////////////////////////////////////////// 391 //// private variables //// 392 // The set of all links in the model. 393 private Set _linkSet; 394 395 // The models of the different types of nodes and edges. 396 private ArcModel _arcModel = new ArcModel(); 397 398 private PortModel _portModel = new PortModel(); 399 400 private StateModel _stateModel = new StateModel(); 401 402 /////////////////////////////////////////////////////////////////// 403 //// inner classes //// 404 405 /** The model for arcs between states. 406 */ 407 public class ArcModel implements MutableEdgeModel { 408 /** Return true if the head of the given edge can be attached to the 409 * given node. 410 * @param edge The edge to attach, which is assumed to be an arc. 411 * @param node The node to attach to. 412 * @return True if the node is an icon. 413 */ 414 @Override 415 public boolean acceptHead(Object edge, Object node) { 416 if (node instanceof Locatable) { 417 return true; 418 } else { 419 return false; 420 } 421 } 422 423 /** Return true if the tail of the given edge can be attached to the 424 * given node. 425 * @param edge The edge to attach, which is assumed to be an arc. 426 * @param node The node to attach to. 427 * @return True if the node is an icon. 428 */ 429 @Override 430 public boolean acceptTail(Object edge, Object node) { 431 if (node instanceof Locatable) { 432 return true; 433 } else { 434 return false; 435 } 436 } 437 438 /** Return the head node of the given edge. 439 * @param edge The edge, which is assumed to be an instance of Arc. 440 * @return The node that is the head of the specified edge. 441 * @see #getTail(Object) 442 * @see #setHead(Object, Object) 443 */ 444 @Override 445 public Object getHead(Object edge) { 446 return ((Link) edge).getHead(); 447 } 448 449 /** Return a MoML String that will delete the given edge from the 450 * Ptolemy model. 451 * @param edge The edge to be removed. 452 * @return A valid MoML string. 453 */ 454 public String getDeleteEdgeMoML(Object edge) { 455 final Link link = (Link) edge; 456 //NamedObj linkHead = (NamedObj) link.getHead(); 457 //NamedObj linkTail = (NamedObj) link.getTail(); 458 Relation linkRelation = link.getRelation(); 459 460 // This moml is parsed to execute the change 461 StringBuffer moml = new StringBuffer(); 462 463 // Make the request in the context of the container. 464 // JDK1.2.2 fails to compile the next line. 465 NamedObj container = getPtolemyModel(); 466 467 moml.append(_deleteRelation(container, linkRelation)); 468 469 // See whether refinement(s) need to be removed. 470 CompositeEntity master = (CompositeEntity) linkRelation 471 .getContainer(); 472 473 // Nothing to do if there is no container. 474 if (master != null) { 475 // Remove any referenced refinements that are not also 476 // referenced by other states. 477 TypedActor[] refinements = null; 478 479 try { 480 if (linkRelation instanceof Transition) { 481 refinements = ((Transition) linkRelation) 482 .getRefinement(); 483 } 484 } catch (IllegalActionException e) { 485 // Ignore, no refinement to remove. 486 } 487 488 // The following code executes only if the link is a Transition, 489 // so we can sure the entities are instances of State. 490 if (refinements != null) { 491 for (TypedActor refinement : refinements) { 492 // By default, if no other state or transition refers 493 // to this refinement, then we will remove it. 494 boolean removeIt = true; 495 Iterator states = master.entityList(State.class) 496 .iterator(); 497 498 while (removeIt && states.hasNext()) { 499 State state = (State) states.next(); 500 TypedActor[] stateRefinements = null; 501 502 try { 503 stateRefinements = state.getRefinement(); 504 } catch (IllegalActionException e1) { 505 // Ignore, no refinement to check. 506 } 507 508 if (stateRefinements == null) { 509 continue; 510 } 511 512 for (TypedActor stateRefinement : stateRefinements) { 513 if (stateRefinement == refinement) { 514 removeIt = false; 515 break; 516 } 517 } 518 } 519 520 // Next check transitions. 521 Iterator transitions = master.relationList().iterator(); 522 523 while (removeIt && transitions.hasNext()) { 524 Relation transition = (Relation) transitions.next(); 525 526 if (transition == linkRelation 527 || !(transition instanceof Transition)) { 528 continue; 529 } 530 531 TypedActor[] transitionRefinements = null; 532 533 try { 534 transitionRefinements = ((Transition) transition) 535 .getRefinement(); 536 } catch (IllegalActionException e1) { 537 // Ignore, no refinement to check. 538 } 539 540 if (transitionRefinements == null) { 541 continue; 542 } 543 544 for (TypedActor transitionRefinement : transitionRefinements) { 545 if (transitionRefinement == refinement) { 546 removeIt = false; 547 break; 548 } 549 } 550 } 551 552 if (removeIt) { 553 moml.append("<deleteEntity name=\"" 554 + ((NamedObj) refinement).getName(container) 555 + "\"/>\n"); 556 } 557 } 558 } 559 } 560 561 return moml.toString(); 562 } 563 564 /** Return the tail node of the specified edge. 565 * @param edge The edge, which is assumed to be an instance of Link. 566 * @return The node that is the tail of the specified edge. 567 * @see #getHead(Object) 568 * @see #setTail(Object, Object) 569 */ 570 @Override 571 public Object getTail(Object edge) { 572 return ((Link) edge).getTail(); 573 } 574 575 /** Return true if this edge is directed. 576 * All transitions are directed, so this always returns true. 577 * @param edge The edge, which is assumed to be an arc. 578 * @return True. 579 */ 580 @Override 581 public boolean isDirected(Object edge) { 582 return true; 583 } 584 585 /** Remove the given edge and delete its associated relation. 586 * This class queues a new change request with the ptolemy model 587 * to make this modification. 588 * @param edge The edge, which is assumed to be an arc. 589 */ 590 public void removeEdge(final Object edge) { 591 final Link link = (Link) edge; 592 Relation linkRelation = link.getRelation(); 593 594 // This moml is parsed to execute the change 595 final StringBuffer moml = new StringBuffer(); 596 597 // Make the request in the context of the container. 598 final NamedObj container = getPtolemyModel(); 599 moml.append(_deleteRelation(container, linkRelation)); 600 601 MoMLChangeRequest request = new MoMLChangeRequest( 602 FSMGraphModel.this, container, moml.toString()) { 603 @Override 604 protected void _execute() throws Exception { 605 super._execute(); 606 link.setHead(null); 607 link.setTail(null); 608 link.setRelation(null); 609 } 610 }; 611 612 // Handle what happens if the mutation fails. 613 request.addChangeListener(new ChangeListener() { 614 @Override 615 public void changeFailed(ChangeRequest change, 616 Exception exception) { 617 // Ignore... nothing we can do about it anyway. 618 } 619 620 @Override 621 public void changeExecuted(ChangeRequest change) { 622 _linkSet.remove(edge); 623 } 624 }); 625 request.setUndoable(true); 626 container.requestChange(request); 627 } 628 629 /** Connect the given edge to the given head node. If the specified 630 * head is null, then any pre-existing relation associated with 631 * this edge will be deleted. 632 * This class queues a new change request with the ptolemy model 633 * to make this modification. 634 * @param edge The edge, which is assumed to be an arc. 635 * @param newLinkHead The new head for the edge, which is assumed to 636 * be an icon. 637 * @see #setTail(Object, Object) 638 * @see #getHead(Object) 639 */ 640 @Override 641 public void setHead(final Object edge, final Object newLinkHead) { 642 final Link link = (Link) edge; 643 NamedObj linkHead = (NamedObj) link.getHead(); 644 NamedObj linkTail = (NamedObj) link.getTail(); 645 Relation linkRelation = link.getRelation(); 646 647 // This moml is parsed to execute the change 648 final StringBuffer moml = new StringBuffer(); 649 650 // This moml is parsed in case the change fails. 651 final StringBuffer failmoml = new StringBuffer(); 652 moml.append("<group>\n"); 653 failmoml.append("<group>\n"); 654 655 // Make the request in the context of the container. 656 final NamedObj container = getPtolemyModel(); 657 658 // If there is a previous connection, remove it. 659 if (linkRelation != null) { 660 if (newLinkHead == null) { 661 // There will be no further connection, so just 662 // delete the relation. 663 moml.append(_deleteRelation(container, linkRelation)); 664 } else { 665 // There will be a further connection, so preserve 666 // the relation. 667 moml.append(_unlinkHead(container, linkHead, linkRelation)); 668 } 669 } 670 671 String relationName = null; 672 673 if (newLinkHead != null) { 674 // create moml to make the new link. 675 relationName = _linkHead(container, moml, failmoml, 676 (NamedObj) newLinkHead, linkTail, linkRelation); 677 } 678 679 moml.append("</group>\n"); 680 failmoml.append("</group>\n"); 681 682 final String relationNameToAdd = relationName; 683 MoMLChangeRequest request = new MoMLChangeRequest( 684 FSMGraphModel.this, container, moml.toString()) { 685 @Override 686 protected void _execute() throws Exception { 687 super._execute(); 688 link.setHead(newLinkHead); 689 690 if (relationNameToAdd != null) { 691 ComponentRelation relation = ((CompositeEntity) getPtolemyModel()) 692 .getRelation(relationNameToAdd); 693 link.setRelation(relation); 694 } 695 } 696 }; 697 698 // Handle what happens if the mutation fails. 699 request.addChangeListener(new ChangeListener() { 700 @Override 701 public void changeFailed(ChangeRequest change, 702 Exception exception) { 703 // If we fail here, then we remove the link entirely. 704 _linkSet.remove(link); 705 link.setHead(null); 706 link.setTail(null); 707 link.setRelation(null); 708 709 // and queue a new change request to clean up the model 710 // Note: JDK1.2.2 requires that this variable not be 711 // called request or we get a compile error. 712 MoMLChangeRequest requestChange = new MoMLChangeRequest( 713 FSMGraphModel.this, container, failmoml.toString()); 714 715 // Fail moml execution not undoable 716 container.requestChange(requestChange); 717 } 718 719 @Override 720 public void changeExecuted(ChangeRequest change) { 721 if (GraphUtilities.isPartiallyContainedEdge(edge, getRoot(), 722 FSMGraphModel.this)) { 723 _linkSet.add(edge); 724 } else { 725 _linkSet.remove(edge); 726 } 727 } 728 }); 729 request.setUndoable(true); 730 container.requestChange(request); 731 } 732 733 /** Connect the given edge to the given tail node. If the specified 734 * tail is null, then any pre-existing relation associated with 735 * this edge will be deleted. 736 * This class queues a new change request with the ptolemy model 737 * to make this modification. 738 * @param edge The edge, which is assumed to be an arc. 739 * @param newLinkTail The new tail for the edge, which is assumed to 740 * be an icon. 741 * @see #setHead(Object, Object) 742 * @see #getTail(Object) 743 */ 744 @Override 745 public void setTail(final Object edge, final Object newLinkTail) { 746 final Link link = (Link) edge; 747 NamedObj linkHead = (NamedObj) link.getHead(); 748 NamedObj linkTail = (NamedObj) link.getTail(); 749 Relation linkRelation = link.getRelation(); 750 751 // This moml is parsed to execute the change 752 final StringBuffer moml = new StringBuffer(); 753 754 // This moml is parsed in case the change fails. 755 final StringBuffer failmoml = new StringBuffer(); 756 moml.append("<group>\n"); 757 failmoml.append("<group>\n"); 758 759 // Make the request in the context of the container. 760 final NamedObj container = getPtolemyModel(); 761 762 // If there is a previous connection, remove it. 763 if (linkRelation != null) { 764 if (newLinkTail == null) { 765 // There will be no further connection, so just 766 // delete the relation. 767 moml.append(_deleteRelation(container, linkRelation)); 768 } else { 769 // There will be a further connection, so preserve 770 // the relation. 771 moml.append(_unlinkTail(container, linkTail, linkRelation)); 772 } 773 } 774 775 String relationName = null; 776 777 if (newLinkTail != null) { 778 // create moml to make the new links. 779 relationName = _linkTail(container, moml, failmoml, linkHead, 780 (NamedObj) newLinkTail, linkRelation); 781 } 782 783 moml.append("</group>\n"); 784 failmoml.append("</group>\n"); 785 786 final String relationNameToAdd = relationName; 787 788 MoMLChangeRequest request = new MoMLChangeRequest( 789 FSMGraphModel.this, container, moml.toString()) { 790 @Override 791 protected void _execute() throws Exception { 792 super._execute(); 793 link.setTail(newLinkTail); 794 795 if (relationNameToAdd != null) { 796 link.setRelation(((CompositeEntity) getPtolemyModel()) 797 .getRelation(relationNameToAdd)); 798 } 799 } 800 }; 801 802 // Handle what happens if the mutation fails. 803 request.addChangeListener(new ChangeListener() { 804 @Override 805 public void changeFailed(ChangeRequest change, 806 Exception exception) { 807 // If we fail here, then we remove the link entirely. 808 _linkSet.remove(link); 809 link.setHead(null); 810 link.setTail(null); 811 link.setRelation(null); 812 813 // and queue a new change request to clean up the model 814 // Note: JDK1.2.2 requires that this variable not be 815 // called request or we get a compile error. 816 MoMLChangeRequest requestChange = new MoMLChangeRequest( 817 FSMGraphModel.this, container, failmoml.toString()); 818 819 // fail moml execution not undaoble 820 container.requestChange(requestChange); 821 } 822 823 @Override 824 public void changeExecuted(ChangeRequest change) { 825 if (GraphUtilities.isPartiallyContainedEdge(edge, getRoot(), 826 FSMGraphModel.this)) { 827 _linkSet.add(edge); 828 } else { 829 _linkSet.remove(edge); 830 } 831 } 832 }); 833 request.setUndoable(true); 834 container.requestChange(request); 835 } 836 837 /////////////////////////////////////////////////////////////////// 838 //// private methods //// 839 840 /** Return moml to remove a relation in the specified container. 841 */ 842 private String _deleteRelation(NamedObj container, Relation relation) { 843 return "<deleteRelation name=\"" + relation.getName(container) 844 + "\"/>\n"; 845 } 846 847 /** Append moml to the given buffer that connects a link with the 848 * given head. If the relation argument is non-null, then that 849 * relation is used, and null is returned. Otherwise, a new 850 * relation is created, and its name is returned. Names in the 851 * generated moml will be relative to the specified container. 852 * The failmoml argument is also populated, but with MoML code 853 * to execute if the link fails. This disconnects any partially 854 * constructed link. 855 */ 856 private String _linkHead(NamedObj container, StringBuffer moml, 857 StringBuffer failmoml, NamedObj linkHead, NamedObj linkTail, 858 Relation linkRelation) { 859 if (linkHead != null && linkTail != null) { 860 NamedObj head = (NamedObj) getSemanticObject(linkHead); 861 NamedObj tail = (NamedObj) getSemanticObject(linkTail); 862 863 if (head instanceof Flowable && tail instanceof Flowable) { 864 // When we connect two states, we actually connect the 865 // appropriate ports. 866 Flowable headState = (Flowable) head; 867 Flowable tailState = (Flowable) tail; 868 ComponentPort headPort = headState.getIncomingPort(); 869 ComponentPort tailPort = tailState.getOutgoingPort(); 870 NamedObj ptolemyModel = getPtolemyModel(); 871 872 // If the context is not the entity that we're editing, 873 // then we need to set the context correctly. 874 if (ptolemyModel != container) { 875 String contextString = "<entity name=\"" 876 + ptolemyModel.getName(container) + "\">\n"; 877 moml.append(contextString); 878 failmoml.append(contextString); 879 } 880 881 boolean createdNewRelation = false; 882 String relationName = null; 883 884 if (linkRelation != null) { 885 // Pre-existing relation. Use it. 886 relationName = linkRelation.getName(ptolemyModel); 887 } else { 888 createdNewRelation = true; 889 890 // Linking two ports with a new relation. 891 relationName = ptolemyModel.uniqueName("relation"); 892 893 // Set the exitAngle based on other relations. 894 // Count the number of connections between the 895 // headPort and the tailPort. 896 Iterator ports = tailPort.deepConnectedPortList() 897 .iterator(); 898 int count = 0; 899 900 while (ports.hasNext()) { 901 if (ports.next() == headPort) { 902 count++; 903 } 904 } 905 906 // Increase the angle as the count increases. 907 // Any function of "count" will work here that starts 908 // at PI/5 and approaches something less than PI 909 // as count gets large. Unfortunately, self-loops 910 // again have to be handled specially. 911 double angle; 912 913 if (headPort.getContainer() != tailPort 914 .getContainer()) { 915 angle = Math.PI / 5.0 916 + 1.5 * Math.atan(0.3 * count); 917 } else { 918 angle = Math.PI / 3.0 919 - 0.75 * Math.atan(0.3 * count); 920 } 921 922 // Create the new relation. 923 // Note that we specify no class so that we use the 924 // container's factory method when this gets parsed 925 moml.append("<relation name=\"" + relationName 926 + "\"><property name=\"exitAngle\" value=\"" 927 + angle + "\"/></relation>\n"); 928 moml.append("<link port=\"" 929 + tailPort.getName(ptolemyModel) 930 + "\" relation=\"" + relationName + "\"/>\n"); 931 } 932 933 moml.append("<link port=\"" + headPort.getName(ptolemyModel) 934 + "\" relation=\"" + relationName + "\"/>\n"); 935 936 // Record moml so that we can blow away these 937 // links in case we can't create them 938 failmoml.append("<unlink port=\"" 939 + headPort.getName(ptolemyModel) + "\" relation=\"" 940 + relationName + "\"/>\n"); 941 942 if (linkRelation == null) { 943 failmoml.append("<unlink port=\"" 944 + tailPort.getName(ptolemyModel) 945 + "\" relation=\"" + relationName + "\"/>\n"); 946 failmoml.append("<deleteRelation name=\"" + relationName 947 + "\"/>\n"); 948 } 949 950 // close the context 951 if (ptolemyModel != container) { 952 moml.append("</entity>"); 953 failmoml.append("</entity>"); 954 } 955 956 if (createdNewRelation) { 957 return relationName; 958 } else { 959 return null; 960 } 961 } else { 962 throw new RuntimeException( 963 "Attempt to create a link between non-states: " 964 + "Head = " + head + ", Tail = " + tail); 965 } 966 } else { 967 // No Linking to do. 968 return null; 969 } 970 } 971 972 /** Append moml to the given buffer that connects a link with the 973 * given tail. If the relation argument is non-null, then that 974 * relation is used, and null is returned. Otherwise, a new 975 * relation is created, and its name is returned. Names in the 976 * generated moml will be relative to the specified container. 977 * The failmoml argument is also populated, but with MoML code 978 * to execute if the link fails. This disconnects any partially 979 * constructed link. 980 */ 981 private String _linkTail(NamedObj container, StringBuffer moml, 982 StringBuffer failmoml, NamedObj linkHead, NamedObj linkTail, 983 Relation linkRelation) { 984 // NOTE: This method is almost identical to the previous 985 // one, but just enough different that it isn't obvious 986 // how to combine them into one method. 987 if (linkHead != null && linkTail != null) { 988 NamedObj head = (NamedObj) getSemanticObject(linkHead); 989 NamedObj tail = (NamedObj) getSemanticObject(linkTail); 990 991 if (head instanceof Flowable && tail instanceof Flowable) { 992 // When we connect two states, we actually connect the 993 // appropriate ports. 994 Flowable headState = (Flowable) head; 995 Flowable tailState = (Flowable) tail; 996 ComponentPort headPort = headState.getIncomingPort(); 997 ComponentPort tailPort = tailState.getOutgoingPort(); 998 NamedObj ptolemyModel = getPtolemyModel(); 999 1000 // If the context is not the entity that we're editing, 1001 // then we need to set the context correctly. 1002 if (ptolemyModel != container) { 1003 String contextString = "<entity name=\"" 1004 + ptolemyModel.getName(container) + "\">\n"; 1005 moml.append(contextString); 1006 failmoml.append(contextString); 1007 } 1008 1009 boolean createdNewRelation = false; 1010 String relationName = null; 1011 1012 if (linkRelation != null) { 1013 // Pre-existing relation. Use it. 1014 relationName = linkRelation.getName(ptolemyModel); 1015 } else { 1016 createdNewRelation = true; 1017 1018 // Linking two ports with a new relation. 1019 relationName = ptolemyModel.uniqueName("relation"); 1020 1021 // Note that we specify no class so that we use the 1022 // container's factory method when this gets parsed 1023 moml.append( 1024 "<relation name=\"" + relationName + "\"/>\n"); 1025 moml.append("<link port=\"" 1026 + headPort.getName(ptolemyModel) 1027 + "\" relation=\"" + relationName + "\"/>\n"); 1028 } 1029 1030 moml.append("<link port=\"" + tailPort.getName(ptolemyModel) 1031 + "\" relation=\"" + relationName + "\"/>\n"); 1032 1033 // Record moml so that we can blow away these 1034 // links in case we can't create them 1035 failmoml.append("<unlink port=\"" 1036 + tailPort.getName(ptolemyModel) + "\" relation=\"" 1037 + relationName + "\"/>\n"); 1038 1039 if (linkRelation == null) { 1040 failmoml.append("<unlink port=\"" 1041 + headPort.getName(ptolemyModel) 1042 + "\" relation=\"" + relationName + "\"/>\n"); 1043 failmoml.append("<deleteRelation name=\"" + relationName 1044 + "\"/>\n"); 1045 } 1046 1047 // close the context 1048 if (ptolemyModel != container) { 1049 moml.append("</entity>"); 1050 failmoml.append("</entity>"); 1051 } 1052 1053 if (createdNewRelation) { 1054 return relationName; 1055 } else { 1056 return null; 1057 } 1058 } else { 1059 throw new RuntimeException( 1060 "Attempt to create a link between non-states: " 1061 + "Head = " + head + ", Tail = " + tail); 1062 } 1063 } else { 1064 // No Linking to do. 1065 return null; 1066 } 1067 } 1068 1069 /** Return moml to unlink a relation with the given head in the 1070 * specified container. 1071 */ 1072 private String _unlinkHead(NamedObj container, NamedObj linkHead, 1073 Relation relation) { 1074 NamedObj head = (NamedObj) getSemanticObject(linkHead); 1075 Flowable headState = (Flowable) head; 1076 ComponentPort headPort = headState.getIncomingPort(); 1077 return "<unlink port=\"" + headPort.getName(container) 1078 + "\" relation=\"" + relation.getName(container) + "\"/>\n"; 1079 } 1080 1081 /** Return moml to unlink a relation with the given tail in the 1082 * specified container. 1083 */ 1084 private String _unlinkTail(NamedObj container, NamedObj linkTail, 1085 Relation relation) { 1086 NamedObj tail = (NamedObj) getSemanticObject(linkTail); 1087 Flowable tailState = (Flowable) tail; 1088 ComponentPort tailPort = tailState.getOutgoingPort(); 1089 return "<unlink port=\"" + tailPort.getName(container) 1090 + "\" relation=\"" + relation.getName(container) + "\"/>\n"; 1091 } 1092 } 1093 1094 /** The model for external ports. 1095 */ 1096 public class PortModel extends NamedObjNodeModel { 1097 /** Return a MoML String that will delete the given node from the 1098 * Ptolemy model. This assumes that the context is the container 1099 * of the port to be deleted. 1100 * @param node The node to be deleted. 1101 * @return A valid MoML string. 1102 */ 1103 @Override 1104 public String getDeleteNodeMoML(Object node) { 1105 NamedObj deleteObj = ((Locatable) node).getContainer(); 1106 String moml = "<deletePort name=\"" + deleteObj.getName() 1107 + "\"/>\n"; 1108 return moml; 1109 } 1110 1111 /** Return the graph parent of the given node. 1112 * @param node The node, which is assumed to be an icon contained in 1113 * this graph model. 1114 * @return The container of the icon's container, which should 1115 * be the root of this graph model. 1116 */ 1117 @Override 1118 public Object getParent(Object node) { 1119 return ((Locatable) node).getContainer().getContainer(); 1120 } 1121 1122 /** Return an iterator over the edges coming into the given node. 1123 * This method first ensures that there is an arc 1124 * object for every link. 1125 * The iterator is constructed by 1126 * removing any arcs that do not have the given node as head. 1127 * @param node The node, which is assumed to be an icon contained in 1128 * this graph model. 1129 * @return An iterator of Arc objects, all of which have 1130 * the given node as their head. 1131 */ 1132 @Override 1133 public Iterator inEdges(Object node) { 1134 return new NullIterator(); 1135 } 1136 1137 /** Return an iterator over the edges coming into the given node. 1138 * This method first ensures that there is an arc 1139 * object for every link. The iterator is constructed by 1140 * removing any arcs that do not have the given node as tail. 1141 * @param node The node, which is assumed to be an icon contained in 1142 * this graph model. 1143 * @return An iterator of Arc objects, all of which have 1144 * the given node as their tail. 1145 */ 1146 @Override 1147 public Iterator outEdges(Object node) { 1148 return new NullIterator(); 1149 } 1150 1151 /** Remove the given node from the model. The node is assumed 1152 * to be an icon. 1153 */ 1154 @Override 1155 public void removeNode(final Object eventSource, Object node) { 1156 NamedObj deleteObj = ((Locatable) node).getContainer(); 1157 String elementName = null; 1158 1159 if (deleteObj instanceof ComponentPort) { 1160 // Object is an entity. 1161 elementName = "deletePort"; 1162 } else { 1163 throw new InternalErrorException( 1164 "Attempt to remove a node that is not an Entity. " 1165 + "node = " + node); 1166 } 1167 1168 String moml = "<" + elementName + " name=\"" + deleteObj.getName() 1169 + "\"/>\n"; 1170 1171 // Make the request in the context of the container. 1172 NamedObj container = deleteObj.getContainer(); 1173 MoMLChangeRequest request = new MoMLChangeRequest( 1174 FSMGraphModel.this, container, moml); 1175 request.setUndoable(true); 1176 request.addChangeListener(new ChangeListener() { 1177 @Override 1178 public void changeFailed(ChangeRequest change, 1179 Exception exception) { 1180 // If we fail, then issue structureChanged. 1181 dispatchGraphEvent(new GraphEvent(eventSource, 1182 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1183 } 1184 1185 @Override 1186 public void changeExecuted(ChangeRequest change) { 1187 // If we succeed, then issue structureChanged, since 1188 // this is likely connected to something. 1189 dispatchGraphEvent(new GraphEvent(eventSource, 1190 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1191 } 1192 }); 1193 request.setUndoable(true); 1194 container.requestChange(request); 1195 } 1196 } 1197 1198 /** The model for an icon that represent states. 1199 */ 1200 public class StateModel extends NamedObjNodeModel { 1201 /** Return a MoML String that will delete the given node from the 1202 * Ptolemy model. This assumes that the context is the container 1203 * of the state. 1204 * @param node The node to be deleted. 1205 * @return A valid MoML string. 1206 */ 1207 @Override 1208 public String getDeleteNodeMoML(Object node) { 1209 NamedObj deleteObj = ((Locatable) node).getContainer(); 1210 NamedObj container = deleteObj.getContainer(); 1211 1212 StringBuffer moml = new StringBuffer("<group>\n"); 1213 1214 moml.append("<deleteEntity name=\"" + deleteObj.getName(container) 1215 + "\"/>\n"); 1216 1217 CompositeEntity master = (CompositeEntity) deleteObj.getContainer(); 1218 1219 // Nothing to do if there is no container. 1220 if (master != null) { 1221 // Remove any referenced refinements that are not also 1222 // referenced by other states. 1223 TypedActor[] refinements = null; 1224 1225 try { 1226 if (deleteObj instanceof State) { 1227 refinements = ((State) deleteObj).getRefinement(); 1228 } 1229 } catch (IllegalActionException ex) { 1230 // Ignore, no refinement to remove. 1231 } 1232 1233 if (refinements != null) { 1234 for (TypedActor refinement : refinements) { 1235 // By default, if no other state or transition refers 1236 // to this refinement, then we will remove it. 1237 boolean removeIt = true; 1238 Iterator states = master.entityList(State.class) 1239 .iterator(); 1240 1241 while (removeIt && states.hasNext()) { 1242 State state = (State) states.next(); 1243 1244 if (state == deleteObj) { 1245 continue; 1246 } 1247 1248 TypedActor[] stateRefinements = null; 1249 1250 try { 1251 stateRefinements = state.getRefinement(); 1252 } catch (IllegalActionException ex) { 1253 // Ignore, no refinement to check 1254 } 1255 1256 if (stateRefinements == null) { 1257 continue; 1258 } 1259 1260 for (TypedActor stateRefinement : stateRefinements) { 1261 if (stateRefinement == refinement) { 1262 removeIt = false; 1263 break; 1264 } 1265 } 1266 } 1267 1268 // Next check transitions. 1269 Iterator transitions = master.relationList().iterator(); 1270 1271 while (removeIt && transitions.hasNext()) { 1272 Relation transition = (Relation) transitions.next(); 1273 1274 if (!(transition instanceof Transition)) { 1275 continue; 1276 } 1277 1278 TypedActor[] transitionRefinements = null; 1279 1280 try { 1281 transitionRefinements = ((Transition) transition) 1282 .getRefinement(); 1283 } catch (IllegalActionException e) { 1284 // Ignore, no refinement to check. 1285 } 1286 1287 if (transitionRefinements == null) { 1288 continue; 1289 } 1290 1291 for (TypedActor transitionRefinement : transitionRefinements) { 1292 if (transitionRefinement == refinement) { 1293 removeIt = false; 1294 break; 1295 } 1296 } 1297 } 1298 1299 if (removeIt) { 1300 moml.append("<deleteEntity name=\"" 1301 + ((NamedObj) refinement).getName(container) 1302 + "\"/>\n"); 1303 } 1304 } 1305 } 1306 } 1307 1308 moml.append("</group>\n"); 1309 return moml.toString(); 1310 } 1311 1312 /** Return the graph parent of the given node. 1313 * @param node The node, which is assumed to be a location. 1314 * @return The container of the icon's container, which should 1315 * be the root of this graph model. 1316 */ 1317 @Override 1318 public Object getParent(Object node) { 1319 return ((Locatable) node).getContainer().getContainer(); 1320 } 1321 1322 /** Return an iterator over the edges coming into the given node. 1323 * This method first ensures that there is an arc 1324 * object for every link. The iterator is constructed by 1325 * removing any arcs that do not have the given node as head. 1326 * @param node The node, which is assumed to be a location. 1327 * @return An iterator of link objects, all of which have 1328 * the given node as their head. 1329 */ 1330 @Override 1331 public Iterator inEdges(Object node) { 1332 Locatable icon = (Locatable) node; 1333 1334 // Go through all the links, creating a list of 1335 // those we are connected to. 1336 List stateLinkList = new LinkedList(); 1337 Iterator links = _linkSet.iterator(); 1338 1339 while (links.hasNext()) { 1340 Link link = (Link) links.next(); 1341 NamedObj head = (NamedObj) link.getHead(); 1342 1343 if (head != null && head.equals(icon)) { 1344 stateLinkList.add(link); 1345 } 1346 } 1347 1348 return stateLinkList.iterator(); 1349 } 1350 1351 /** Return an iterator over the edges coming into the given node. 1352 * This method first ensures that there is an arc 1353 * object for every link. The iterator is constructed by 1354 * removing any arcs that do not have the given node as tail. 1355 * @param node The node, which is assumed to be a location. 1356 * @return An iterator of Link objects, all of which have 1357 * the given node as their tail. 1358 */ 1359 @Override 1360 public Iterator outEdges(Object node) { 1361 Locatable icon = (Locatable) node; 1362 1363 // Go through all the links, creating a list of 1364 // those we are connected to. 1365 List stateLinkList = new LinkedList(); 1366 Iterator links = _linkSet.iterator(); 1367 1368 while (links.hasNext()) { 1369 Link link = (Link) links.next(); 1370 Object tail = link.getTail(); 1371 1372 if (tail != null && tail.equals(icon)) { 1373 stateLinkList.add(link); 1374 } 1375 } 1376 1377 return stateLinkList.iterator(); 1378 } 1379 1380 /** Remove the given node from the model. The node is assumed 1381 * to be a Locatable belonging to an entity. 1382 */ 1383 @Override 1384 public void removeNode(final Object eventSource, Object node) { 1385 NamedObj deleteObj = ((Locatable) node).getContainer(); 1386 1387 // First remove all the in and out edges. 1388 // This isn't done automatically by the Ptolemy kernel, because 1389 // the kernel only removes the links to the relations. The 1390 // relations themselves are left in place. But in the FSM 1391 // domain, it makes no sense to have a relation with only one 1392 // link to it. So we remove the relations. 1393 Iterator inEdges = inEdges(node); 1394 1395 while (inEdges.hasNext()) { 1396 disconnectEdge(eventSource, inEdges.next()); 1397 } 1398 1399 Iterator outEdges = outEdges(node); 1400 1401 while (outEdges.hasNext()) { 1402 disconnectEdge(eventSource, outEdges.next()); 1403 } 1404 1405 String elementName = null; 1406 1407 if (deleteObj instanceof ComponentEntity) { 1408 // Object is an entity. 1409 elementName = "deleteEntity"; 1410 } else { 1411 throw new InternalErrorException( 1412 "Attempt to remove a node that is not an Entity. " 1413 + "node = " + node); 1414 } 1415 1416 String moml = "<" + elementName + " name=\"" + deleteObj.getName() 1417 + "\"/>\n"; 1418 1419 // Make the request in the context of the container. 1420 NamedObj container = deleteObj.getContainer(); 1421 MoMLChangeRequest request = new MoMLChangeRequest( 1422 FSMGraphModel.this, container, moml); 1423 request.addChangeListener(new ChangeListener() { 1424 @Override 1425 public void changeFailed(ChangeRequest change, 1426 Exception exception) { 1427 // If we fail, then issue structureChanged. 1428 dispatchGraphEvent(new GraphEvent(eventSource, 1429 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1430 } 1431 1432 @Override 1433 public void changeExecuted(ChangeRequest change) { 1434 // If we succeed, then issue structureChanged, since 1435 // this is likely connected to something. 1436 dispatchGraphEvent(new GraphEvent(eventSource, 1437 GraphEvent.STRUCTURE_CHANGED, getRoot())); 1438 } 1439 }); 1440 request.setUndoable(true); 1441 container.requestChange(request); 1442 } 1443 } 1444}