001/* A named object that can be either a class or an instance. 002 003 Copyright (c) 2003-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.io.IOException; 031import java.io.Writer; 032import java.lang.ref.WeakReference; 033import java.util.Collections; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.ListIterator; 037 038import ptolemy.kernel.util.IllegalActionException; 039import ptolemy.kernel.util.Instantiable; 040import ptolemy.kernel.util.NameDuplicationException; 041import ptolemy.kernel.util.NamedObj; 042import ptolemy.kernel.util.Workspace; 043import ptolemy.util.StringUtilities; 044 045/////////////////////////////////////////////////////////////////// 046//// InstantiableNamedObj 047 048/** 049 An InstantiableNamedObj is a named object that can be either a class 050 definition or an instance. If it is a class definition, then "instances" of 051 that class definition can be created by the instantiate() method. Those 052 instances are called the "children" of this "parent." Changes 053 to the parent propagate automatically to the children as described 054 in the {@link Instantiable} interface. 055 <p> 056 Note that the {@link #instantiate(NamedObj, String)} permits instantiating 057 an object into a workspace that is different from the one associated with 058 this object. This means that some care must be exercised when propagating 059 changes from a parent to a child, since they may be in different workspaces. 060 Suppose for example that the change that has to propagate is made via a 061 change request. Although it may be a safe time to execute a change request 062 in the parent, it is not necessarily a safe time to execute a change request 063 in the child. Classes that restrict these safe times should override 064 the propagateExistence(), propagateValue(), and propagateValues() methods 065 to ensure that the destinations of the propagation are in a state that 066 they can accept changes. 067 068 @author Edward A. Lee 069 @version $Id$ 070 @since Ptolemy II 4.0 071 @see Instantiable 072 @Pt.ProposedRating Green (eal) 073 @Pt.AcceptedRating Green (neuendor) 074 */ 075public class InstantiableNamedObj extends NamedObj implements Instantiable { 076 /** Construct an object in the default workspace with an empty string 077 * as its name. 078 * The object is added to the workspace directory. 079 * Increment the version number of the workspace. 080 */ 081 public InstantiableNamedObj() { 082 super(); 083 } 084 085 /** Construct an object in the default workspace with the given name. 086 * If the name argument 087 * is null, then the name is set to the empty string. 088 * The object is added to the workspace directory. 089 * Increment the version number of the workspace. 090 * @param name The name of this object. 091 * @exception IllegalActionException If the name has a period. 092 */ 093 public InstantiableNamedObj(String name) throws IllegalActionException { 094 super(name); 095 } 096 097 /** Construct an object in the given workspace with an empty string 098 * as a name. 099 * If the workspace argument is null, use the default workspace. 100 * The object is added to the workspace directory. 101 * Increment the version of the workspace. 102 * @param workspace The workspace for synchronization and version tracking. 103 */ 104 public InstantiableNamedObj(Workspace workspace) { 105 super(workspace); 106 } 107 108 /** Construct an object in the given workspace with the given name. 109 * If the workspace argument is null, use the default workspace. 110 * If the name argument 111 * is null, then the name is set to the empty string. 112 * The object is added to the workspace directory. 113 * Increment the version of the workspace. 114 * @param workspace The workspace for synchronization and version tracking. 115 * @param name The name of this object. 116 * @exception IllegalActionException If the name has a period. 117 */ 118 public InstantiableNamedObj(Workspace workspace, String name) 119 throws IllegalActionException { 120 super(workspace, name); 121 } 122 123 /////////////////////////////////////////////////////////////////// 124 //// public methods //// 125 126 /** Clone the object into the specified workspace. The new object is 127 * <i>not</i> added to the directory of that workspace (you must do this 128 * yourself if you want it there). The result is a new instance of 129 * InstantiableNamedObj that is a child of the parent of this object, 130 * if this object has a parent. The new instance has no children. 131 * This method gets read access on the workspace associated with 132 * this object. 133 * @param workspace The workspace for the cloned object. 134 * @exception CloneNotSupportedException If one of the attributes 135 * cannot be cloned. 136 * @return A new instance of InstantiableNamedObj. 137 */ 138 @Override 139 public Object clone(Workspace workspace) throws CloneNotSupportedException { 140 try { 141 workspace().getReadAccess(); 142 143 InstantiableNamedObj newObject = (InstantiableNamedObj) super.clone( 144 workspace); 145 146 // The new object does not have any other objects deferring 147 // their MoML definitions to it, so we have to reset this. 148 newObject._children = null; 149 150 // Set the parent using _setParent() rather than the default 151 // clone to get the side effects. 152 newObject._parent = null; 153 154 // NOTE: This used to do the following, 155 // but we now rely on _adjustDeferrals() 156 // to fix the parent relationships. 157 158 /* 159 if (_parent != null) { 160 try { 161 newObject._setParent(_parent); 162 } catch (IllegalActionException ex) { 163 throw new InternalErrorException(ex); 164 } 165 } 166 */ 167 return newObject; 168 } finally { 169 workspace().doneReading(); 170 } 171 } 172 173 /** Write a MoML description of this object with the specified 174 * indentation depth and with the specified name substituting 175 * for the name of this object. The description has one of two 176 * forms, depending on whether this is a class definition. 177 * If it is, then the exported MoML looks like this: 178 * <pre> 179 * <class name="<i>name</i>" extends="<i>classname</i> source="<i>source</i>"> 180 * <i>body, determined by _exportMoMLContents()</i> 181 * </class> 182 * </pre> 183 * Otherwise, the exported MoML is that generated by the 184 * superclass method that this overrides. 185 * <p> 186 * If this object has no container and the depth argument is zero, 187 * then this method prepends XML file header information, which is: 188 * <pre> 189 * <?xml version="1.0" standalone="no"?> 190 * <!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN" 191 * "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"> 192 * </pre> 193 * In the above, "entity" may be replaced by "property" or 194 * "port" if what is being exported is an attribute or a port. 195 * <p> 196 * The text that is written is indented according to the specified 197 * depth, with each line (including the last one) 198 * terminated with a newline. 199 * Derived classes can override this method to change the MoML 200 * description of an object. They can override the protected 201 * method _exportMoMLContents() if they need to only change which 202 * contents are described. 203 * <p> 204 * If this object is not persistent, or if there is no MoML 205 * description of this object, or if this object is implied 206 * by a parent-child relationship that less than <i>depth</i> 207 * levels up in the containment hierarchy and it has not 208 * been overridden, then write nothing. 209 * @param output The output stream to write to. 210 * @param depth The depth in the hierarchy, to determine indenting. 211 * @param name The name to use in the exported MoML. 212 * @exception IOException If an I/O error occurs. 213 * @see ptolemy.kernel.util.MoMLExportable 214 */ 215 @Override 216 public void exportMoML(Writer output, int depth, String name) 217 throws IOException { 218 219 if (!isClassDefinition()) { 220 super.exportMoML(output, depth, name); 221 return; 222 } 223 224 // escape any < character in name. unescapeForXML occurs in 225 // NamedObj.setName(String) 226 // If we don't escape the name here then we generate 227 // MoML that is not valid XML. See MoMLParser-34.0 in 228 // moml/test/MoMLParser.tcl 229 name = StringUtilities.escapeForXML(name); 230 231 // If the object is not persistent, and we are not 232 // at level 0, do nothing. 233 if (_isMoMLSuppressed(depth)) { 234 return; 235 } 236 237 if (depth == 0 && getContainer() == null) { 238 // No container, and this is a top level moml element. 239 // Generate header information. 240 // NOTE: Currently, there is only one class designation, 241 // and it always applies to an Entity. Attributes that 242 // are classes are not yet supported. When they are, 243 // then "class" below may need to replaced with something 244 // else. 245 output.write("<?xml version=\"1.0\" standalone=\"no\"?>\n" 246 + "<!DOCTYPE class PUBLIC " 247 + "\"-//UC Berkeley//DTD MoML 1//EN\"\n" 248 + " \"http://ptolemy.eecs.berkeley.edu" 249 + "/xml/dtd/MoML_1.dtd\">\n"); 250 } 251 252 output.write(_getIndentPrefix(depth) + "<class name=\"" + name 253 + "\" extends=\"" + getClassName() + "\""); 254 255 if (getSource() != null) { 256 output.write(" source=\"" + getSource() + "\">\n"); 257 } else { 258 output.write(">\n"); 259 } 260 261 _exportMoMLContents(output, depth + 1); 262 263 // Write the close of the element. 264 output.write(_getIndentPrefix(depth) + "</class>\n"); 265 } 266 267 /** Get a list of weak references to instances of Instantiable 268 * that are children of this object. This method 269 * may return null or an empty list to indicate that there are 270 * no children. 271 * @return An unmodifiable list of instances of 272 * java.lang.ref.WeakReference that refer to 273 * instances of Instantiable or null if this object 274 * has no children. 275 * @see Instantiable 276 * @see java.lang.ref.WeakReference 277 */ 278 @Override 279 public List getChildren() { 280 if (_children == null) { 281 return null; 282 } 283 284 return Collections.unmodifiableList(_children); 285 } 286 287 /** Get the MoML element name. If this is a class definition, then 288 * return "class". Otherwise, defer to the base class. 289 * @return The MoML element name for this object. 290 * @see ptolemy.kernel.util.MoMLExportable 291 */ 292 @Override 293 public String getElementName() { 294 if (isClassDefinition()) { 295 return "class"; 296 } else { 297 return super.getElementName(); 298 } 299 } 300 301 /** Return the parent of this object, or null if there is none. 302 * @return The parent of this object, or null if there is none. 303 * @see #_setParent(Instantiable) 304 * @see Instantiable 305 */ 306 @Override 307 public Instantiable getParent() { 308 return _parent; 309 } 310 311 /** Return a list of prototypes for this object. The list is ordered 312 * so that more local prototypes are listed before more remote 313 * prototypes. Specifically, if this object has a parent, then the 314 * parent is listed first. If the container has a parent, and 315 * that parent contains an object whose name matches the name 316 * of this object, then that object is listed next. 317 * If the container of the container has a parent, and that parent 318 * (deeply) contains a prototype, then that prototype is listed next. 319 * And so on up the hierarchy. 320 * @return A list of prototypes for this object, each of which is 321 * assured of being an instance of the same (Java) class as this 322 * object, or an empty list if there are no prototypes. 323 * @exception IllegalActionException If a prototype with the right 324 * name but the wrong class is found. 325 * @see ptolemy.kernel.util.Derivable 326 */ 327 @Override 328 public List getPrototypeList() throws IllegalActionException { 329 List result = super.getPrototypeList(); 330 331 if (getParent() != null) { 332 result.add(0, getParent()); 333 } 334 335 return result; 336 } 337 338 /** Create an instance by (deeply) cloning this object and then adjusting 339 * the parent-child relationships between the clone and its parent. 340 * Specifically, the clone defers its definition to this object, 341 * which becomes its "parent." The "child" inherits all the objects 342 * contained by this object. If this object is a composite, then this 343 * method adjusts any parent-child relationships that are entirely 344 * contained within the child. That is, for any parent-child relationship 345 * that is entirely contained within this object (i.e., both the parent 346 * and the child are deeply contained by this object), a corresponding 347 * parent-child relationship is created within the clone such that 348 * both the parent and the child are entirely contained within 349 * the clone. 350 * <p> 351 * The new object is not a class definition by default (it is an 352 * "instance" rather than a "class"). To make it a class 353 * definition (a "subclass"), call {@link #setClassDefinition(boolean)} 354 * with a <i>true</i> argument. 355 * <p> 356 * In this base class, the container argument is ignored except that 357 * it provides the workspace into which to clone this object. Derived 358 * classes with setContainer() methods are responsible for overriding 359 * this and calling setContainer(). 360 * <p> 361 * Note that the workspace for the instantiated object can be different 362 * from the workspace for this object. This means that propagation of 363 * changes from a parent to a child may not be able to be safely 364 * performed in the child even when they are safely performed in the 365 * parent. Subclasses that restrict when changes are performed are 366 * therefore required to check whether the workspaces are the same 367 * before propagating changes. 368 * @param container The container for the instance, or null 369 * to instantiate it at the top level. Note that this base class 370 * does not set the container. It uses the container argument to 371 * get the workspace. Derived classes are responsible for 372 * setting the container. 373 * @param name The name for the instance. 374 * @return A new instance that is a clone of this object 375 * with adjusted parent-child relationships. 376 * @exception CloneNotSupportedException If this object 377 * cannot be cloned. 378 * @exception IllegalActionException If this object is not a 379 * class definition or the proposed container is not acceptable. 380 * @exception NameDuplicationException If the name collides with 381 * an object already in the container. 382 * @see #setClassDefinition(boolean) 383 * @see Instantiable 384 */ 385 @Override 386 public Instantiable instantiate(NamedObj container, String name) 387 throws CloneNotSupportedException, IllegalActionException, 388 NameDuplicationException { 389 if (!isClassDefinition()) { 390 throw new IllegalActionException(this, 391 "Cannot instantiate an object that is not a " 392 + "class definition"); 393 } 394 395 // Use the workspace of the container, if there is one, 396 // or the workspace of this object, if there isn't. 397 Workspace workspace = workspace(); 398 399 if (container != null) { 400 workspace = container.workspace(); 401 } 402 403 InstantiableNamedObj clone = (InstantiableNamedObj) clone(workspace); 404 405 // The cloning process results an object that defers change 406 // requests. By default, we do not want to defer change 407 // requests, but more importantly, we need to execute 408 // any change requests that may have been queued 409 // during cloning. The following call does that. 410 clone.setDeferringChangeRequests(false); 411 412 // Set the name before the container to not get 413 // spurious name conflicts. 414 clone.setName(name); 415 clone._setParent(this); 416 clone.setClassDefinition(false); 417 clone.setClassName(getFullName()); 418 419 // Mark the contents of the instantiated object as being derived. 420 clone._markContentsDerived(0); 421 422 return clone; 423 } 424 425 /** Return true if this object is a class definition, which means that 426 * it can be instantiated. 427 * @return True if this object is a class definition. 428 * @see #setClassDefinition(boolean) 429 * @see Instantiable 430 */ 431 @Override 432 public final boolean isClassDefinition() { 433 return _isClassDefinition; 434 } 435 436 /** Return true if this object is a class definition or is within 437 * a class definition, which means that 438 * any container above it in the hierarchy is 439 * a class definition. 440 * @return True if this object is within a class definition. 441 * @see #setClassDefinition(boolean) 442 * @see Instantiable 443 */ 444 public final boolean isWithinClassDefinition() { 445 if (_isClassDefinition) { 446 return true; 447 } else { 448 NamedObj container = getContainer(); 449 while (container != null) { 450 if (container instanceof InstantiableNamedObj) { 451 if (((InstantiableNamedObj) container)._isClassDefinition) { 452 return true; 453 } 454 } 455 container = container.getContainer(); 456 } 457 return false; 458 } 459 } 460 461 /** Specify whether this object is a class definition. 462 * This method is write synchronized on the workspace. 463 * @param isClass True to make this object a class definition, false 464 * to make it an instance. 465 * @exception IllegalActionException If there are subclasses and/or 466 * instances and the argument is false. 467 * @see #isClassDefinition() 468 * @see Instantiable 469 */ 470 public void setClassDefinition(boolean isClass) 471 throws IllegalActionException { 472 workspace().getWriteAccess(); 473 474 try { 475 if (!isClass && _isClassDefinition && getChildren() != null 476 && getChildren().size() > 0) { 477 throw new IllegalActionException(this, 478 "Cannot change from a class to an instance because" 479 + " there are subclasses and/or instances."); 480 } 481 482 if (_isClassDefinition != isClass) { 483 // Changing the class status is a hierarchy event that 484 // contained objects need to be notified of. 485 _notifyHierarchyListenersBeforeChange(); 486 _isClassDefinition = isClass; 487 } 488 } finally { 489 workspace().doneWriting(); 490 _notifyHierarchyListenersAfterChange(); 491 } 492 } 493 494 /** Specify the parent for this object. This method should be called 495 * to make this object either an instance or a subclass of 496 * the other object. When generating 497 * a MoML description of this object, instead of giving a detailed 498 * description, this object will henceforth refer to the 499 * specified other object. The name of that other object goes 500 * into the "class" or "extends" attribute of the MoML element 501 * defining this object (depending on whether this is an instance 502 * or a subclass). This method is called when this object 503 * is constructed using the {@link #instantiate(NamedObj, String)} 504 * method. This method is write synchronized on 505 * the workspace because it modifies the object that is the 506 * argument to refer back to this one. 507 * <p> 508 * Note that a parent references a child via a weak reference. 509 * This means that the parent will not prevent the child from 510 * being garbage collected. However, as long as the child has 511 * not been garbage collected, changes to the parent will 512 * propagate to the child even if there are no other live 513 * references to the child. If there are a large number of 514 * such dangling children, this could create performance 515 * problems when making changes to the parent. 516 * @param parent The parent, or null to specify that there is 517 * no parent. 518 * @exception IllegalActionException If the parent is not an 519 * instance of InstantiableNamedObj. 520 * @see #exportMoML(Writer, int) 521 * @see #getParent() 522 * @see Instantiable 523 */ 524 protected void _setParent(Instantiable parent) 525 throws IllegalActionException { 526 if (parent != null && !(parent instanceof InstantiableNamedObj)) { 527 throw new IllegalActionException(this, 528 "Parent of an InstantiableNamedObj must also " 529 + "be an InstantiableNamedObj."); 530 } 531 532 try { 533 _workspace.getWriteAccess(); 534 535 if (_parent != null) { 536 // Previously existing deferral. 537 // Remove it. 538 // NOTE: If WeakReference overrides equal(), 539 // then this could probably be done more simply. 540 List deferredFromList = _parent._children; 541 542 if (deferredFromList != null) { 543 // Removing a previous reference. 544 // Note that this is a list of weak references, so 545 // it is not sufficient to just remove this! 546 ListIterator references = deferredFromList.listIterator(); 547 548 while (references.hasNext()) { 549 WeakReference reference = (WeakReference) references 550 .next(); 551 552 if (reference == null || reference.get() == this) { 553 references.remove(); 554 } 555 } 556 } 557 } 558 559 _parent = (InstantiableNamedObj) parent; 560 561 if (_parent != null) { 562 if (_parent._children == null) { 563 _parent._children = new LinkedList(); 564 } 565 566 // NOTE: These need to be weak references. 567 _parent._children.add(new WeakReference(this)); 568 } 569 } finally { 570 _workspace.doneWriting(); 571 } 572 } 573 574 /////////////////////////////////////////////////////////////////// 575 //// private variables //// 576 577 /** List of weak references to children for which this object 578 * is the parent. 579 */ 580 private List _children; 581 582 /** Parent to which this object defers its definition to, or 583 * null if there is none. 584 */ 585 private InstantiableNamedObj _parent; 586 587 /** Indicator of whether this is a class definition. */ 588 private boolean _isClassDefinition; 589}