001/* A Relation links ports, and therefore the entities that contain them. 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.HashSet; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Set; 036 037import ptolemy.kernel.util.CrossRefList; 038import ptolemy.kernel.util.IllegalActionException; 039import ptolemy.kernel.util.InternalErrorException; 040import ptolemy.kernel.util.NamedObj; 041import ptolemy.kernel.util.Workspace; 042 043/////////////////////////////////////////////////////////////////// 044//// Relation 045 046/** 047 A Relation links ports, and therefore the entities that contain them. 048 To link a port to a relation, use the link() method 049 in the Port class. To remove a link, use the unlink() method in the 050 Port class. 051 <p> 052 Relations can also be linked to other Relations. 053 To create such a link, use the link() method of this class. 054 To remove such a link, use the unlink() method. A group of linked 055 relations behaves exactly as if it were one relation directly linked 056 to all the ports linked to by each relation. In particular, the 057 connectedPortList() method of the Port class returns the same list 058 whether a single relation is used or a relation group. The order 059 in which the ports are listed is the order in which links were 060 made between the port and relations in the relation group. 061 It is not relevant which relation in a relation group the port 062 links to. 063 <p> 064 Derived classes may wish to disallow links under certain circumstances, 065 for example if the proposed port is not an instance of an appropriate 066 subclass of Port, or if the relation cannot support any more links. 067 Such derived classes should override the protected method _checkPort() 068 or _checkRelation() to throw an exception. 069 070 @author Edward A. Lee, Neil Smyth 071 @version $Id$ 072 @since Ptolemy II 0.2 073 @Pt.ProposedRating Green (eal) 074 @Pt.AcceptedRating Green (acataldo) 075 @see Port 076 @see Entity 077 */ 078public class Relation extends NamedObj { 079 /** Construct a relation in the default workspace with an empty string 080 * as its name. Increment the version number of the workspace. 081 * The object is added to the workspace directory. 082 */ 083 public Relation() { 084 super(); 085 _elementName = "relation"; 086 } 087 088 /** Construct a relation in the default workspace with the given name. 089 * If the name argument is null, then the name is set to the empty string. 090 * Increment the version number of the workspace. 091 * The object is added to the workspace directory. 092 * @param name Name of this object. 093 * @exception IllegalActionException If the name has a period. 094 */ 095 public Relation(String name) throws IllegalActionException { 096 super(name); 097 _elementName = "relation"; 098 } 099 100 /** Construct a relation in the given workspace with an empty string 101 * as a name. 102 * If the workspace argument is null, use the default workspace. 103 * The object is added to the workspace directory. 104 * Increment the version of the workspace. 105 * @param workspace The workspace for synchronization and version tracking. 106 */ 107 public Relation(Workspace workspace) { 108 super(workspace); 109 _elementName = "relation"; 110 } 111 112 /** Construct a relation in the given workspace with the given name. 113 * If the workspace argument is null, use the default workspace. 114 * If the name argument is null, then the name is set to the empty string. 115 * The object is added to the workspace directory. 116 * Increment the version of the workspace. 117 * @param workspace Workspace for synchronization and version tracking 118 * @param name Name of this object. 119 * @exception IllegalActionException If the name has a period. 120 */ 121 public Relation(Workspace workspace, String name) 122 throws IllegalActionException { 123 super(workspace, name); 124 _elementName = "relation"; 125 } 126 127 /////////////////////////////////////////////////////////////////// 128 //// public methods //// 129 130 /** Clone the object into the specified workspace. The new object is 131 * <i>not</i> added to the directory of that workspace (you must do this 132 * yourself if you want it there). 133 * The result is a new relation with no links and no container. 134 * @param workspace The workspace for the cloned object. 135 * @exception CloneNotSupportedException If one of the attributes cannot 136 * be cloned. 137 * @return A new Relation. 138 */ 139 @Override 140 public Object clone(Workspace workspace) throws CloneNotSupportedException { 141 Relation newObject = (Relation) super.clone(workspace); 142 newObject._linkList = new CrossRefList(newObject); 143 return newObject; 144 } 145 146 /** Link this relation with another relation. The relation is required 147 * to be at the same level of the hierarchy as this relation. 148 * That is, level-crossing links are not allowed. 149 * If the specified relation is already linked to this one, 150 * do nothing. 151 * In derived classes, the relation may be required to be an 152 * instance of a particular subclass of Relation (this is checked 153 * by the _checkRelation() protected method). 154 * This method is write-synchronized on the workspace and increments 155 * its version number. 156 * @param relation The relation to link to this relation. 157 * @exception IllegalActionException If the link would cross levels of 158 * the hierarchy, or the relation is incompatible, 159 * or this relation has no container, or this relation is not in the 160 * same workspace as the relation. 161 */ 162 public void link(Relation relation) throws IllegalActionException { 163 if (relation != null && _workspace != relation.workspace()) { 164 throw new IllegalActionException(this, relation, 165 "Cannot link because workspaces are different."); 166 } 167 168 try { 169 _workspace.getWriteAccess(); 170 171 if (relation != null) { 172 _checkRelation(relation, true); 173 174 if (!_linkList.isLinked(relation)) { 175 _linkList.link(relation._linkList); 176 } 177 } else { 178 _linkList.link(null); 179 } 180 } finally { 181 _workspace.doneWriting(); 182 } 183 } 184 185 /** Return a list of the objects directly linked to this 186 * relation (ports and relations). 187 * Note that a port may appear more than 188 * once if more than one link to it has been established, 189 * but a relation will appear only once. There is no significance 190 * to multiple links between the same relations. 191 * This method is read-synchronized on the workspace. 192 * @return A list of Port and Relation objects. 193 */ 194 public List linkedObjectsList() { 195 try { 196 _workspace.getReadAccess(); 197 198 // NOTE: This should probably be cached. 199 // Unfortunately, CrossRefList returns an enumeration only. 200 // Use it to construct a list. 201 LinkedList result = new LinkedList(); 202 Enumeration links = _linkList.getContainers(); 203 204 while (links.hasMoreElements()) { 205 Object next = links.nextElement(); 206 result.add(next); 207 } 208 209 return result; 210 } finally { 211 _workspace.doneReading(); 212 } 213 } 214 215 /** List the ports linked to any relation in this relation's 216 * group. Note that a port may appear more than 217 * once if more than one link to it has been established. 218 * The order in which ports are listed has no significance. 219 * This method is read-synchronized on the workspace. 220 * @return A list of Port objects. 221 */ 222 public List linkedPortList() { 223 try { 224 _workspace.getReadAccess(); 225 226 // Unfortunately, CrossRefList returns an enumeration only. 227 // Use it to construct a list. 228 LinkedList result = new LinkedList(); 229 Enumeration links = _linkList.getContainers(); 230 231 Set<Relation> exceptRelations = new HashSet<Relation>(); 232 exceptRelations.add(this); 233 234 while (links.hasMoreElements()) { 235 Object next = links.nextElement(); 236 237 if (next instanceof Port) { 238 result.add(next); 239 } else { 240 // Must be another relation. 241 result.addAll(((Relation) next)._linkedPortList(null, 242 exceptRelations)); 243 } 244 } 245 246 return result; 247 } finally { 248 _workspace.doneReading(); 249 } 250 } 251 252 /** List the ports linked to any relation in this relation's 253 * group except the specified port. 254 * Note that a port may appear more than 255 * once if more than on link to it has been established. 256 * The order in which ports are listed has no significance. 257 * This method is read-synchronized on the workspace. 258 * @param except Port to exclude from the list. 259 * @return A list of Port objects. 260 */ 261 public List linkedPortList(Port except) { 262 // This works by constructing a linked list and then returning it. 263 try { 264 _workspace.getReadAccess(); 265 266 LinkedList result = new LinkedList(); 267 Enumeration links = _linkList.getContainers(); 268 269 Set<Relation> exceptRelations = new HashSet<Relation>(); 270 exceptRelations.add(this); 271 272 while (links.hasMoreElements()) { 273 Object link = links.nextElement(); 274 275 if (link instanceof Port) { 276 if (link != except) { 277 result.add(link); 278 } 279 } else { 280 // Must be another relation. 281 result.addAll(((Relation) link)._linkedPortList(except, 282 exceptRelations)); 283 } 284 } 285 286 return result; 287 } finally { 288 _workspace.doneReading(); 289 } 290 } 291 292 /** Enumerate the linked ports. Note that a port may appear more than 293 * once if more than one link to it has been established. 294 * This method is read-synchronized on the workspace. 295 * @deprecated Use linkedPortList() instead. 296 * @return An Enumeration of Port objects. 297 */ 298 @Deprecated 299 public Enumeration linkedPorts() { 300 return Collections.enumeration(linkedPortList()); 301 } 302 303 /** Enumerate the linked ports except the specified port. 304 * Note that a port may appear more than 305 * once if more than on link to it has been established. 306 * This method is read-synchronized on the workspace. 307 * @param except Port to exclude from the enumeration. 308 * @return An Enumeration of Port objects. 309 * @deprecated Use linkedPortList(Port) instead. 310 */ 311 @Deprecated 312 public Enumeration linkedPorts(Port except) { 313 return Collections.enumeration(linkedPortList(except)); 314 } 315 316 /** Return the number of links to ports, either directly 317 * or indirectly via other relations in the relation 318 * group. This is the size 319 * of the list returned by linkedPortList(). 320 * This method is read-synchronized on the workspace. 321 * @return The number of links. 322 * @see #linkedPortList() 323 */ 324 public int numLinks() { 325 return linkedPortList().size(); 326 } 327 328 /** Return the list of relations in the relation group containing 329 * this relation. 330 * The relation group includes this relation, all relations 331 * directly linked to it, all relations directly linked to 332 * those, etc. That is, it is a maximal set of linked 333 * relations. There is no significance to the order of the 334 * returned list, but the returned value is a List rather than 335 * a Set to facilitate testing. 336 * @return The relation group. 337 */ 338 public List relationGroupList() { 339 LinkedList result = new LinkedList(); 340 _relationGroup(result); 341 return result; 342 } 343 344 /** Unlink the specified Relation. If the Relation 345 * is not linked to this relation, do nothing. 346 * This method is write-synchronized on the 347 * workspace and increments its version number. 348 * @param relation The relation to unlink. 349 */ 350 public void unlink(Relation relation) { 351 try { 352 _workspace.getWriteAccess(); 353 _linkList.unlink(relation); 354 } finally { 355 _workspace.doneWriting(); 356 } 357 } 358 359 /** Unlink all ports and relations that are directly linked 360 * to this relation. 361 * This method is write-synchronized on the workspace and increments 362 * its version number. 363 */ 364 public void unlinkAll() { 365 try { 366 _workspace.getWriteAccess(); 367 368 // NOTE: Do not just use _linkList.unlinkAll() because then the 369 // containers of ports are not notified of the change. 370 // Also, have to first copy the link references, then remove 371 // them, to avoid a corrupted enumeration exception. 372 int size = _linkList.size(); 373 Object[] linkedObjectsArray = new Object[size]; 374 int i = 0; 375 Enumeration links = _linkList.getContainers(); 376 377 while (links.hasMoreElements()) { 378 Object linkedObject = links.nextElement(); 379 linkedObjectsArray[i++] = linkedObject; 380 } 381 382 // NOTE: It would be better if there were a 383 // Linkable interface so that these instanceof 384 // tests would not be needed. 385 for (i = 0; i < size; i++) { 386 if (linkedObjectsArray[i] instanceof Port) { 387 ((Port) linkedObjectsArray[i]).unlink(this); 388 } else { 389 ((Relation) linkedObjectsArray[i]).unlink(this); 390 } 391 } 392 } finally { 393 _workspace.doneWriting(); 394 } 395 } 396 397 /////////////////////////////////////////////////////////////////// 398 //// protected methods //// 399 400 /** Throw an exception if the specified port cannot be linked to this 401 * relation. In this base class, the exception is not thrown, but 402 * in derived classes, it might be, for example if the relation cannot 403 * support any more links, or the port is not an instance of an 404 * appropriate subclass of Port. 405 * @param port The port to link to. 406 * @exception IllegalActionException Not thrown in this base class. 407 */ 408 protected void _checkPort(Port port) throws IllegalActionException { 409 } 410 411 /** Check that this relation is compatible with the specified relation. 412 * In this base class, this method calls the corresponding check method 413 * of the specified relation, and if that returns, then it returns. 414 * Derived classes should override this method to check validity 415 * of the specified relation, and then call super._checkRelation(). 416 * @param relation The relation to link to. 417 * @param symmetric If true, the call _checkRelation on the specified 418 * relation with this as an argument. 419 * @exception IllegalActionException If this relation has no container, 420 * or if this relation is not an acceptable relation for the specified 421 * relation. 422 */ 423 protected void _checkRelation(Relation relation, boolean symmetric) 424 throws IllegalActionException { 425 if (relation != null && symmetric) { 426 // Throw an exception if this relation is not of an acceptable 427 // class for the specified relation. 428 relation._checkRelation(this, false); 429 } 430 } 431 432 /** Return a description of the object. The level of detail depends 433 * on the argument, which is an or-ing of the static final constants 434 * defined in the NamedObj class. Lines are indented according to 435 * to the level argument using the protected method _getIndentPrefix(). 436 * Zero, one or two brackets can be specified to surround the returned 437 * description. If one is specified it is the the leading bracket. 438 * This is used by derived classes that will append to the description. 439 * Those derived classes are responsible for the closing bracket. 440 * An argument other than 0, 1, or 2 is taken to be equivalent to 0. 441 * This method is read-synchronized on the workspace. 442 * @param detail The level of detail. 443 * @param indent The amount of indenting. 444 * @param bracket The number of surrounding brackets (0, 1, or 2). 445 * @return A description of the object. 446 * @exception IllegalActionException If thrown while getting the 447 * description of subcomponents. 448 */ 449 @Override 450 protected String _description(int detail, int indent, int bracket) 451 throws IllegalActionException { 452 try { 453 _workspace.getReadAccess(); 454 455 StringBuffer result = new StringBuffer(); 456 457 if (bracket == 1 || bracket == 2) { 458 result.append(super._description(detail, indent, 1)); 459 } else { 460 result.append(super._description(detail, indent, 0)); 461 } 462 463 if ((detail & LINKS) != 0) { 464 if (result.toString().trim().length() > 0) { 465 result.append(" "); 466 } 467 468 // To avoid infinite loop, turn off the LINKS flag 469 // when querying the Ports. 470 detail &= ~LINKS; 471 result.append("links {\n"); 472 473 Enumeration links = _linkList.getContainers(); 474 475 while (links.hasMoreElements()) { 476 Object object = links.nextElement(); 477 478 if (object instanceof Port) { 479 result.append(((Port) object)._description(detail, 480 indent + 1, 2) + "\n"); 481 } else { 482 result.append(((Relation) object)._description(detail, 483 indent + 1, 2) + "\n"); 484 } 485 } 486 487 result.append(_getIndentPrefix(indent) + "}"); 488 } 489 490 if (bracket == 2) { 491 result.append("}"); 492 } 493 494 return result.toString(); 495 } finally { 496 _workspace.doneReading(); 497 } 498 } 499 500 /** Get a relation with the specified name in the specified container. 501 * The returned object is assured of being an 502 * instance of the same class as this object. 503 * @param relativeName The name relative to the container. 504 * @param container The container expected to contain the object, which 505 * must be an instance of CompositeEntity. 506 * @return An object of the same class as this object, or null if 507 * there is none. 508 * @exception IllegalActionException If the object exists 509 * and has the wrong class, or if the specified container is not 510 * an instance of CompositeEntity. 511 */ 512 @Override 513 protected NamedObj _getContainedObject(NamedObj container, 514 String relativeName) throws IllegalActionException { 515 if (!(container instanceof CompositeEntity)) { 516 throw new InternalErrorException("Expected " 517 + container.getFullName() 518 + " to be an instance of ptolemy.kernel.CompositeEntity, " 519 + "but it is " + container.getClass().getName()); 520 } 521 522 Relation candidate = ((CompositeEntity) container) 523 .getRelation(relativeName); 524 525 if (candidate != null && !getClass().isInstance(candidate)) { 526 throw new IllegalActionException(this, 527 "Expected " + candidate.getFullName() 528 + " to be an instance of " + getClass().getName() 529 + ", but it is " + candidate.getClass().getName()); 530 } 531 532 return candidate; 533 } 534 535 /////////////////////////////////////////////////////////////////// 536 //// protected variables //// 537 538 /** The list of Ports and Relations that are linked to this Relation. */ 539 protected CrossRefList _linkList = new CrossRefList(this); 540 541 /////////////////////////////////////////////////////////////////// 542 //// private methods //// 543 544 /** List the linked ports except the <i>exceptPort</i> and 545 * those linked via the relations in the <i>exceptRelations</i> 546 * list. This relation is 547 * added to the <i>exceptRelations</i> list before returning. 548 * Note that a port may appear more than 549 * once if more than one link to it has been established. 550 * This method is not read-synchronized on the workspace, 551 * so the caller is expected to be. 552 * @param exceptPort A port to exclude, or null to not 553 * specify one. 554 * @param exceptRelations Set of relations to exclude. 555 * @return A list of Port objects. 556 */ 557 private List _linkedPortList(Port exceptPort, Set exceptRelations) { 558 // This works by constructing a linked list and then returning it. 559 LinkedList result = new LinkedList(); 560 561 if (exceptRelations.contains(this)) { 562 return result; 563 } 564 565 // Prevent listing the ports connected to this relation again. 566 exceptRelations.add(this); 567 568 Enumeration links = _linkList.getContainers(); 569 570 while (links.hasMoreElements()) { 571 Object link = links.nextElement(); 572 573 if (link instanceof Port) { 574 if (link != exceptPort) { 575 result.add(link); 576 } 577 } else { 578 // Link must be to a relation. 579 Relation relation = (Relation) link; 580 581 if (!exceptRelations.contains(relation)) { 582 result.addAll(relation._linkedPortList(exceptPort, 583 exceptRelations)); 584 } 585 } 586 } 587 588 return result; 589 } 590 591 /** Append to the specified list all relations in the relation 592 * group with this one that are not already on the list. 593 * The relation group includes this relation, all relations 594 * directly linked to it, all relations directly linked to 595 * those, etc. 596 * @param list The list to append to. 597 */ 598 private void _relationGroup(List list) { 599 if (!list.contains(this)) { 600 list.add(this); 601 602 Enumeration links = _linkList.getContainers(); 603 604 while (links.hasMoreElements()) { 605 Object link = links.nextElement(); 606 607 if (link instanceof Relation) { 608 ((Relation) link)._relationGroup(list); 609 } 610 } 611 } 612 } 613}