001/* A relation supporting clustered graphs. 002 003 Copyright (c) 1997-2014 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.kernel; 029 030import java.util.Collections; 031import java.util.Enumeration; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035 036import ptolemy.kernel.util.ChangeRequest; 037import ptolemy.kernel.util.IllegalActionException; 038import ptolemy.kernel.util.InternalErrorException; 039import ptolemy.kernel.util.NameDuplicationException; 040import ptolemy.kernel.util.NamedObj; 041import ptolemy.kernel.util.Workspace; 042 043/////////////////////////////////////////////////////////////////// 044//// ComponentRelation 045 046/** 047 This class defines a relation supporting hierarchy (clustered graphs). 048 Specifically, a method is added for defining a container and for 049 performing deep traversals of 050 a graph. Most importantly, however, instances of this class refuse to link 051 to ports that are not instances of ComponentPort. Thus, this class 052 ensures that ComponentPort instances are only connected to other 053 ComponentPort instances. 054 <p> 055 Derived classes may wish to further constrain linked ports to a subclass 056 of ComponentPort, or to disallow links under other circumstances, 057 for example if the relation cannot support any more links. 058 Such derived classes should override the protected method _checkPort() 059 to throw an exception. 060 <p> 061 To link a ComponentPort to a ComponentRelation, use the link() or 062 liberalLink() method in the ComponentPort class. To remove a link, 063 use the unlink() method. 064 <p> 065 The container for instances of this class can only be instances of 066 ComponentEntity. Derived classes may wish to further constrain the 067 container to subclasses of ComponentEntity. To do this, they should 068 override the protected _checkContainer() method. 069 070 @author Edward A. Lee 071 @version $Id$ 072 @since Ptolemy II 0.2 073 @Pt.ProposedRating Green (eal) 074 @Pt.AcceptedRating Green (johnr) 075 */ 076public class ComponentRelation extends Relation { 077 /** Construct a relation in the default workspace with an empty string 078 * as its name. Add the relation to the directory of the workspace. 079 */ 080 public ComponentRelation() { 081 super(); 082 } 083 084 /** Construct a relation in the specified workspace with an empty 085 * string as a name. You can then change the name with setName(). 086 * If the workspace argument is null, then use the default workspace. 087 * Add the relation to the workspace directory. 088 * 089 * @param workspace The workspace that will list the relation. 090 */ 091 public ComponentRelation(Workspace workspace) { 092 super(workspace); 093 } 094 095 /** Construct a relation with the given name contained by the specified 096 * entity. The container argument must not be null, or a 097 * NullPointerException will be thrown. This relation will use the 098 * workspace of the container for synchronization and version counts. 099 * If the name argument is null, then the name is set to the empty string. 100 * This constructor write-synchronizes on the workspace. 101 * 102 * @param container The container. 103 * @param name The name of the relation. 104 * @exception IllegalActionException If the container is incompatible 105 * with this relation. 106 * @exception NameDuplicationException If the name coincides with 107 * a relation already in the container. 108 */ 109 public ComponentRelation(CompositeEntity container, String name) 110 throws IllegalActionException, NameDuplicationException { 111 super(container.workspace(), name); 112 setContainer(container); 113 } 114 115 /////////////////////////////////////////////////////////////////// 116 //// public methods //// 117 118 /** Clone the object into the specified workspace. The new object is 119 * <i>not</i> added to the directory of that workspace (you must do this 120 * yourself if you want it there). 121 * The result is a new relation with no links and no container. 122 * @param workspace The workspace for the cloned object. 123 * @exception CloneNotSupportedException If one or more of the attributes 124 * cannot be cloned. 125 * @return A new ComponentRelation. 126 */ 127 @Override 128 public Object clone(Workspace workspace) throws CloneNotSupportedException { 129 ComponentRelation newObject = (ComponentRelation) super.clone( 130 workspace); 131 newObject._container = null; 132 return newObject; 133 } 134 135 /** Deeply list the ports linked to this relation. Look through 136 * all transparent ports and return only opaque ports. 137 * This method is read-synchronized on the workspace. 138 * @return An unmodifiable list of ComponentPorts. 139 */ 140 public List deepLinkedPortList() { 141 try { 142 _workspace.getReadAccess(); 143 144 if (_deepLinkedPortsVersion == _workspace.getVersion()) { 145 // Cache is valid. Use it. 146 return _deepLinkedPorts; 147 } 148 149 Iterator nearPorts = linkedPortList().iterator(); 150 _deepLinkedPorts = new LinkedList(); 151 152 while (nearPorts.hasNext()) { 153 ComponentPort port = (ComponentPort) nearPorts.next(); 154 155 if (port._isInsideLinkable(this.getContainer())) { 156 // Port is above me in the hierarchy. 157 if (port.isOpaque()) { 158 // Port is opaque. Append it to list. 159 _deepLinkedPorts.add(port); 160 } else { 161 // Port is transparent. See through it. 162 _deepLinkedPorts.addAll(port.deepConnectedPortList()); 163 } 164 } else { 165 // Port below me in the hierarchy. 166 if (port.isOpaque()) { 167 _deepLinkedPorts.add(port); 168 } else { 169 _deepLinkedPorts.addAll(port.deepInsidePortList()); 170 } 171 } 172 } 173 174 _deepLinkedPortsVersion = _workspace.getVersion(); 175 return Collections.unmodifiableList(_deepLinkedPorts); 176 } finally { 177 _workspace.doneReading(); 178 } 179 } 180 181 /** Deeply enumerate the ports linked to this relation. Look through 182 * all transparent ports and return only opaque ports. 183 * This method is read-synchronized on the workspace. 184 * @return An enumeration of ComponentPorts. 185 * @deprecated Use deepLinkedPortList() instead. 186 */ 187 @Deprecated 188 public Enumeration deepLinkedPorts() { 189 return Collections.enumeration(deepLinkedPortList()); 190 } 191 192 /** Get the container entity. 193 * @return An instance of CompositeEntity. 194 * @see #setContainer(CompositeEntity) 195 */ 196 @Override 197 public NamedObj getContainer() { 198 return _container; 199 } 200 201 /** Move this object down by one in the list of relations of 202 * its container. If this object is already last, do nothing. 203 * Increment the version of the workspace. 204 * @return The index of the specified object prior to moving it, 205 * or -1 if it is not moved. 206 * @exception IllegalActionException If this object has 207 * no container. 208 */ 209 @Override 210 public int moveDown() throws IllegalActionException { 211 CompositeEntity container = (CompositeEntity) getContainer(); 212 213 if (container == null) { 214 throw new IllegalActionException(this, "Has no container."); 215 } 216 217 try { 218 _workspace.getWriteAccess(); 219 220 int result = container._containedRelations.moveDown(this); 221 222 // Propagate. 223 Iterator derivedObjects = getDerivedList().iterator(); 224 225 while (derivedObjects.hasNext()) { 226 NamedObj derived = (NamedObj) derivedObjects.next(); 227 container = (CompositeEntity) derived.getContainer(); 228 container._containedRelations.moveDown(derived); 229 } 230 231 return result; 232 } finally { 233 _workspace.doneWriting(); 234 } 235 } 236 237 /** Move this object to the first position in the list 238 * of relations of the container. If this object is already first, 239 * do nothing. Increment the version of the workspace. 240 * @return The index of the specified object prior to moving it, 241 * or -1 if it is not moved. 242 * @exception IllegalActionException If this object has 243 * no container. 244 */ 245 @Override 246 public int moveToFirst() throws IllegalActionException { 247 CompositeEntity container = (CompositeEntity) getContainer(); 248 249 if (container == null) { 250 throw new IllegalActionException(this, "Has no container."); 251 } 252 253 try { 254 _workspace.getWriteAccess(); 255 256 int result = container._containedRelations.moveToFirst(this); 257 258 // Propagate. 259 Iterator derivedObjects = getDerivedList().iterator(); 260 261 while (derivedObjects.hasNext()) { 262 NamedObj derived = (NamedObj) derivedObjects.next(); 263 container = (CompositeEntity) derived.getContainer(); 264 container._containedRelations.moveToFirst(derived); 265 } 266 267 return result; 268 } finally { 269 _workspace.doneWriting(); 270 } 271 } 272 273 /** Move this object to the specified position in the list 274 * of relations of the container. If this object is already at 275 * the specified position, do nothing. Increment the version of the 276 * workspace. 277 * @param index The position to move this object to. 278 * @return The index of the specified object prior to moving it, 279 * or -1 if it is not moved. 280 * @exception IllegalActionException If this object has 281 * no container or if the index is out of bounds. 282 */ 283 @Override 284 public int moveToIndex(int index) throws IllegalActionException { 285 CompositeEntity container = (CompositeEntity) getContainer(); 286 287 if (container == null) { 288 throw new IllegalActionException(this, "Has no container."); 289 } 290 291 try { 292 _workspace.getWriteAccess(); 293 294 int result = container._containedRelations.moveToIndex(this, index); 295 296 // Propagate. 297 Iterator derivedObjects = getDerivedList().iterator(); 298 299 while (derivedObjects.hasNext()) { 300 NamedObj derived = (NamedObj) derivedObjects.next(); 301 container = (CompositeEntity) derived.getContainer(); 302 container._containedRelations.moveToIndex(derived, index); 303 } 304 305 return result; 306 } finally { 307 _workspace.doneWriting(); 308 } 309 } 310 311 /** Move this object to the last position in the list 312 * of relations of the container. If this object is already last, 313 * do nothing. Increment the version of the workspace. 314 * @return The index of the specified object prior to moving it, 315 * or -1 if it is not moved. 316 * @exception IllegalActionException If this object has 317 * no container. 318 */ 319 @Override 320 public int moveToLast() throws IllegalActionException { 321 CompositeEntity container = (CompositeEntity) getContainer(); 322 323 if (container == null) { 324 throw new IllegalActionException(this, "Has no container."); 325 } 326 327 try { 328 _workspace.getWriteAccess(); 329 330 int result = container._containedRelations.moveToLast(this); 331 332 // Propagate. 333 Iterator derivedObjects = getDerivedList().iterator(); 334 335 while (derivedObjects.hasNext()) { 336 NamedObj derived = (NamedObj) derivedObjects.next(); 337 container = (CompositeEntity) derived.getContainer(); 338 container._containedRelations.moveToLast(derived); 339 } 340 341 return result; 342 } finally { 343 _workspace.doneWriting(); 344 } 345 } 346 347 /** Move this object up by one in the list of 348 * relations of the container. If this object is already first, do 349 * nothing. Increment the version of the workspace. 350 * @return The index of the specified object prior to moving it, 351 * or -1 if it is not moved. 352 * @exception IllegalActionException If this object has 353 * no container. 354 */ 355 @Override 356 public int moveUp() throws IllegalActionException { 357 CompositeEntity container = (CompositeEntity) getContainer(); 358 359 if (container == null) { 360 throw new IllegalActionException(this, "Has no container."); 361 } 362 363 try { 364 _workspace.getWriteAccess(); 365 366 int result = container._containedRelations.moveUp(this); 367 368 // Propagate. 369 Iterator derivedObjects = getDerivedList().iterator(); 370 371 while (derivedObjects.hasNext()) { 372 NamedObj derived = (NamedObj) derivedObjects.next(); 373 container = (CompositeEntity) derived.getContainer(); 374 container._containedRelations.moveUp(derived); 375 } 376 377 return result; 378 } finally { 379 _workspace.doneWriting(); 380 } 381 } 382 383 /** Specify the container entity, adding the relation to the list 384 * of relations in the container. If the container already contains 385 * a relation with the same name, then throw an exception and do not make 386 * any changes. Similarly, if the container is not in the same 387 * workspace as this relation, throw an exception. If the relation is 388 * a class element and the proposed container does not match 389 * the current container, then also throw an exception. 390 * If the relation is already contained by the container, do nothing. 391 * If this relation already has a container, remove it 392 * from that container first. Otherwise, remove it from 393 * the workspace directory, if it is present. 394 * If the argument is null, then unlink the ports from the relation and 395 * remove it from its container. 396 * It is not added to the workspace directory, so this could result in 397 * this relation being garbage collected. 398 * Derived classes may further constrain the class of the container 399 * to a subclass of CompositeEntity. This method validates all 400 * deeply contained instances of Settable, since they may no longer 401 * be valid in the new context. This method is write-synchronized 402 * on the workspace and increments its version number. 403 * @param container The proposed container. 404 * @exception IllegalActionException If this entity and the container 405 * are not in the same workspace, or if 406 * a contained Settable becomes invalid and the error handler 407 * throws it. 408 * @exception NameDuplicationException If the name collides with a name 409 * already on the contents list of the container. 410 * @see #getContainer() 411 */ 412 public void setContainer(CompositeEntity container) 413 throws IllegalActionException, NameDuplicationException { 414 if (container != null && _workspace != container.workspace()) { 415 throw new IllegalActionException(this, container, 416 "Cannot set container because workspaces are different."); 417 } 418 419 try { 420 _workspace.getWriteAccess(); 421 _checkContainer(container); 422 423 CompositeEntity previousContainer = (CompositeEntity) getContainer(); 424 425 if (previousContainer == container) { 426 return; 427 } 428 429 // Do this first, because it may throw an exception. 430 if (container != null) { 431 container._addRelation(this); 432 433 if (previousContainer == null) { 434 _workspace.remove(this); 435 } 436 } 437 438 _notifyHierarchyListenersBeforeChange(); 439 440 try { 441 _container = container; 442 443 if (previousContainer != null) { 444 previousContainer._removeRelation(this); 445 } 446 447 if (container == null) { 448 unlinkAll(); 449 } else { 450 // We have successfully set a new container for this 451 // object. Mark it modified to ensure MoML export. 452 // Transfer any queued change requests to the 453 // new container. There could be queued change 454 // requests if this component is deferring change 455 // requests. 456 if (_changeRequests != null) { 457 Iterator requests = _changeRequests.iterator(); 458 459 while (requests.hasNext()) { 460 ChangeRequest request = (ChangeRequest) requests 461 .next(); 462 container.requestChange(request); 463 } 464 465 _changeRequests = null; 466 } 467 } 468 469 // Validate all deeply contained settables, since 470 // they may no longer be valid in the new context. 471 validateSettables(); 472 } finally { 473 // Since we definitely notified the listeners 474 // before the change, we must definitely notify 475 // them after the change, even if the change caused 476 // some exceptions. Note that this too may trigger 477 // exceptions. 478 _notifyHierarchyListenersAfterChange(); 479 } 480 } finally { 481 _workspace.doneWriting(); 482 } 483 } 484 485 /** Set the name of the ComponentRelation. If there is already 486 * a ComponentRelation of the container with the same name, throw an 487 * exception. 488 * @exception IllegalActionException If the name has a period. 489 * @exception NameDuplicationException If there is already a relation 490 * with the same name in the container. 491 */ 492 @Override 493 public void setName(String name) 494 throws IllegalActionException, NameDuplicationException { 495 if (name == null) { 496 name = ""; 497 } 498 499 CompositeEntity container = (CompositeEntity) getContainer(); 500 501 if (container != null) { 502 ComponentRelation another = container.getRelation(name); 503 504 if (another != null && another != this) { 505 throw new NameDuplicationException(container, 506 "Name duplication: " + name); 507 } 508 } 509 510 super.setName(name); 511 } 512 513 /** Override the base class to break inside links on ports as well 514 * as outside lists. 515 * This method is write-synchronized on the workspace and increments 516 * its version number. 517 */ 518 @Override 519 public void unlinkAll() { 520 // NOTE: Do not just use _portList.unlinkAll() because then the 521 // containers of the ports are not notified of the change. 522 // Also, have to first copy the ports references, then remove 523 // them, to avoid a corrupted enumeration exception. 524 // Unlink the outside links of linked ports. 525 super.unlinkAll(); 526 try { 527 528 // Next, remove the links that are inside links of ports. 529 _workspace.getWriteAccess(); 530 531 LinkedList ports = new LinkedList(); 532 Enumeration links = _linkList.getContainers(); 533 534 while (links.hasMoreElements()) { 535 Object link = links.nextElement(); 536 537 if (link instanceof ComponentPort) { 538 ports.add(link); 539 } 540 } 541 542 Iterator portsIterator = ports.iterator(); 543 544 while (portsIterator.hasNext()) { 545 ((ComponentPort) portsIterator.next()).unlinkInside(this); 546 } 547 } finally { 548 _workspace.doneWriting(); 549 } 550 } 551 552 /////////////////////////////////////////////////////////////////// 553 //// protected methods //// 554 555 /** Check that the specified container is of a suitable class for 556 * this relation. In this base class, this method returns immediately 557 * without doing anything. 558 * @param container The proposed container. 559 * @exception IllegalActionException If the container is not of 560 * an acceptable class. Not thrown in this base class. 561 */ 562 protected void _checkContainer(CompositeEntity container) 563 throws IllegalActionException { 564 } 565 566 /** Throw an exception if the specified port cannot be linked to this 567 * relation (is not of class ComponentPort). 568 * @param port The port to link to. 569 * @exception IllegalActionException If the port is not a ComponentPort. 570 */ 571 @Override 572 protected void _checkPort(Port port) throws IllegalActionException { 573 if (!(port instanceof ComponentPort)) { 574 throw new IllegalActionException(this, port, 575 "ComponentRelation can only link to a ComponentPort."); 576 } 577 } 578 579 /** Throw an exception if the specified relation is not an instance 580 * of ComponentRelation. 581 * @param relation The relation to link to. 582 * @param symmetric If true, the call _checkRelation on the specified 583 * relation with this as an argument. 584 * @exception IllegalActionException If this port has no container, 585 * or if this port is not an acceptable port for the specified 586 * relation. 587 */ 588 @Override 589 protected void _checkRelation(Relation relation, boolean symmetric) 590 throws IllegalActionException { 591 if (!(relation instanceof ComponentRelation)) { 592 throw new IllegalActionException(this, relation, 593 "ComponentRelation can only link to a ComponentRelation."); 594 } 595 596 super._checkRelation(relation, symmetric); 597 } 598 599 /** Propagate existence of this object to the 600 * specified object. This overrides the base class 601 * to set the container. 602 * @param container Object to contain the new object. 603 * @exception IllegalActionException If the object 604 * cannot be cloned. 605 * @return A new object of the same class and name 606 * as this one. 607 */ 608 @Override 609 protected NamedObj _propagateExistence(NamedObj container) 610 throws IllegalActionException { 611 try { 612 ComponentRelation newObject = (ComponentRelation) super._propagateExistence( 613 container); 614 // FindBugs warns that the cast of container is 615 // unchecked. 616 if (!(container instanceof CompositeEntity)) { 617 throw new InternalErrorException( 618 container + " is not a CompositeEntity."); 619 } else { 620 newObject.setContainer((CompositeEntity) container); 621 } 622 return newObject; 623 } catch (NameDuplicationException e) { 624 throw new InternalErrorException(e); 625 } 626 } 627 628 /////////////////////////////////////////////////////////////////// 629 //// private variables //// 630 631 /** @serial The entity that contains this entity. */ 632 private CompositeEntity _container; 633 634 // A cache of the deeply linked ports, and the version used to 635 // construct it. 636 // 'transient' means that the variable will not be serialized. 637 private transient List _deepLinkedPorts; 638 639 private transient long _deepLinkedPortsVersion = -1; 640}