001/* A hierarchical library of components specified in MoML. 002 003 Copyright (c) 1998-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.moml; 029 030import java.io.IOException; 031import java.io.StringWriter; 032import java.io.Writer; 033import java.net.URL; 034import java.util.Iterator; 035import java.util.List; 036 037import ptolemy.actor.Librariable; 038import ptolemy.kernel.ComponentEntity; 039import ptolemy.kernel.CompositeEntity; 040import ptolemy.kernel.util.Attribute; 041import ptolemy.kernel.util.IllegalActionException; 042import ptolemy.kernel.util.InternalErrorException; 043import ptolemy.kernel.util.InvalidStateException; 044import ptolemy.kernel.util.KernelException; 045import ptolemy.kernel.util.LazyComposite; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.NamedObj; 048import ptolemy.kernel.util.Workspace; 049import ptolemy.util.MessageHandler; 050 051/////////////////////////////////////////////////////////////////// 052//// EntityLibrary 053 054/** 055 This class provides a hierarchical library of components specified 056 in MoML. The contents are typically specified via the configure() 057 method, which is passed MoML code. The MoML is evaluated 058 lazily; i.e. it is not actually evaluated until there is a request 059 for its contents, via a call to getEntity(), numEntities(), 060 entityList(), or any related method. You can also force evaluation 061 of the MoML by calling populate(). When you export MoML for this 062 object, the MoML description of the contents is wrapped in a configure 063 element. This object contains an attribute with name "_libraryMarker", 064 which marks it as a library. This is used by the library browser in 065 vergil to know to expand the composite entity. 066 <p> 067 The contents of the library can be entities, ports, relations, or 068 attributes. I.e., it can contain anything contained by a CompositeEntity. 069 An attempt to access any of these will trigger populating the library. 070 <p> 071 The configure method can be given a URL or MoML text or both. 072 If it is given MoML text, that text will normally be wrapped in a 073 processing instruction, as follows: 074 <pre> 075 <?moml 076 <group>[ 077 ... <i>MoML elements giving library contents</i> ... 078 </group> 079 ?> 080 </pre> 081 The processing instruction, which is enclosed in "<?" and "?>" 082 prevents premature evaluation of the MoML. The processing instruction 083 has a <i>target</i>, "moml", which specifies that it contains MoML code. 084 The keyword "moml" in the processing instruction must 085 be exactly as above, or the entire processing instruction will 086 be ignored. The populate() method 087 strips off the processing instruction and evaluates the MoML elements. 088 The group element allows the library contents to be given as a set 089 of elements (the MoML parser requires that there always be a single 090 top-level element, which in this case will be the group element). 091 <p> 092 One subtlety in using this class arises because of a problem typical 093 of lazy evaluation. A number of exceptions may be thrown because of 094 errors in the MoML code when the MoML code is evaluated. However, 095 since that code is evaluated lazily, it is evaluated in a context 096 where these exceptions are not expected. There is no completely 097 clean solution to this problem; our solution is to translate all 098 exceptions to runtime exceptions in the populate() method. 099 This method, therefore, violates the condition for using runtime 100 exceptions in that the condition that causes these exceptions to 101 be thrown is not a testable precondition. 102 <p> 103 A second subtlety involves cloning. When this class is cloned, 104 if the configure MoML text has not yet been evaluated, then the clone 105 is created with the same (unevaluated) MoML text, rather than being 106 populated with the contents specified by that text. If the object 107 is cloned after being populated, the clone will also be populated. 108 <p> 109 A third subtlety involves the doc element. Unfortunately, MoML 110 semantics define the doc element to replace any previously existing 111 doc elements. But to find out whether there is any previously 112 existing doc element, the MoML parser calls getAttribute, which 113 has the effect of populating the library. Thus, doc elements 114 should go inside the group, not outside. For example, the 115 following organization results in no deferred evaluation: 116 <pre> 117 <entity name="director library" class="ptolemy.moml.EntityLibrary"> 118 <doc>default director library</doc> 119 <configure> 120 <?moml 121 <group> 122 ... 123 </group> 124 ?> 125 </configure> 126 </entity> 127 </pre> 128 The following, by contrast, is OK: 129 <pre> 130 <entity name="director library" class="ptolemy.moml.EntityLibrary"> 131 <configure> 132 <?moml 133 <group> 134 <doc>default director library</doc> 135 ... 136 </group> 137 ?> 138 </configure> 139 </entity> 140 </pre> 141 142 @author Edward A. Lee 143 @version $Id$ 144 @since Ptolemy II 1.0 145 @Pt.ProposedRating Red (eal) 146 @Pt.AcceptedRating Red (bilung) 147 */ 148 149// FIXME: Have to do ports and relations. Only done attributes and entities. 150public class EntityLibrary extends CompositeEntity 151 implements LazyComposite, Librariable { 152 /** Construct a library in the default workspace with no 153 * container and an empty string as its name. Add the library to the 154 * workspace directory. 155 * Increment the version number of the workspace. 156 */ 157 public EntityLibrary() { 158 super(); 159 160 try { 161 // NOTE: Used to call uniqueName() here to choose the name for the 162 // marker. This is a bad idea. This calls getEntity(), which 163 // triggers populate() on the library, defeating deferred 164 // evaluation. 165 new Attribute(this, "_libraryMarker"); 166 } catch (KernelException ex) { 167 throw new InternalErrorException(null, ex, null); 168 } 169 _populated = false; 170 } 171 172 /** Construct a library in the specified workspace with 173 * no container and an empty string as a name. You can then change 174 * the name with setName(). If the workspace argument is null, then 175 * use the default workspace. Add the actor to the workspace directory. 176 * Increment the version number of the workspace. 177 * @param workspace The workspace that will list the actor. 178 */ 179 public EntityLibrary(Workspace workspace) { 180 super(workspace); 181 182 try { 183 // NOTE: Used to call uniqueName() here to choose the name for the 184 // marker. This is a bad idea. This calls getEntity(), which 185 // triggers populate() on the library, defeating deferred 186 // evaluation. 187 new Attribute(this, "_libraryMarker"); 188 } catch (KernelException ex) { 189 throw new InternalErrorException(ex.toString()); 190 } 191 _populated = false; 192 } 193 194 /** Construct a library with the given container and name. 195 * @param container The container. 196 * @param name The name of this library. 197 * @exception IllegalActionException If the entity cannot be contained 198 * by the proposed container. 199 * @exception NameDuplicationException If the container already has an 200 * actor with this name. 201 */ 202 public EntityLibrary(CompositeEntity container, String name) 203 throws NameDuplicationException, IllegalActionException { 204 super(container, name); 205 206 // NOTE: Used to call uniqueName() here to choose the name for the 207 // marker. This is a bad idea. This calls getEntity(), which 208 // triggers populate() on the library, defeating deferred 209 // evaluation. 210 new Attribute(this, "_libraryMarker"); 211 _populated = false; 212 } 213 214 /////////////////////////////////////////////////////////////////// 215 //// public methods //// 216 217 /** Return a list of the attributes contained by this object. 218 * If there are no attributes, return an empty list. 219 * This overrides the base class 220 * to first populate the library, if necessary, by calling populate(). 221 * This method is read-synchronized on the workspace. 222 * @return An unmodifiable list of instances of Attribute. 223 */ 224 @Override 225 public List attributeList() { 226 populate(); 227 return super.attributeList(); 228 } 229 230 /** Return a list of the attributes contained by this object that 231 * are instances of the specified class. If there are no such 232 * instances, then return an empty list. 233 * This overrides the base class 234 * to first populate the library, if necessary, by calling populate(). 235 * This method is read-synchronized on the workspace. 236 * @param filter The class of attribute of interest. 237 * @return A list of instances of specified class. 238 */ 239 @Override 240 public List attributeList(Class filter) { 241 populate(); 242 return super.attributeList(filter); 243 } 244 245 /** Clone the library into the specified workspace. The new object is 246 * <i>not</i> added to the directory of that workspace (you must do this 247 * yourself if you want it there). If the library has not yet been 248 * populated, then the clone will also not have been populated. 249 * @param workspace The workspace for the cloned object. 250 * @exception CloneNotSupportedException If the library contains 251 * level crossing transitions so that its connections cannot be cloned, 252 * or if one of the attributes cannot be cloned. 253 * @return A new EntityLibrary. 254 */ 255 @Override 256 public Object clone(Workspace workspace) throws CloneNotSupportedException { 257 // To prevent populating during cloning, we set a flag. 258 _cloning = true; 259 260 try { 261 EntityLibrary result = (EntityLibrary) super.clone(workspace); 262 result._cloning = false; 263 return result; 264 } finally { 265 _cloning = false; 266 } 267 } 268 269 /** Specify the library contents by giving either a URL (the 270 * <i>source</i> argument), or by directly giving the MoML text 271 * (the <i>text</i> argument), or both. The MoML is evaluated 272 * when the populate() method is called. This occurs 273 * lazily, when there is a request for the contents of the library. 274 * @param base The base relative to which references within the input 275 * are found, or null if this is not known, or there is none. 276 * @param source The input source, which specifies a URL, or null 277 * if none. 278 * @param text Configuration information given as text, or null if 279 * none. 280 */ 281 @Override 282 public void configure(URL base, String source, String text) { 283 // NOTE: This may be called more than once, in which case we need 284 // to accumulate the changes that are specified. 285 if (_configureText != null) { 286 populate(); 287 } 288 _base = base; 289 _configureSource = source; 290 _configureText = text; 291 _configureDone = false; 292 } 293 294 /** List the contained class definitions in the order they were added 295 * (using their setContainer() method). 296 * The returned list is static in the sense 297 * that it is not affected by any subsequent additions or removals 298 * of entities. This overrides the base class 299 * to first populate the library, if necessary, by calling populate(). 300 * Note that this may result in a runtime exception being thrown 301 * (if there is an error evaluating the MoML). 302 * This method is read-synchronized on the workspace. 303 * @return An unmodifiable list of ComponentEntity objects. 304 */ 305 @Override 306 public List classDefinitionList() { 307 populate(); 308 return super.classDefinitionList(); 309 } 310 311 /** Return true if this object contains the specified object, 312 * directly or indirectly. That is, return true if the specified 313 * object is contained by an object that this contains, or by an 314 * object contained by an object contained by this, etc. 315 * This method ignores whether the entities report that they are 316 * atomic (see CompositeEntity), and always returns false if the entities 317 * are not in the same workspace. 318 * This method is read-synchronized on the workspace. 319 * @param inside The NamedObj that is searched for. 320 * @see ptolemy.kernel.CompositeEntity#isAtomic() 321 * @return True if this contains the argument, directly or indirectly. 322 */ 323 @Override 324 public boolean deepContains(NamedObj inside) { 325 // If this has not yet been populated, then it can't possibly contain 326 // the proposed object because the proposed object would not 327 // exist yet. Therefore, we do not need to populate. 328 // Note that this makes this method override completely 329 // unnecessary, but we keep it here anyway to record the fact. 330 // Note that if we do call populate here, then the library 331 // will be populated whenever setContainer() is called on this 332 // library, which means that deferred evaluation will not ever 333 // actually be deferred. 334 // populate(); 335 return super.deepContains(inside); 336 } 337 338 /** List the opaque entities that are directly or indirectly 339 * contained by this entity. The list will be empty if there 340 * are no such contained entities. This overrides the base class 341 * to first populate the library, if necessary, by calling populate(). 342 * Note that this may result in a runtime exception being thrown 343 * (if there is an error evaluating the MoML). 344 * This method is read-synchronized on the workspace. 345 * @return A list of opaque ComponentEntity objects. 346 */ 347 @Override 348 public List deepEntityList() { 349 populate(); 350 return super.deepEntityList(); 351 } 352 353 /** List the contained entities in the order they were added 354 * (using their setContainer() method). 355 * The returned list is static in the sense 356 * that it is not affected by any subsequent additions or removals 357 * of entities. This overrides the base class 358 * to first populate the library, if necessary, by calling populate(). 359 * Note that this may result in a runtime exception being thrown 360 * (if there is an error evaluating the MoML). 361 * This method is read-synchronized on the workspace. 362 * @return An unmodifiable list of ComponentEntity objects. 363 */ 364 @Override 365 public List entityList() { 366 populate(); 367 return super.entityList(); 368 } 369 370 /** Get the attribute with the given name. The name may be compound, 371 * with fields separated by periods, in which case the attribute 372 * returned is contained by a (deeply) contained attribute. 373 * This overrides the base class 374 * to first populate the library, if necessary, by calling populate(). 375 * This method is read-synchronized on the workspace. 376 * @param name The name of the desired attribute. 377 * @return The requested attribute if it is found, null otherwise. 378 */ 379 @Override 380 public Attribute getAttribute(String name) { 381 populate(); 382 return super.getAttribute(name); 383 } 384 385 /** Get a contained entity by name. The name may be compound, 386 * with fields separated by periods, in which case the entity 387 * returned is contained by a (deeply) contained entity. 388 * This overrides the base class 389 * to first populate the library, if necessary, by calling populate(). 390 * Note that this may result in a runtime exception being thrown 391 * (if there is an error evaluating the MoML). 392 * This method is read-synchronized on the workspace. 393 * @param name The name of the desired entity. 394 * @return An entity with the specified name, or null if none exists. 395 */ 396 @Override 397 public ComponentEntity getEntity(String name) { 398 populate(); 399 return super.getEntity(name); 400 } 401 402 /** Return the input source that was specified the last time the configure 403 * method was called. 404 * @return The string representation of the input URL, or null if the 405 * no source has been used to configure this object, or null if no 406 * external source need be used to configure this object. 407 */ 408 @Override 409 public String getConfigureSource() { 410 return _configureSource; 411 } 412 413 /** Return the text string that represents the current configuration of 414 * this object. Note that any configuration that was previously 415 * specified using the source attribute need not be represented here 416 * as well. 417 * @return A configuration string, or null if no configuration 418 * has been used to configure this object, or null if no 419 * configuration string need be used to configure this object. 420 */ 421 @Override 422 public String getConfigureText() { 423 // This method gets called by MoMLParser upon encountering 424 // </configure> as a protection against exceptions. 425 // In particular, it is possible for propagation to fail 426 // (albeit unlikely), so MoMLParser makes a backup copy 427 // of the previous configure text to restore the value 428 // if an exception occurs. However, calling this method 429 // used to trigger populate(), which defeats the purpose of lazy 430 // evaluation. Hence, as an optimization, we check here 431 // for the situation where configure() has not been called 432 // and no attributes or entities have been manually added. 433 // In that situation, we return an empty string, and avoid 434 // calling populate(). 435 if (!_populated) { 436 return _configureText; 437 } 438 try { 439 StringWriter stringWriter = new StringWriter(); 440 stringWriter.write("<group>\n"); 441 442 Iterator classes = classDefinitionList().iterator(); 443 444 while (classes.hasNext()) { 445 ComponentEntity entity = (ComponentEntity) classes.next(); 446 entity.exportMoML(stringWriter, 1); 447 } 448 449 Iterator entities = entityList().iterator(); 450 451 while (entities.hasNext()) { 452 ComponentEntity entity = (ComponentEntity) entities.next(); 453 entity.exportMoML(stringWriter, 1); 454 } 455 456 stringWriter.write("</group>"); 457 return stringWriter.toString(); 458 } catch (IOException ex) { 459 return ""; 460 } 461 } 462 463 /** Override the base class to prevent populating. 464 * @return An iterator over instances of NamedObj contained by this 465 * object. 466 */ 467 @Override 468 public Iterator lazyContainedObjectsIterator() { 469 boolean previous = _populating; 470 try { 471 _populating = true; 472 return containedObjectsIterator(); 473 } finally { 474 _populating = previous; 475 } 476 } 477 478 /** Return the number of contained entities. This overrides the base class 479 * to first populate the library, if necessary, by calling populate(). 480 * Note that this may result in a runtime exception being thrown 481 * (if there is an error evaluating the MoML). 482 * This method is read-synchronized on the workspace. 483 * @return The number of entities. 484 */ 485 @Override 486 public int numberOfEntities() { 487 populate(); 488 return super.numberOfEntities(); 489 } 490 491 /** Populate the actor by reading the file specified by the 492 * <i>source</i> parameter. Note that the exception thrown here is 493 * a runtime exception, inappropriately. This is because execution of 494 * this method is deferred to the last possible moment, and it is often 495 * evaluated in a context where a compile-time exception cannot be 496 * thrown. Thus, extra care should be exercised to provide valid 497 * MoML specifications. 498 * @exception InvalidStateException If the source cannot be read, or if 499 * an exception is thrown parsing its MoML data. 500 */ 501 @Override 502 public void populate() throws InvalidStateException { 503 if (_populating) { 504 return; 505 } 506 try { 507 // Avoid populating during cloning. 508 if (_cloning) { 509 return; 510 } 511 512 _populating = true; 513 514 if (!_configureDone) { 515 // NOTE: If you suspect this is being called prematurely, 516 // then uncomment the following to see who is doing the 517 // calling. 518 // System.out.println("-----------------------"); 519 // (new Exception()).printStackTrace(); 520 // NOTE: Set this early to prevent repeated attempts to 521 // evaluate if an exception occurs. This way, it will 522 // be possible to examine a partially populated entity. 523 _configureDone = true; 524 525 // NOTE: This does not seem like the right thing to do! 526 // removeAllEntities(); 527 MoMLParser parser = new MoMLParser(workspace()); 528 529 parser.setContext(this); 530 531 if (_configureSource != null && !_configureSource.equals("")) { 532 URL xmlFile = new URL(_base, _configureSource); 533 parser.parse(xmlFile, xmlFile); 534 } 535 536 if (_configureText != null && !_configureText.equals("")) { 537 // NOTE: Regrettably, the XML parser we are using cannot 538 // deal with having a single processing instruction at the 539 // outer level. Thus, we have to strip it. 540 String trimmed = _configureText.trim(); 541 542 if (trimmed.startsWith("<?") && trimmed.endsWith("?>")) { 543 trimmed = trimmed.substring(2, trimmed.length() - 2) 544 .trim(); 545 546 if (trimmed.startsWith("moml")) { 547 trimmed = trimmed.substring(4).trim(); 548 parser.parse(_base, trimmed); 549 } 550 551 // If it's not a moml processing instruction, ignore. 552 } else { 553 // Data is not enclosed in a processing instruction. 554 // Must have been given in a CDATA section. 555 parser.parse(_base, _configureText); 556 } 557 } 558 } 559 // Set this if everything succeeds. 560 _populated = true; 561 } catch (Exception ex) { 562 MessageHandler.error("Failed to populate library.", ex); 563 564 // Oddly, under JDK1.3.1, we may see the line 565 // "Exception occurred during event dispatching:" 566 // in the console window, but there is no stack trace. 567 // If we change this exception to a RuntimeException, then 568 // the stack trace appears. My guess is this indicates a 569 // bug in the ptolemy.kernel.Exception* classes or in JDK1.3.1 570 // Note that under JDK1.4, the stack trace is printed in 571 // both cases. 572 throw new InvalidStateException(this, ex, 573 "Failed to populate Library"); 574 } finally { 575 _populating = false; 576 } 577 } 578 579 /////////////////////////////////////////////////////////////////// 580 //// protected methods //// 581 582 /** Override the base class to prevent triggering a populate() call 583 * when this occurs. 584 * @param name The name of the attribute. 585 * @param text The text with which to configure the attribute. 586 */ 587 @Override 588 protected void _attachText(String name, String text) { 589 boolean previous = _populating; 590 try { 591 _populating = true; 592 super._attachText(name, text); 593 } finally { 594 _populating = previous; 595 } 596 } 597 598 /** Write a MoML description of the contents of this object, wrapped 599 * in a configure element. This is done by first populating the model, 600 * and then exporting its contents into a configure element. This method 601 * is called by exportMoML(). Each description is indented according to 602 * the specified depth and terminated with a newline character. 603 * @param output The output stream to write to. 604 * @param depth The depth in the hierarchy, to determine indenting. 605 * @exception IOException If an I/O error occurs. 606 */ 607 @Override 608 protected void _exportMoMLContents(Writer output, int depth) 609 throws IOException { 610 output.write(_getIndentPrefix(depth) + "<configure>\n"); 611 output.write(_getIndentPrefix(depth + 1) + "<group>\n"); 612 super._exportMoMLContents(output, depth + 2); 613 output.write(_getIndentPrefix(depth + 1) + "</group>\n"); 614 output.write(_getIndentPrefix(depth) + "</configure>\n"); 615 } 616 617 /////////////////////////////////////////////////////////////////// 618 //// protected variables //// 619 620 /** The base specified by the configure() method. */ 621 protected URL _base; 622 623 /** Indicate that we are cloning. */ 624 protected boolean _cloning = false; 625 626 /** Indicate whether data given by configure() has been processed. */ 627 protected boolean _configureDone = false; 628 629 /** URL specified to the configure() method. */ 630 protected String _configureSource; 631 632 /** Text specified to the configure() method. */ 633 protected String _configureText; 634 635 /** Flag indicating populate() has been called, and hence 636 * the configuration should be obtained from the current contents 637 * (in case they have changed) rather than from the _configureText. 638 */ 639 protected boolean _populated = false; 640 641 /** Indicator that we are in the midst of populating. */ 642 protected boolean _populating = false; 643}