001/* A parser for MoML (modeling markup language) 002 003 Copyright (c) 1998-2017 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.BufferedReader; 031import java.io.File; 032import java.io.FileNotFoundException; 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.InputStreamReader; 036import java.io.Reader; 037import java.io.StringReader; 038import java.io.StringWriter; 039import java.lang.ref.WeakReference; 040import java.lang.reflect.Constructor; 041import java.lang.reflect.InvocationTargetException; 042import java.net.MalformedURLException; 043import java.net.URI; 044import java.net.URL; 045import java.util.ArrayList; 046import java.util.EmptyStackException; 047import java.util.HashMap; 048import java.util.HashSet; 049import java.util.Iterator; 050import java.util.LinkedList; 051import java.util.List; 052import java.util.Locale; 053import java.util.Map; 054import java.util.Set; 055import java.util.Stack; 056 057import org.ptolemy.classloading.ClassLoadingStrategy; 058import org.ptolemy.classloading.SimpleClassLoadingStrategy; 059import org.ptolemy.commons.VersionSpecification; 060 061import com.microstar.xml.HandlerBase; 062import com.microstar.xml.XmlException; 063import com.microstar.xml.XmlParser; 064 065import ptolemy.actor.CompositeActor; 066import ptolemy.actor.IOPort; 067import ptolemy.actor.parameters.SharedParameter; 068import ptolemy.kernel.ComponentEntity; 069import ptolemy.kernel.ComponentPort; 070import ptolemy.kernel.ComponentRelation; 071import ptolemy.kernel.CompositeEntity; 072import ptolemy.kernel.Entity; 073import ptolemy.kernel.InstantiableNamedObj; 074import ptolemy.kernel.Port; 075import ptolemy.kernel.Relation; 076import ptolemy.kernel.attributes.URIAttribute; 077import ptolemy.kernel.undo.UndoAction; 078import ptolemy.kernel.undo.UndoActionsList; 079import ptolemy.kernel.undo.UndoStackAttribute; 080import ptolemy.kernel.util.Attribute; 081import ptolemy.kernel.util.ChangeListener; 082import ptolemy.kernel.util.ChangeRequest; 083import ptolemy.kernel.util.Configurable; 084import ptolemy.kernel.util.IllegalActionException; 085import ptolemy.kernel.util.Instantiable; 086import ptolemy.kernel.util.InternalErrorException; 087import ptolemy.kernel.util.KernelException; 088import ptolemy.kernel.util.NameDuplicationException; 089import ptolemy.kernel.util.NamedObj; 090import ptolemy.kernel.util.ScopeExtender; 091import ptolemy.kernel.util.Settable; 092import ptolemy.kernel.util.Singleton; 093import ptolemy.kernel.util.Workspace; 094import ptolemy.util.CancelException; 095import ptolemy.util.ClassUtilities; 096import ptolemy.util.FileUtilities; 097import ptolemy.util.FileUtilities.StreamAndURL; 098import ptolemy.util.MessageHandler; 099import ptolemy.util.StringUtilities; 100 101/////////////////////////////////////////////////////////////////// 102//// MoMLParser 103 104/** 105 This class constructs Ptolemy II models from specifications 106 in MoML (modeling markup language), which is based on XML. 107 The class contains an instance of the Microstar Ælfred XML 108 parser and implements callback methods to interpret the parsed XML. 109 The way to use this class is to call its parse() method. 110 The returned value is top-level composite entity of the model. 111 <p> 112 For convenience, there are several forms of the parse method. 113 Most of these take two arguments, a base, and some specification 114 of the MoML to parse (a stream or the text itself). The base is 115 used to interpret relative URLs that might be present in the MoML. 116 For example, the base might be the document base of an applet. 117 An applet might use this class as follows: 118 <pre> 119 MoMLParser parser = new MoMLParser(); 120 URL docBase = getDocumentBase(); 121 URL xmlFile = new URL(docBase, modelURL); 122 NamedObj toplevel = parser.parse(docBase, xmlFile); 123 </pre> 124 If the first argument to parse() is null, then it is assumed that 125 all URLs in the MoML file are absolute. 126 <p> 127 It can be difficult to create an appropriate URL to give as a base, 128 particularly if what you have is a file or file name 129 in the directory that you want to use as a base. The easiest 130 technique is to use the toURL() method of the File class. 131 Some of the URL constructors, for reasons we don't understand, 132 create URLs that do not work. 133 <p> 134 The MoML code given to a parse() method may be a fragment, 135 and does not need to include the "<?xml ... >" element nor 136 the DOCTYPE specification. However, if the DOCTYPE specification 137 is not given, then the DTD will not be read. The main consequence 138 of this, given the parser we are using, is that default values 139 for attributes will not be set. This could cause errors. 140 The parser itself is not a validating parser, however, so it 141 makes very limited use of the DTD. This may change in the future, 142 so it is best to give the DOCTYPE element. 143 <p> 144 The parse() methods can be used for incremental parsing. After 145 creating an initial model using a call to parse(), further MoML 146 fragments without top-level entity or derived objects can be evaluated 147 to modify the model. You can specify the context in which the 148 MoML to be interpreted by calling setContext(). However, the 149 XML parser limits each fragment to one element. So there always has 150 to be one top-level element. If you wish to evaluate a group of 151 MoML elements in some context, set the context and then place your 152 MoML elements within a group element, as follows: 153 <pre> 154 <group> 155 ... sequence of MoML elements ... 156 </group> 157 </pre> 158 The group element is ignored, and just serves to aggregate the MoML 159 elements, unless it has a name attribute. If it has a name attribute, 160 then the name becomes a prefix (separated by a colon) of all the names 161 of items immediately in the group element. If the value of the name 162 attribute is "auto", then the group is treated specially. Each item 163 immediately contained by the group (i.e. not deeply contained) will 164 be created with its specified name or a modified version of that name 165 that does not match a pre-existing object already contained by the 166 container. That is, when name="auto" is specified, each item is 167 forced to be created with unique name, rather than possibly matching 168 a pre-existing item. 169 <p> 170 If the group name is "doNotOverwriteOverrides", then parameter values 171 will be set only if either the parameter did not previously exist or 172 it has not been overridden. This is a rather specialized attribute 173 used to update a component or model without losing previously set 174 parameter values. 175 <p> 176 The parse methods throw a variety of exceptions if the parsed 177 data does not represent a valid MoML file or if the stream 178 cannot be read for some reason. 179 <p> 180 This parser supports the way Ptolemy II handles hierarchical models, 181 where components are instances cloned from reference models called 182 "classes." A model (a composite entity) is a "class" in Ptolemy II if 183 the elementName field of its MoMLInfo object is the string "class". If a 184 component is cloned from a class, then when that component exports 185 MoML, it references the class from which it was cloned 186 and exports only differences from that class. I.e., if further changes are 187 made to the component, it is important that when the component 188 exports MoML, that those changes are represented in the exported MoML. 189 This effectively implements an inheritance mechanism, where 190 a component inherits all the features of the master from which it 191 is cloned, but then extends the model with its own changes. 192 <p> 193 This class always processes MoML commands in the following 194 order within a "class" or "entity" element, irrespective of the order 195 in which they appear: 196 <ol> 197 <li> Create properties, entities, ports and relations; and 198 <li> Create links. 199 </ol> 200 Within each category, the order of actions depends on the order in 201 which the commands appear in the MoML text. 202 <p> 203 This class works closely with MoMLChangeRequest to implement another 204 feature of Ptolemy II hierarchy. In particular, if an entity is cloned 205 from another that identifies itself as a "class", then any changes that 206 are made to the class via a MoMLChangeRequest are also made to the clone. 207 This parser ensures that those changes are <i>not</i> exported when 208 MoML is exported by the clone, because they will be exported when the 209 master exports MoML. 210 211 @see MoMLChangeRequest 212 @author Edward A. Lee, Steve Neuendorffer, John Reekie, Contributor: Christopher Brooks, Erwin de Ley. 213 @version $Id$ 214 @since Ptolemy II 0.4 215 @Pt.ProposedRating Red (eal) 216 @Pt.AcceptedRating Red (johnr) 217 */ 218public class MoMLParser extends HandlerBase implements ChangeListener { 219 /** Construct a parser that creates a new workspace into which to 220 * put the entities created by the parse() method. 221 */ 222 public MoMLParser() { 223 this(null); 224 } 225 226 /** Construct a parser that creates entities 227 * in the specified workspace. If the argument is null, 228 * create a new workspace with an empty name. Classes will be 229 * created using the classloader that created this class. 230 * @param workspace The workspace into which to place entities. 231 */ 232 public MoMLParser(Workspace workspace) { 233 super(); 234 235 if (workspace == null) { 236 // NOTE: Workspace has no name, to ensure that full names 237 // of enties conform to MoML standard of starting with a 238 // leading period. 239 workspace = new Workspace(); 240 } 241 242 _workspace = workspace; 243 _ifElementStack = new Stack(); 244 _ifElementStack.push(Integer.valueOf(0)); 245 } 246 247 /** Construct a parser that creates entities in the specified workspace. 248 * If the workspace argument is null, then 249 * create a new workspace with an empty name. Classes will be 250 * created using the classloader that created this class. 251 * @param workspace The workspace into which to place entities. 252 * @param loader The class loader that will be used to create classes, 253 * or null if the the bootstrap class loader is to be used. 254 */ 255 public MoMLParser(Workspace workspace, ClassLoader loader) { 256 this(workspace); 257 if (loader != null) { 258 _classLoader = loader; 259 } 260 } 261 262 /** Construct a parser that creates entities in the specified workspace 263 * with a default verisoin specification. 264 * If the workspace argument is null, then 265 * create a new workspace with an empty name. Classes will be 266 * created using the classloader that created this class. 267 * @param workspace The workspace into which to place entities. 268 * @param defaultVersionSpecification The default version specification 269 * @param loader The class loader that will be used to create classes, 270 * or null if the the bootstrap class loader is to be used. 271 */ 272 273 public MoMLParser(Workspace workspace, 274 VersionSpecification defaultVersionSpecification, 275 ClassLoader loader) { 276 this(workspace, loader); 277 this._defaultVersionSpecification = defaultVersionSpecification; 278 } 279 280 /////////////////////////////////////////////////////////////////// 281 //// public methods //// 282 // FIXME: For all propagations, there 283 // are two problems that we haven't dealt with. 284 // 1) The changes are not atomic. If a failure occurs 285 // halfway through propagation, then the model is 286 // corrupted, and no further editing is reliably possible. 287 // For example, the derivation invariant may not be 288 // satisfied. 289 // 2) If the original change was in a class definition 290 // and the objects to which the change propagates 291 // are in different models, then it is not 292 // safe to make changes in those models. They may 293 // be executing for example. One way to find out 294 // whether it is safe is to ask them 295 // isDeferringChangeRequests(), but if we use 296 // ChangeRequests to propagate we take a severe 297 // performance hit and make it essentially 298 // impossible to make the change atomic (see 299 // (1) above). 300 301 /** Add a MoMLFilter to the end of the list of MoMLFilters used 302 * to translate names. If the list of MoMLFilters already contains 303 * the filter, then the filter is not added again. 304 * Note that this method is static. The specified MoMLFilter 305 * will filter all MoML for any instances of this class. 306 * 307 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 308 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 309 * 310 * <p>To avoid leaking memory, it is best if the MoMLParser is 311 * created in a separate Workspace and this method is not called, instead 312 * call {@link #addMoMLFilter(MoMLFilter, Workspace)}:</p> 313 * <pre> 314 * Workspace workspace = new Workspace("MyWorkspace"); 315 * MoMLParser parser = new MoMLParser(workspace); 316 * MoMLFilter myFilter = new ptolemy.moml.filter.ClassChanges(); 317 * MoMLParser.addMoMLFilter(myfilter, workspace); 318 * </pre> 319 * 320 * @param filter The MoMLFilter to add to the list of MoMLFilters. 321 * @see #addMoMLFilters(List filterList, Workspace workspace) 322 * @see #addMoMLFilters(List filterList) 323 * @see #addMoMLFilters(List filterList, Workspace workspace) 324 * @see #getMoMLFilters() 325 * @see #setMoMLFilters(List filterList) 326 * @see #setMoMLFilters(List filterList, Workspace workspace) 327 */ 328 public static void addMoMLFilter(MoMLFilter filter) { 329 MoMLParser.addMoMLFilter(filter, new Workspace("MoMLFilter")); 330 } 331 332 /** Add a MoMLFilter to the end of the list of MoMLFilters used 333 * to translate names. If the list of MoMLFilters already contains 334 * the filter, then the filter is not added again. 335 * Note that this method is static. The specified MoMLFilter 336 * will filter all MoML for any instances of this class. 337 * 338 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 339 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 340 * 341 * <p>To avoid leaking memory, it is best if the MoMLParser is 342 * created in a separate Workspace:</p> 343 * <pre> 344 * Workspace workspace = new Workspace("MyWorkspace"); 345 * MoMLParser parser = new MoMLParser(workspace); 346 * MoMLFilter myFilter = new ptolemy.moml.filter.ClassChanges(); 347 * MoMLParser.addMoMLFilter(myfilter, workspace); 348 * </pre> 349 * 350 * @param filter The MoMLFilter to add to the list of MoMLFilters. 351 * @param workspace MoMLFilters are passed a MoMLParser that is optionally 352 * used by a filter. This parameter determines the Workspace in which 353 * that MoMLFilter is created. To avoid memory leaks, typically the 354 * MoMLFilter that is used to parse a model is created in a new workspace. 355 * The MoMLFilters are static, so we need to pass in the Workspace from 356 * the top level MoMLFilter. 357 * @see #addMoMLFilter(MoMLFilter filter) 358 * @see #addMoMLFilters(List filterList) 359 * @see #addMoMLFilters(List filterList, Workspace workspace) 360 * @see #getMoMLFilters() 361 * @see #setMoMLFilters(List filterList) 362 * @see #setMoMLFilters(List filterList, Workspace workspace) 363 */ 364 public static void addMoMLFilter(MoMLFilter filter, Workspace workspace) { 365 if (_filterList == null) { 366 _filterList = new LinkedList(); 367 } 368 if (_filterMoMLParser == null) { 369 _filterMoMLParser = new MoMLParser(workspace); 370 } 371 if (!_filterList.contains(filter)) { 372 _filterList.add(filter); 373 } 374 } 375 376 /** Add a List of MoMLFilters to the end of the list of MoMLFilters used 377 * to translate names. The argument list of filters is added even if 378 * the current list already contains some of the filters in the argument 379 * list. 380 * Note that this method is static. The specified MoMLFilter 381 * will filter all MoML for any instances of this class. 382 * 383 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 384 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 385 * 386 * <p>To avoid leaking memory, it is best if the MoMLParser is 387 * created in a separate Workspace and this method is not called, instead 388 * call {@link #addMoMLFilters(List, Workspace)}:</p> 389 * <pre> 390 * Workspace workspace = new Workspace("MyWorkspace"); 391 * MoMLParser parser = new MoMLParser(workspace); 392 * List myFilters = BackwardCompatibility.allFilters(); 393 * MoMLParser.addMoMLFilters(myfilter, workspace); 394 * </pre> 395 * 396 * @param filterList The list of MoMLFilters to add to the 397 * list of MoMLFilters to be used to translate names. 398 * @see #addMoMLFilter(MoMLFilter filter) 399 * @see #addMoMLFilter(MoMLFilter filter, Workspace workspace) 400 * @see #addMoMLFilters(List filterList, Workspace workspace) 401 * @see #getMoMLFilters() 402 * @see #setMoMLFilters(List filterList) 403 * @see #setMoMLFilters(List filterList, Workspace workspace) 404 */ 405 public static void addMoMLFilters(List filterList) { 406 MoMLParser.addMoMLFilters(filterList, new Workspace("MoMLFilter")); 407 } 408 409 /** Add a List of MoMLFilters to the end of the list of MoMLFilters used 410 * to translate names. The argument list of filters is added even if 411 * the current list already contains some of the filters in the argument 412 * list. 413 * Note that this method is static. The specified MoMLFilter 414 * will filter all MoML for any instances of this class. 415 * 416 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 417 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 418 * 419 * <p>To avoid leaking memory, it is best if the MoMLParser is 420 * created in a separate Workspace:</p> 421 * <pre> 422 * Workspace workspace = new Workspace("MyWorkspace"); 423 * MoMLParser parser = new MoMLParser(workspace); 424 * List myFiltersList = ... 425 * MoMLParser.addMoMLFilters(myFilterList, workspace); 426 * </pre> 427 * 428 * @param filterList The list of MoMLFilters to add to the 429 * list of MoMLFilters to be used to translate names. 430 * @param workspace MoMLFilters are passed a MoMLParser that is optionally 431 * used by a filter. This parameter determines the Workspace in which 432 * that MoMLFilter is created. To avoid memory leaks, typically the 433 * MoMLFilter that is used to parse a model is created in a new workspace. 434 * The MoMLFilters are static, so we need to pass in the Workspace from 435 * the top level MoMLFilter. 436 * @see #addMoMLFilter(MoMLFilter filter) 437 * @see #addMoMLFilter(MoMLFilter filter, Workspace workspace) 438 * @see #addMoMLFilters(List filterList) 439 * @see #getMoMLFilters() 440 * @see #setMoMLFilters(List filterList) 441 * @see #setMoMLFilters(List filterList, Workspace workspace) 442 */ 443 public static void addMoMLFilters(List filterList, Workspace workspace) { 444 if (_filterList == null) { 445 _filterList = new LinkedList(); 446 } 447 if (_filterMoMLParser == null) { 448 _filterMoMLParser = new MoMLParser(workspace); 449 } 450 _filterList.addAll(filterList); 451 } 452 453 /** Handle an attribute assignment that is part of an XML element. 454 * This method is called prior to the corresponding startElement() 455 * call, so it simply accumulates attributes in a hashtable for 456 * use by startElement(). 457 * @param name The name of the attribute. 458 * @param value The value of the attribute, or null if the attribute 459 * is <code>#IMPLIED</code> and not specified. 460 * @param specified True if the value is specified, false if the 461 * value comes from the default value in the DTD rather than from 462 * the XML file. 463 * @exception XmlException If the name or value is null. 464 */ 465 @Override 466 public void attribute(String name, String value, boolean specified) 467 throws XmlException { 468 if (name == null) { 469 throw new XmlException("Attribute has no name", 470 _currentExternalEntity(), _getLineNumber(), 471 _getColumnNumber()); 472 } 473 474 // If the current namespace is _AUTO_NAMESPACE, then look up the 475 // translation for the name. If there is a translation, then 476 // this name has previously been converted in this group. 477 // Otherwise, this name has not been previously converted, 478 // so we convert it now. To accomplish the conversion, if 479 // we are not at the top level, then the converted name 480 // is the result of calling the container's uniqueName() 481 // method, passing it the specified name. 482 // The auto namespace is disabled while propagating, since 483 // this would otherwise just result in chaotic names for 484 // propagated changes. 485 if (_namespace.equals(_AUTO_NAMESPACE) && _current != null 486 && (name.equals("name") || name.equals("port") 487 || name.startsWith("relation") || name.equals("vertex") 488 || name.equals("pathTo"))) { 489 // See whether the name is in the translation table. 490 // Note that the name might be compound, e.g. "Const.output", 491 // in which case, we need to parse it and check to see whether 492 // the first part of it is in the translation table. 493 // NOTE: There is a remaining bug (or feature): 494 // If the name is absolute, then no translation will be 495 // performed. This may be reasonable behavior. 496 boolean nameSeenAlready = false; 497 498 if (_namespaceTranslationTable != null) { 499 // If the name contains a period, then it is a compound name. 500 String prefix = value; 501 String suffix = ""; 502 503 // NOTE: Paranoid coding, in case value is null. 504 int period = -1; 505 506 if (value != null) { 507 period = value.indexOf("."); 508 } 509 510 if (period >= 0) { 511 prefix = value.substring(0, period); 512 suffix = value.substring(period); 513 } 514 515 String replacement = (String) _namespaceTranslationTable 516 .get(prefix); 517 518 if (replacement != null) { 519 // Replace name with translation. 520 value = replacement + suffix; 521 nameSeenAlready = true; 522 } 523 } 524 525 if (!nameSeenAlready && name.equals("name")) { 526 // We only convert "name" attributes, not "port" or 527 // "relation", etc. 528 String oldValue = value; 529 // If we have seen a createIfNecessary="true", 530 // then skip if there already is an element with that name. 531 String createIfNecessary = (String) _attributes 532 .get("createIfNecessary"); 533 // uniqueName() is not a good test for whether oldValue is 534 // present because uniqueName() strips off any numeric suffix 535 // before testing for existence. Thus, if q1 exists, then 536 // uniqueName(q1) will return q if there is no q. 537 value = _current.uniqueName(oldValue); 538 if (createIfNecessary != null 539 && createIfNecessary.equals("true")) { 540 // Check value against oldValue so that MoMLVariableChecker-2.3.4 passes. 541 // Check for oldValue in _current so that MoMLVariableChecker-3.2 passes. 542 if (!value.equals(oldValue) 543 || _current.getAttribute(oldValue) == null) { 544 // Needed to find Parameters that are up scope. 545 // FIXME: does this check ScopeExtendingAttributes? 546 // Should this use ModelScope.getScopedVariable()? 547 Attribute masterAttribute = null; 548 NamedObj searchContainer = _current; 549 while (searchContainer != null 550 && masterAttribute == null) { 551 masterAttribute = searchContainer 552 .getAttribute(oldValue); 553 searchContainer = searchContainer.getContainer(); 554 } 555 if (!value.equals(oldValue) 556 || masterAttribute != null) { 557 // There already is something with that name, so we skip. 558 String currentElement = _xmlParser 559 .getCurrentElement(); 560 561 // FIXME: increment _skipElement or set it to 1? 562 _skipElement++; 563 _skipElementIsNew = true; 564 _skipElementName = currentElement; 565 return; 566 } 567 } 568 } 569 if (_namespaceTranslationTable == null) { 570 throw new InternalErrorException( 571 "_namespaceTranslationTable was null, which should not happen."); 572 } else { 573 _namespaceTranslationTable.put(oldValue, value); 574 } 575 } 576 } else { 577 // If we have a non-default namespace, then prepend the namespace. 578 // This needs to be done for every attribute whose value is a name. 579 if (!_namespace.equals(_DEFAULT_NAMESPACE) 580 && !_namespace.equals(_AUTO_NAMESPACE) 581 && (name.equals("name") || name.equals("port") 582 || name.equals("relation") || name.equals("vertex") 583 || name.equals("pathTo"))) { 584 value = _namespace + ":" + value; 585 } 586 } 587 588 // Apply MoMLFilters here. 589 // Filters can filter out graphical classes, or change 590 // the names of ports to handle backward compatibility. 591 if (_filterList != null) { 592 // FIXME: There is a slight risk of xmlParser being null here. 593 if (_xmlParser == null) { 594 throw new InternalErrorException( 595 "_xmlParser is null? This can occur " 596 + " when parse(URL, String, Reader)" 597 + " calls itself because that method" 598 + " sets _xmlParser to null while exiting. " 599 + "name: " + name + " value: " + value); 600 } 601 String currentElement = _xmlParser.getCurrentElement(); 602 Iterator filters = _filterList.iterator(); 603 String filteredValue = value; 604 605 while (filters.hasNext()) { 606 MoMLFilter filter = (MoMLFilter) filters.next(); 607 filteredValue = filter.filterAttributeValue(_current, 608 currentElement, name, filteredValue, _xmlFileName, 609 _filterMoMLParser); 610 } 611 612 // Sometimes the value we pass in is null, so we only 613 // want to skip if filterAttributeValue returns null 614 // when passed a non-null value. 615 if (value != null && filteredValue == null) { 616 // If attribute() found an element to skip, then 617 // the first time we startElement(), we do not 618 // want to increment _skipElement again in 619 // startElement() because we already did it in 620 // attribute(). 621 _skipElementIsNew = true; 622 _skipElementName = currentElement; 623 624 // Is there ever a case when _skipElement would not 625 // be 0 here? I'm not sure . . . 626 _skipElement++; 627 } 628 629 value = filteredValue; 630 } 631 632 // NOTE: value may be null if attribute default is #IMPLIED. 633 _attributes.put(name, value); 634 _attributeNameList.add(name); 635 } 636 637 /** React to a change request has been successfully executed. 638 * This method is called after a change request 639 * has been executed successfully. It does nothing. 640 * @param change The change that has been executed, or null if 641 * the change was not done via a ChangeRequest. 642 */ 643 @Override 644 public void changeExecuted(ChangeRequest change) { 645 } 646 647 /** React to a change request has resulted in an exception. 648 * This method is called after a change request was executed, 649 * but during the execution an exception was thrown. 650 * Presumably the change was a propagation request. 651 * If there is a registered error handler, then the 652 * error is delegated to that handler. Otherwise, 653 * the error is reported to stderr. 654 * @param change The change that was attempted or null if 655 * the change was not done via a ChangeRequest. 656 * @param exception The exception that resulted. 657 */ 658 @Override 659 public void changeFailed(ChangeRequest change, Exception exception) { 660 if (_handler != null) { 661 int reply = _handler.handleError(change.toString(), _toplevel, 662 exception); 663 664 if (reply == ErrorHandler.CONTINUE) { 665 return; 666 } 667 } 668 669 // No handler, or cancel button pushed. 670 // FIXME: What is the right thing to do here? 671 System.err.println(exception.toString()); 672 exception.printStackTrace(); 673 } 674 675 /** Handle character data. In this implementation, the 676 * character data is accumulated in a buffer until the end element. 677 * Character data appears only in doc and configure elements. 678 * Ælfred will call this method once for each chunk of 679 * character data found in the contents of elements. Note that 680 * the parser may break up a long sequence of characters into 681 * smaller chunks and call this method once for each chunk. 682 * @param chars The character data. 683 * @param offset The starting position in the array. 684 * @param length The number of characters available. 685 */ 686 @Override 687 public void charData(char[] chars, int offset, int length) { 688 // If we haven't initialized _currentCharData, then we don't 689 // care about character data, so we ignore it. 690 if (_currentCharData != null) { 691 _currentCharData.append(chars, offset, length); 692 } 693 } 694 695 /** Clear or create the top objects list. The top objects list 696 * is a list of top-level objects that this parser has 697 * created. This method has the side effect of starting the 698 * parser recording creation of top-level objects. 699 * @see #topObjectsCreated() 700 */ 701 public void clearTopObjectsList() { 702 if (_topObjectsCreated == null) { 703 _topObjectsCreated = new LinkedList(); 704 } else { 705 _topObjectsCreated.clear(); 706 } 707 } 708 709 /** If a public ID is given, and is not that of MoML, 710 * then throw a CancelException, which causes the parse to abort 711 * and return null. Note that the version number is not checked, 712 * so future versions of MoML should also work. 713 * @param name The name of the document type. 714 * @param publicID The public ID of the document type. 715 * @param systemID The system ID of the document type. 716 * @exception CancelException If the public ID is not that of MoML. 717 */ 718 @Override 719 public void doctypeDecl(String name, String publicID, String systemID) 720 throws CancelException { 721 if (publicID != null && !publicID.trim().equals("") 722 && !publicID.startsWith("-//UC Berkeley//DTD MoML")) { 723 throw new CancelException( 724 "Public ID is not that of MoML version 1: " + publicID); 725 } 726 } 727 728 /** End the document. The MoMLParser calls this method once, when 729 * it has finished parsing the complete XML document. It is 730 * guaranteed that this will be the last method called in the XML 731 * parsing process. As a consequence, it is guaranteed that all 732 * dependencies between parameters used in the XML description 733 * are resolved. This method executes any change requests that 734 * may have been made during the parsing process. 735 * @exception CancelException If an error occurs parsing one of the 736 * parameter values, and the user clicks on "cancel" to cancel the 737 * parse. 738 */ 739 @Override 740 public void endDocument() throws Exception { 741 // If link or delete requests are issued at the top level, 742 // then they must be processed here. 743 _processPendingRequests(); 744 745 // Push the undo entry. 746 // Note that it is not correct here to check _undoEnabled because 747 // undo might have been disabled part way through the current element. 748 if (_undoContext != null && _undoContext.hasUndoMoML()) { 749 String undoMoML = _undoContext.getUndoMoML(); 750 751 if (_undoDebug) { 752 // Print out what has been generated 753 System.out.println("======================="); 754 System.out.println("Generated UNDO MoML: "); 755 System.out.print(undoMoML); 756 System.out.println("======================="); 757 } 758 759 // Create a new undo entry! 760 // NOTE: we use the current context to undo the change. This is 761 // because a change request sets the context before applying 762 // some incremental MoML. 763 NamedObj context = _current; 764 765 if (context == null) { 766 context = _toplevel; 767 } 768 769 UndoAction newEntry = new MoMLUndoEntry(context, undoMoML); 770 UndoStackAttribute undoInfo = UndoStackAttribute 771 .getUndoInfo(context); 772 773 // If we have additional undo actions that have to execute 774 // in a different context, bundle those together with this one 775 // before pushing on the undo stack. 776 if (_undoForOverrides.size() > 0) { 777 newEntry = new UndoActionsList(newEntry); 778 for (UndoAction action : _undoForOverrides) { 779 ((UndoActionsList) newEntry).add(action); 780 } 781 } 782 783 // If we are in the middle of processing an undo, then this will 784 // go onto the redo stack. 785 undoInfo.push(newEntry); 786 787 // Clear up the various MoML variables. 788 _resetUndo(); 789 } 790 791 // See https://projects.ecoinformatics.org/ecoinfo/issues/6587: summarize missing actors 792 if (_missingClasses != null) { 793 StringBuffer warning = new StringBuffer(); 794 for (String missingClass : _missingClasses) { 795 warning.append(missingClass + ", "); 796 } 797 // Get rid of the trailing comma and space. 798 warning.delete(warning.length() - 2, warning.length()); 799 800 // Adding another dialog is annoying, so we print out the warning. 801 System.err.println("Warning: Missing Classes: " + warning); 802 803 // MessageHandler(String) is not selectable, so we use MessageHandler(String, Throwable). 804 //MessageHandler.warning("Missing Classes", new Exception(warning.toString())); 805 } 806 807 // If there were any unrecognized elements, warn the user. 808 if (_unrecognized != null) { 809 StringBuffer warning = new StringBuffer( 810 "Warning: Unrecognized XML elements:"); 811 Iterator elements = _unrecognized.iterator(); 812 813 while (elements.hasNext()) { 814 warning.append(" " + elements.next().toString()); 815 } 816 817 try { 818 MessageHandler.warning(warning.toString()); 819 } catch (CancelException ex) { 820 // Ignore, since this is a one-time notification. 821 } 822 } 823 824 try { 825 // Execute any change requests that might have been queued 826 // as a consequence of this change request. 827 // NOTE: This used to be done after validating parameters, 828 // but now these change requests might add to the list 829 // of parameters to validate (because of propagation). 830 // EAL 3/04 831 if (_toplevel != null) { 832 // Set the top level back to the default 833 // found in startDocument. 834 _toplevel.setDeferringChangeRequests(_previousDeferStatus); 835 _toplevel.executeChangeRequests(); 836 } 837 838 // Before evaluating parameters, expand all ScopeExtenders. 839 // Scope extenders will create parameters that other parameters may depend 840 // on, and these parameters were not seen during parsing. 841 _expandScopeExtenders(); 842 843 // Force evaluation of parameters so that any listeners are notified. 844 // This will also force evaluation of any parameter that this variable 845 // depends on. 846 Iterator parameters = _paramsToParse.iterator(); 847 848 // As an optimization, if there are multiple instances of 849 // SharedParameter in the list that are shared, we only 850 // validate the first of these. This prevents a square-law 851 // increase in complexity, because each validation of an 852 // instance of SharedParameter causes validation of all 853 // its shared instances. EAL 9/10/06. 854 HashSet parametersValidated = new HashSet(); 855 while (parameters.hasNext()) { 856 Settable param = (Settable) parameters.next(); 857 858 if (parametersValidated.contains(param)) { 859 continue; 860 } 861 862 // NOTE: We used to catch exceptions here and issue 863 // a warning only, but this has the side effect of blocking 864 // the mechanism in PtolemyQuery that carefully prompts 865 // the user for corrected parameter values. 866 try { 867 param.validate(); 868 869 // Also validate derived objects. 870 Iterator derivedParams = ((NamedObj) param).getDerivedList() 871 .iterator(); 872 873 while (derivedParams.hasNext()) { 874 Settable derivedParam = (Settable) derivedParams.next(); 875 derivedParam.validate(); 876 parametersValidated.add(derivedParam); 877 } 878 879 if (param instanceof SharedParameter) { 880 parametersValidated.addAll( 881 ((SharedParameter) param).sharedParameterSet()); 882 } 883 } catch (Exception ex) { 884 if (_handler != null) { 885 int reply = _handler.handleError("<param name=\"" 886 + param.getName() + "\" value=\"" 887 + param.getExpression() + "\"/>", 888 param.getContainer(), ex); 889 890 if (reply == ErrorHandler.CONTINUE) { 891 continue; 892 } 893 } 894 895 // No handler, or cancel button pushed. 896 throw ex; 897 } 898 } 899 } finally { 900 if (_handler != null) { 901 _handler.enableErrorSkipping(false); 902 } 903 } 904 } 905 906 /** End an element. This method pops the current container from 907 * the stack, if appropriate, and also adds specialized properties 908 * to the container, such as <i>_doc</i>, if appropriate. 909 * Ælfred will call this method at the end of each element 910 * (including EMPTY elements). 911 * @param elementName The element type name. 912 * @exception Exception If thrown while adding properties. 913 */ 914 @Override 915 public void endElement(String elementName) throws Exception { 916 // Apply MoMLFilters here. 917 // FIXME: Why is this done first? Perhaps it should be 918 // done last? 919 if (_filterList != null) { 920 Iterator filters = _filterList.iterator(); 921 922 while (filters.hasNext()) { 923 MoMLFilter filter = (MoMLFilter) filters.next(); 924 filter.filterEndElement(_current, elementName, _currentCharData, 925 _xmlFileName, _filterMoMLParser); 926 } 927 } 928 929 if (((Integer) _ifElementStack.peek()).intValue() > 1) { 930 _ifElementStack 931 .push(((Integer) _ifElementStack.pop()).intValue() - 1); 932 } else if (_skipElement <= 0) { 933 // If we are not skipping an element, then adjust the 934 // configuration nesting and doc nesting counts accordingly. 935 // This was illustrated by having the RemoveGraphicalClasses 936 // filter remove the SketchedSource from sources.xml, 937 // which resulted in _docNesting being decremented from 0 to -1, 938 // which caused problems with undo. 939 // See test 1.4 in filter/test/RemoveGraphicalClasses.tcl 940 // FIXME: Instead of doing string comparisons, do a hash lookup. 941 if (elementName.equals("configure")) { 942 // Count configure tags so that they can nest. 943 _configureNesting--; 944 945 if (_configureNesting < 0) { 946 throw new XmlException( 947 "Internal Error: _configureNesting is " 948 + _configureNesting 949 + " which is <0, which indicates a nesting bug", 950 _currentExternalEntity(), _getLineNumber(), 951 _getColumnNumber()); 952 } 953 } else if (elementName.equals("doc")) { 954 // Count doc tags so that they can nest. 955 _docNesting--; 956 957 if (_docNesting < 0) { 958 throw new XmlException("Internal Error: _docNesting is " 959 + _docNesting 960 + " which is <0, which indicates a nesting bug", 961 _currentExternalEntity(), _getLineNumber(), 962 _getColumnNumber()); 963 } 964 } 965 966 if (_configureNesting > 0 || _docNesting > 0) { 967 // Inside a configure or doc tag. 968 // Simply replicate the element in the current 969 // character buffer. 970 _currentCharData.append("</"); 971 _currentCharData.append(elementName); 972 _currentCharData.append(">"); 973 return; 974 } 975 } 976 977 if (_skipRendition) { 978 if (elementName.equals("rendition")) { 979 _skipRendition = false; 980 } 981 } else if (_skipElement > 0) { 982 if (elementName.equals(_skipElementName)) { 983 // Nested element name. Have to count so we properly 984 // close the skipping. 985 _skipElement--; 986 } 987 } else if (((Integer) _ifElementStack.peek()).intValue() > 1) { 988 } else if (elementName.equals("if") 989 && ((Integer) _ifElementStack.peek()).intValue() == 1) { 990 _ifElementStack.pop(); 991 } else if (elementName.equals("configure")) { 992 try { 993 Configurable castCurrent = (Configurable) _current; 994 String previousSource = castCurrent.getConfigureSource(); 995 String previousText = castCurrent.getConfigureText(); 996 castCurrent.configure(_base, _configureSource, 997 _currentCharData.toString()); 998 999 // Propagate to derived classes and instances. 1000 try { 1001 // This has the side effect of marking the value 1002 // overridden. 1003 _current.propagateValue(); 1004 } catch (IllegalActionException ex) { 1005 // Propagation failed. Restore previous value. 1006 castCurrent.configure(_base, previousSource, previousText); 1007 throw ex; 1008 } 1009 } catch (NoClassDefFoundError e) { 1010 // If we are running without a display and diva.jar 1011 // is not in the classpath, then we may get" 1012 // "java.lang.NoClassDefFoundError: diva/canvas/Figure" 1013 } 1014 } else { 1015 // The doc and group used to be part of "else if" above but had 1016 // to move into here to handle undo 1017 if (elementName.equals("doc")) { 1018 // NOTE: for the undo of a doc element, all work is done here 1019 if (_currentDocName == null && _docNesting == 0) { 1020 _currentDocName = "_doc"; 1021 } 1022 1023 // For undo need to know if a previous doc attribute with this 1024 // name existed 1025 Documentation previous = (Documentation) _current 1026 .getAttribute(_currentDocName); 1027 String previousValue = null; 1028 1029 if (previous != null) { 1030 previousValue = previous.getValueAsString(); 1031 } 1032 1033 // Set the doc value only if it differs from the previous. 1034 // FIXME: Is this the right thing to do w.r.t. propagation? 1035 // In effect, this means that if the value is the same as 1036 // the previous, then this will revert to not being an 1037 // override when the model is next opened. 1038 // Cf. What is done with parameter values. 1039 if (!_currentCharData.toString().equals(previousValue)) { 1040 if (previous != null) { 1041 String newString = _currentCharData.toString(); 1042 1043 // If the newString is an empty list, then 1044 // this will have the side effect of deleting 1045 // the doc element by calling setContainer(null) 1046 // in a change request. Since this is done in 1047 // a change request, it will be done after 1048 // propagation, as it should be. 1049 previous.setExpression(newString); 1050 1051 // Propagate to derived classes and instances. 1052 // If the new string is empty, this will remove 1053 // the doc tag from any derived object that has 1054 // not overridden the value of the doc tag. 1055 try { 1056 // This has the side effect of marking the 1057 // value overridden. 1058 previous.propagateValue(); 1059 } catch (IllegalActionException ex) { 1060 // Propagation failed. Restore previous value. 1061 previous.setExpression(previousValue); 1062 throw ex; 1063 } 1064 } else { 1065 Documentation doc = new Documentation(_current, 1066 _currentDocName); 1067 doc.setValue(_currentCharData.toString()); 1068 1069 // Propagate. This has the side effect of marking 1070 // the object overridden from its class definition. 1071 doc.propagateExistence(); 1072 1073 // Propagate value. This has the side effect of marking 1074 // the object overridden from its class definition. 1075 doc.propagateValue(); 1076 } 1077 } 1078 1079 if (_undoEnabled) { 1080 _undoContext.appendUndoMoML( 1081 "<doc name=\"" + _currentDocName + "\">"); 1082 1083 if (previous != null) { 1084 _undoContext.appendUndoMoML(previousValue); 1085 } 1086 1087 _undoContext.appendUndoMoML("</doc>\n"); 1088 } 1089 1090 _currentDocName = null; 1091 } else if (elementName.equals("group")) { 1092 // Process link requests that have accumulated in 1093 // this element. 1094 _processPendingRequests(); 1095 1096 try { 1097 _namespace = (String) _namespaces.pop(); 1098 1099 _namespaceTranslationTable = (Map) _namespaceTranslations 1100 .pop(); 1101 } catch (EmptyStackException ex) { 1102 _namespace = _DEFAULT_NAMESPACE; 1103 } 1104 // If we are closing the outermost group that indicated 1105 // doNotOverwriteOverrides, then reset so that we can start 1106 // overwritting overrides again. 1107 if (_groupCount <= _overwriteOverrides) { 1108 _overwriteOverrides = 0; 1109 } 1110 _groupCount--; 1111 1112 try { 1113 _linkRequests = (List) _linkRequestStack.pop(); 1114 } catch (EmptyStackException ex) { 1115 // We are back at the top level. 1116 _linkRequests = null; 1117 } 1118 1119 try { 1120 _deleteRequests = (List) _deleteRequestStack.pop(); 1121 } catch (EmptyStackException ex) { 1122 // We are back at the top level. 1123 _deleteRequests = null; 1124 } 1125 } else if (elementName.equals("class") 1126 || elementName.equals("entity") 1127 || elementName.equals("model")) { 1128 // Process link requests that have accumulated in 1129 // this element. 1130 _processPendingRequests(); 1131 1132 try { 1133 _current = (NamedObj) _containers.pop(); 1134 } catch (EmptyStackException ex) { 1135 // We are back at the top level. 1136 _current = null; 1137 1138 } 1139 1140 try { 1141 _namespace = (String) _namespaces.pop(); 1142 1143 _namespaceTranslationTable = (Map) _namespaceTranslations 1144 .pop(); 1145 } catch (EmptyStackException ex) { 1146 _namespace = _DEFAULT_NAMESPACE; 1147 } 1148 1149 try { 1150 _linkRequests = (List) _linkRequestStack.pop(); 1151 } catch (EmptyStackException ex) { 1152 // We are back at the top level. 1153 _linkRequests = null; 1154 } 1155 1156 try { 1157 _deleteRequests = (List) _deleteRequestStack.pop(); 1158 } catch (EmptyStackException ex) { 1159 // We are back at the top level. 1160 _deleteRequests = null; 1161 } 1162 } else if (elementName.equals("property") 1163 || elementName.equals("director") 1164 || elementName.equals("port") 1165 || elementName.equals("relation") 1166 || elementName.equals("rendition") 1167 || elementName.equals("vertex")) { 1168 try { 1169 _current = (NamedObj) _containers.pop(); 1170 } catch (EmptyStackException ex) { 1171 // We are back at the top level. 1172 _current = null; 1173 } 1174 1175 try { 1176 _namespace = (String) _namespaces.pop(); 1177 1178 _namespaceTranslationTable = (Map) _namespaceTranslations 1179 .pop(); 1180 } catch (EmptyStackException ex) { 1181 _namespace = _DEFAULT_NAMESPACE; 1182 } 1183 } 1184 } 1185 1186 // Handle the undoable aspect, if undo is enabled. 1187 // FIXME: How should _skipElement and _undoEnable interact? 1188 // If we are skipping an element, are we sure that we want 1189 // to add it to the undoContext? 1190 String undoMoML = null; 1191 // Note that it is not correct here to check _undoEnabled because 1192 // undo might have been disabled part way through the current element. 1193 if (_undoContext != null && _undoContext.hasUndoMoML()) { 1194 // Get the result from this element, as we'll be pushing 1195 // it onto the stack of children MoML for the parent context 1196 undoMoML = _undoContext.generateUndoEntry(); 1197 1198 if (_undoDebug) { 1199 System.out.println("Completed element: " + elementName + "\n" 1200 + _undoContext.getUndoMoML()); 1201 } 1202 } 1203 // Have to pop even if undo is not enabled! 1204 // Otherwise, we don't restore undo at the next level up. 1205 try { 1206 // Reset the undo context to the parent. 1207 // NOTE: if this is the top context, then doing a pop here 1208 // will cause the EmptyStackException 1209 _undoContext = (UndoContext) _undoContexts.pop(); 1210 _undoEnabled = _undoContext.isUndoable(); 1211 } catch (EmptyStackException ex) { 1212 // At the top level. The current _undoContext has the undo 1213 // that we want to preserve. 1214 if (_undoDebug) { 1215 System.out.println( 1216 "Reached top level of undo " + "context stack"); 1217 } 1218 } 1219 // Push the child's undo MoML on the stack of child 1220 // undo entries. 1221 if (undoMoML != null) { 1222 _undoContext.pushUndoEntry(undoMoML); 1223 } 1224 } 1225 1226 /** Handle the end of an external entity. This pops the stack so 1227 * that error reporting correctly reports the external entity that 1228 * causes the error. 1229 * @param systemID The URI for the external entity. 1230 */ 1231 @Override 1232 public void endExternalEntity(String systemID) { 1233 _externalEntities.pop(); 1234 } 1235 1236 /** Indicate a fatal XML parsing error. 1237 * Ælfred will call this method whenever it encounters 1238 * a serious error. This method simply throws an XmlException. 1239 * @param message The error message. 1240 * @param systemID The URI of the entity that caused the error. 1241 * @param line The approximate line number of the error. 1242 * @param column The approximate column number of the error. 1243 * @exception XmlException If called. 1244 */ 1245 @Override 1246 public void error(String message, String systemID, int line, int column) 1247 throws XmlException { 1248 String currentExternalEntity = ""; 1249 1250 try { 1251 // Error message methods should be very careful to handle 1252 // exceptions while trying to provide the user with information 1253 currentExternalEntity = _currentExternalEntity(); 1254 1255 // Restore the status of change requests. 1256 // Execute any change requests that might have been queued 1257 // as a consequence of this change request. 1258 if (_toplevel != null) { 1259 // Set the top level back to the default 1260 // found in startDocument. 1261 _toplevel.setDeferringChangeRequests(_previousDeferStatus); 1262 _toplevel.executeChangeRequests(); 1263 } 1264 } catch (Throwable throwable) { 1265 // Ignore any exceptions here. 1266 } 1267 1268 throw new XmlException(message, currentExternalEntity, line, column); 1269 } 1270 1271 /** Given a file name or URL description, find a URL on which 1272 * openStream() will work. If a base is given, the URL can 1273 * be found relative to that base. 1274 * If the URL cannot be found relative to this base, then it 1275 * is searched for relative to the current working directory 1276 * (if this is permitted with the current security restrictions), 1277 * and then relative to the classpath. If the URL is external 1278 * and is not relative to a previously defined base, then the 1279 * user will be warned about a security concern and given the 1280 * opportunity to cancel. 1281 * <p> 1282 * NOTE: This may trigger a dialog with the user (about 1283 * security concerns), and hence should be called in the event 1284 * thread. 1285 * @param source A file name or URL description. 1286 * @param base The base URL for relative references, or null if 1287 * there is none. 1288 * @return A URL on which openStream() will succeed. 1289 * @exception Exception If the file or URL cannot be found or 1290 * if the user cancels on being warned of a security concern. 1291 */ 1292 public URL fileNameToURL(String source, URL base) throws Exception { 1293 URL result = null; 1294 StringBuffer errorMessage = new StringBuffer(); 1295 InputStream input = null; 1296 1297 try { 1298 result = new URL(base, source); 1299 1300 // Security concern here. Warn if external source. 1301 // and we are not running within an applet. 1302 // The warning method will throw a CancelException if the 1303 // user clicks "Cancel". 1304 String protocol = result.getProtocol(); 1305 1306 // To test this code: 1307 // $PTII/bin/vergil $PTII/ptolemy/moml/demo/Networked/Networked.xml 1308 if (protocol != null && (protocol.trim() 1309 .toLowerCase(Locale.getDefault()).equals("http") 1310 || protocol.trim().toLowerCase(Locale.getDefault()) 1311 .equals("https"))) { 1312 1313 SecurityManager security = System.getSecurityManager(); 1314 boolean withinUntrustedApplet = false; 1315 1316 if (security != null) { 1317 try { 1318 // This is sort of arbitrary, but seems to be the 1319 // closest choice. 1320 security.checkCreateClassLoader(); 1321 } catch (SecurityException securityException) { 1322 // If we are running under an untrusted applet. 1323 // then a SecurityException will be thrown, 1324 // and we can rely on the Applet protection 1325 // mechanism to protect the user against 1326 // a wayward model. 1327 // Note that if the jar files were signed 1328 // for use with Web Start, then we are in a trusted 1329 // applet, so the SecurityException will _not_ 1330 // be thrown and withinUntrustedApplet will be false. 1331 withinUntrustedApplet = true; 1332 } 1333 } 1334 1335 String host = result.getHost(); 1336 if ((security == null || withinUntrustedApplet == false) 1337 && !_approvedRemoteXmlFiles.contains(result) 1338 && !_approvedRemoteXmlFiles.contains(host)) { 1339 // If the result and _base have a common root, 1340 // then the code is ok. 1341 String resultBase = result.toString().substring(0, 1342 result.toString().lastIndexOf("/")); 1343 1344 boolean answer = false; 1345 if (_base == null 1346 || !resultBase.startsWith(_base.toString())) { 1347 answer = MessageHandler.yesNoCancelQuestion( 1348 "OK to download MoML at " 1349 + result.toExternalForm() 1350 + "?\nAllow all future downloads from " 1351 + host + "?", 1352 "Allow this download only", 1353 "Allow all from this host", "Cancel"); 1354 } 1355 1356 // If we get to here, the the user did not hit cancel, 1357 // so we cache the file or host. 1358 if (answer) { 1359 _approvedRemoteXmlFiles.add(result); 1360 } else { 1361 _approvedRemoteXmlFiles.add(host); 1362 } 1363 } 1364 } 1365 1366 // Get the InputStream and possibly redirected url. 1367 1368 // This method returns an object that contains the 1369 // InputStream and URL which means we don't need to follow 1370 // redirects twice. 1371 1372 StreamAndURL streamAndURL = FileUtilities 1373 .openStreamFollowingRedirectsReturningBoth(result); 1374 result = streamAndURL.url(); 1375 input = streamAndURL.stream(); 1376 1377 } catch (IOException ioException) { 1378 errorMessage.append("-- " + ioException.getMessage() + "\n"); 1379 1380 // The error messages used to be more verbose, uncomment 1381 // the next line if you would like to know what failed and why 1382 // errorMessage.append( 1383 // "\n base: " + base 1384 // + "\n source: " + source 1385 // + "\n result: " + result 1386 // + "\n" +KernelException.stackTraceToString(ioException)); 1387 result = _classLoader.getResource(source); 1388 1389 if (result != null) { 1390 input = result.openStream(); 1391 } else { 1392 errorMessage.append( 1393 "-- XML file not found relative to classpath.\n"); 1394 1395 // Failed to open relative to the classpath. 1396 // Try relative to the current working directory. 1397 // NOTE: This is last because it will fail with a 1398 // security exception in applets. 1399 String cwd = StringUtilities.getProperty("user.dir"); 1400 try { 1401 base = new File(cwd).toURI().toURL(); 1402 result = new URL(base, source); 1403 input = result.openStream(); 1404 } catch (Throwable throwable) { 1405 errorMessage.append("-- " + cwd + File.separator + source 1406 + "\n" + throwable.getMessage() + "\n"); 1407 } 1408 } 1409 } 1410 1411 if (input == null) { 1412 throw new XmlException(errorMessage.toString(), 1413 _currentExternalEntity(), _getLineNumber(), 1414 _getColumnNumber()); 1415 } 1416 1417 // If we get here, then result cannot possibly be null. 1418 // Close the open stream, which was used only to make 1419 // sure it would work. 1420 input.close(); 1421 return result; 1422 } 1423 1424 /** Get the error handler to handle parsing errors. 1425 * Note that this method is static. The returned error handler 1426 * will handle all errors for any instance of this class. 1427 * @return The ErrorHandler currently handling errors. 1428 * @see #setErrorHandler(ErrorHandler) 1429 */ 1430 public static ErrorHandler getErrorHandler() { 1431 return _handler; 1432 } 1433 1434 /** Get the icon loader for all MoMLParsers. 1435 * @return The IconLoader for all MoMLParsers. 1436 * @see #setIconLoader(IconLoader) 1437 */ 1438 public static IconLoader getIconLoader() { 1439 return _iconLoader; 1440 } 1441 1442 /** Get the List of MoMLFilters used to translate names. 1443 * Note that this method is static. The returned MoMLFilters 1444 * will filter all MoML for any instances of this class. 1445 * @return The MoMLFilters currently filtering. 1446 * @see #addMoMLFilter(MoMLFilter filter) 1447 * @see #addMoMLFilter(MoMLFilter filter, Workspace workspace) 1448 * @see #setMoMLFilters(List filterList) 1449 * @see #setMoMLFilters(List filterList, Workspace workspace) 1450 */ 1451 public static List getMoMLFilters() { 1452 return _filterList; 1453 } 1454 1455 /** Get the top-level entity associated with this parser, or null if none. 1456 * @return The top-level associated with this parser. 1457 * @see #setToplevel(NamedObj) 1458 */ 1459 public NamedObj getToplevel() { 1460 return _toplevel; 1461 } 1462 1463 /** Return the value set by setModified(), or false if setModified() 1464 * has yet not been called. 1465 * @see #setModified(boolean) 1466 * @return True if the data has been modified. 1467 */ 1468 public static boolean isModified() { 1469 return _modified; 1470 } 1471 1472 /** Parse the MoML file at the given URL, which may be a file 1473 * on the local file system, using the specified base 1474 * to expand any relative references within the MoML file. 1475 * If the <i>input</i> URL has already been parsed, then 1476 * return the model that was previously parsed. Note that this 1477 * means that an application that opens and then closes 1478 * a model and expects to re-parse the XML when re-opening 1479 * must call purgeModelRecord() when closing it. 1480 * This method uses parse(URL, InputStream). 1481 * @param base The base URL for relative references, or null if 1482 * not known. 1483 * @param input The stream from which to read XML. 1484 * @return The top-level composite entity of the Ptolemy II model, or 1485 * null if the file is not recognized as a MoML file. 1486 * @exception Exception If the parser fails. 1487 * @see #purgeModelRecord(URL) 1488 * @see #purgeAllModelRecords() 1489 */ 1490 @SuppressWarnings("resource") 1491 public NamedObj parse(URL base, URL input) throws Exception { 1492 _setXmlFile(input); 1493 1494 try { 1495 if (_imports == null) { 1496 _imports = new HashMap(); 1497 } else { 1498 WeakReference reference = (WeakReference) _imports.get(input); 1499 NamedObj previous = null; 1500 1501 if (reference != null) { 1502 previous = (NamedObj) reference.get(); 1503 1504 if (previous == null) { 1505 _imports.remove(input); 1506 } 1507 } 1508 1509 if (previous != null) { 1510 // NOTE: In theory, we should not even have to 1511 // check whether the file has been updated, because 1512 // if changes were made to model since it was loaded, 1513 // they should have been propagated. 1514 return previous; 1515 } 1516 } 1517 1518 InputStream inputStream = null; 1519 1520 try { 1521 try { 1522 inputStream = input.openStream(); 1523 } catch (Exception ex) { 1524 // Try opening it up as a Jar URL. 1525 // vergilPtiny.jnlp needs this. 1526 URL jarURL = ClassUtilities 1527 .jarURLEntryResource(input.toExternalForm()); 1528 1529 if (jarURL != null) { 1530 inputStream = jarURL.openStream(); 1531 } else { 1532 throw ex; 1533 } 1534 } 1535 // Pass the input URL in case we need it for an error message. 1536 // See test MoMLParser-31.1 1537 NamedObj result = parse(base, input.toString(), inputStream); 1538 // Note that the parse() call above can parse a model that 1539 // call parseMoML() in the expression language which calls 1540 // resetAll(), which sets _imports to null. 1541 if (_imports == null) { 1542 _imports = new HashMap(); 1543 } 1544 _imports.put(input, new WeakReference(result)); 1545 return result; 1546 } finally { 1547 if (inputStream != null) { 1548 inputStream.close(); 1549 } 1550 } 1551 } finally { 1552 _setXmlFile(null); 1553 } 1554 } 1555 1556 /** Parse the given stream, using the specified url as the base 1557 * to expand any external references within the MoML file. 1558 * This method uses parse(URL, Reader). Note that this 1559 * bypasses the mechanism of parse(URL, URL) that returns 1560 * a previously parsed model. This method will always re-parse 1561 * using data from the stream. 1562 * @param base The base URL for relative references, or null if 1563 * not known. 1564 * @param input The stream from which to read XML. 1565 * @return The top-level composite entity of the Ptolemy II model, or 1566 * null if the file is not recognized as a MoML file. 1567 * @exception Exception If the parser fails. 1568 * @deprecated Use 1569 * {@link #parse(URL base, String systemID, InputStream input)} 1570 * for better error messages that include the name of the file being 1571 * read. 1572 */ 1573 @Deprecated 1574 public NamedObj parse(URL base, InputStream input) throws Exception { 1575 return parse(base, new InputStreamReader(input, 1576 java.nio.charset.Charset.defaultCharset())); 1577 } 1578 1579 /** Parse the given stream, using the specified url as the base 1580 * to expand any external references within the MoML file. 1581 * This method uses parse(URL, Reader). Note that this 1582 * bypasses the mechanism of parse(URL, URL) that returns 1583 * a previously parsed model. This method will always re-parse 1584 * using data from the stream. 1585 * @param base The base URL for relative references, or null if 1586 * not known. 1587 * @param systemID The URI of the document. 1588 * @param input The stream from which to read XML. 1589 * @return The top-level composite entity of the Ptolemy II model, or 1590 * null if the file is not recognized as a MoML file. 1591 * @exception Exception If the parser fails. 1592 */ 1593 public NamedObj parse(URL base, String systemID, InputStream input) 1594 throws Exception { 1595 return parse(base, systemID, new InputStreamReader(input, 1596 java.nio.charset.Charset.defaultCharset())); 1597 } 1598 1599 /** Parse the given stream, using the specified url as the base 1600 * The reader is wrapped in a BufferedReader before being used. 1601 * Note that this 1602 * bypasses the mechanism of parse(URL, URL) that returns 1603 * a previously parsed model. This method will always re-parse 1604 * using data from the stream. 1605 * @param base The base URL for relative references, or null if 1606 * not known. 1607 * @param reader The reader from which to read XML. 1608 * @return The top-level composite entity of the Ptolemy II model, or 1609 * null if the file is not recognized as a MoML file. 1610 * @exception Exception If the parser fails. 1611 * @deprecated Use {@link #parse(URL base, String systemID, Reader reader)} 1612 * for better error messages that include the name of the file being 1613 * read. 1614 */ 1615 @Deprecated 1616 public NamedObj parse(URL base, Reader reader) throws Exception { 1617 return parse(base, null, reader); 1618 } 1619 1620 /** Parse the given stream, using the specified url as the base 1621 * The reader is wrapped in a BufferedReader before being used. 1622 * Note that this 1623 * bypasses the mechanism of parse(URL, URL) that returns 1624 * a previously parsed model. This method will always re-parse 1625 * using data from the stream. 1626 * @param base The base URL for relative references, or null if 1627 * not known. 1628 * @param systemID The URI of the document. 1629 * @param reader The reader from which to read XML. 1630 * @return The top-level composite entity of the Ptolemy II model, or 1631 * null if the file is not recognized as a MoML file. 1632 * @exception Exception If the parser fails. 1633 */ 1634 public NamedObj parse(URL base, String systemID, Reader reader) 1635 throws Exception { 1636 base = FileUtilities.followRedirects(base); 1637 _base = base; 1638 1639 // Invoking a Vertx demo and then a Nashorn demo can result in 1640 // Ptolemy classes not being found. In particular, exporting 1641 // two models in a row by editing 1642 // $PTII/ptolemy/configs/models.txt so that it contains: 1643 // $CLASSPATH/ptolemy/actor/lib/vertx/demo/TokenTransmissionTime/Receiver.xml 1644 // $CLASSPATH/ptolemy/demo/Robot/RandomWalkIntruder.xml 1645 // and then running cd $PTII/ptolemy/vergil/basic/export/test/junit; make long_test 1646 // resulted in the second model failing to find Ptolemy classes from within Nashorn 1647 // See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/JSClassLoaderProblem 1648 if (Thread.currentThread().getContextClassLoader() == null) { 1649 Thread.currentThread() 1650 .setContextClassLoader(ClassLoader.getSystemClassLoader()); 1651 } 1652 1653 Reader buffered = new BufferedReader(reader); 1654 1655 try { 1656 // We allocate a new XmlParser each time so as to avoid leaks. 1657 _xmlParser = new XmlParser(); 1658 _xmlParser.setHandler(this); 1659 if (base == null) { 1660 _xmlParser.parse(systemID, null, buffered); 1661 } else { 1662 // If we have tmp.moml and tmp/tmp2.moml and tmp.moml 1663 // contains <entity name="tmp2" class="tmp.tmp2"> 1664 // then we want to be sure that we set _xmlFile properly 1665 // NOTE: I'm not sure if it is necessary to check to 1666 // see if _xmlFile is null before hand, but it seems 1667 // like it is safer to check before resetting it to null. 1668 boolean xmlFileWasNull = false; 1669 1670 if (_xmlFile == null) { 1671 xmlFileWasNull = true; 1672 _setXmlFile(new URL(base.toExternalForm())); 1673 } 1674 1675 try { 1676 _xmlParser.parse(FileUtilities.followRedirects(base) 1677 .toExternalForm(), null, buffered); 1678 } finally { 1679 if (xmlFileWasNull) { 1680 _setXmlFile(null); 1681 } 1682 } 1683 } 1684 } catch (CancelException ex) { 1685 // Parse operation cancelled. 1686 return null; 1687 } catch (Exception ex) { 1688 // If you change this code, try running 1689 // ptolemy.moml.test.MoMLParserLeak with the heap profiler 1690 // and look for leaks. 1691 if (_toplevel != null && _toplevel instanceof ComponentEntity) { 1692 try { 1693 ((ComponentEntity) _toplevel).setContainer(null); 1694 } catch (Throwable throwable2) { 1695 // Ignore. setContainer(null) might throw an exception 1696 // if there are deferrables, but we don't want to hide 1697 // the original exception. 1698 // This problem comes up with tests in 1699 // actor/gui/test/UserActorLibrary.tcl. 1700 } 1701 // Since the container is probably already null, then 1702 // the setContainer(null) call probably did not do anything. 1703 // so, we remove the object from the workspace so it 1704 // can get gc'd. 1705 // FIXME: perhaps we should do more of what 1706 // ComponentEntity.setContainer() does and remove the ports? 1707 try { 1708 _workspace.getWriteAccess(); 1709 _workspace.remove(_toplevel); 1710 } finally { 1711 _workspace.doneWriting(); 1712 } 1713 _toplevel = null; 1714 } 1715 1716 _paramsToParse.clear(); 1717 if (_scopeExtenders != null) { 1718 _scopeExtenders.clear(); 1719 } 1720 reset(); 1721 if (base != null) { 1722 purgeModelRecord(base); 1723 } 1724 throw ex; 1725 } finally { 1726 // Avoid memory leaks 1727 _xmlParser = null; 1728 buffered.close(); 1729 } 1730 1731 if (_toplevel == null) { 1732 // If we try to read a HSIF file but Ptolemy is not properly 1733 // configured, then we may end up here. 1734 throw new Exception( 1735 "Toplevel was null? Perhaps the xml does not contain " 1736 + "a Ptolemy model?\n base ='" + base 1737 + "',\n reader = '" + reader + "'"); 1738 } 1739 1740 // Add a parser attribute to the toplevel to indicate a parser 1741 // responsible for handling changes, unless there already is a 1742 // parser, in which case we just set the parser to this one. 1743 MoMLParser parser = ParserAttribute.getParser(_toplevel); 1744 1745 if (parser != this) { 1746 // Force the parser to be this one. 1747 ParserAttribute parserAttribute = (ParserAttribute) _toplevel 1748 .getAttribute("_parser", ParserAttribute.class); 1749 1750 if (parserAttribute == null) { 1751 parserAttribute = new ParserAttribute(_toplevel, "_parser"); 1752 } 1753 1754 parserAttribute.setParser(this); 1755 } 1756 1757 return _toplevel; 1758 } 1759 1760 /** Parse the given string, which contains MoML. 1761 * If there are external references in the MoML, they are interpreted 1762 * relative to the current working directory. 1763 * Note that this method attempts to read the user.dir system 1764 * property, which is not generally available in applets. Hence 1765 * it is probably not a good idea to use this method in applet code, 1766 * since it will probably fail outright. 1767 * @param text The string from which to read MoML. 1768 * @return The top-level composite entity of the Ptolemy II model. 1769 * @exception Exception If the parser fails. 1770 * @exception SecurityException If the user.dir system property is 1771 * not available. 1772 */ 1773 public NamedObj parse(String text) throws Exception { 1774 // Use the current working directory as a base. 1775 String cwd = StringUtilities.getProperty("user.dir"); 1776 1777 URL base = new File(cwd).toURI().toURL(); 1778 1779 return parse(base, new StringReader(text)); 1780 } 1781 1782 /** Parse the given string, which contains MoML, using the specified 1783 * base to evaluate relative references. 1784 * This method uses parse(URL, Reader). 1785 * @param base The base URL for relative references, or null if 1786 * not known. 1787 * @param text The string from which to read MoML. 1788 * @return The top-level composite entity of the Ptolemy II model. 1789 * @exception Exception If the parser fails. 1790 */ 1791 public NamedObj parse(URL base, String text) throws Exception { 1792 return parse(base, new StringReader(text)); 1793 } 1794 1795 /** Parse the file with the given name, which contains MoML. 1796 * If there are external references in the MoML, they are interpreted 1797 * relative to the current working directory. 1798 * 1799 * <p>If you have an absolute pathname, rather than calling 1800 * this method, you may want to try: 1801 * <pre> 1802 * CompositeActor toplevel = (CompositeActor) parser.parse(null, 1803 * new File(xmlFilename).toURL()); 1804 * </pre> 1805 * 1806 * <p>Note that this method attempts to read the user.dir system 1807 * property, which is not generally available in applets. Hence 1808 * it is probably not a good idea to use this method in applet code, 1809 * since it will probably fail outright. 1810 * 1811 * <p>If the file has already been parsed, then 1812 * return the model that previously parsed. Note that this 1813 * means that an application that opens and then closes 1814 * a model and expects to re-parse the XML when re-opening 1815 * should call purgeModelRecord() when closing it. 1816 * 1817 * @param filename The file name from which to read MoML. 1818 * @return The top-level composite entity of the Ptolemy II model. 1819 * @exception Exception If the parser fails. 1820 * @exception SecurityException If the user.dir system property is 1821 * not available. 1822 * @see #purgeModelRecord(String) 1823 * @see #purgeAllModelRecords() 1824 */ 1825 public NamedObj parseFile(String filename) throws Exception { 1826 // Use the current working directory as a base. 1827 String cwd = StringUtilities.getProperty("user.dir"); 1828 1829 URL base = new File(cwd).toURI().toURL(); 1830 1831 // Java's I/O is so lame that it can't find files in the current 1832 // working directory... 1833 File file = new File(filename); 1834 if (!file.exists()) { 1835 file = new File(new File(cwd), filename); 1836 } 1837 if (!file.exists()) { 1838 throw new FileNotFoundException("Could not find file \"" + filename 1839 + "\", also tried \"" + file + "\""); 1840 } 1841 return parse(base, file.toURI().toURL()); 1842 } 1843 1844 /** Handle a processing instruction. Processing instructions 1845 * are allowed in doc and configure elements, and are passed through 1846 * unchanged. In the case of the doc element, they will be stored 1847 * in the Documentation attribute. In the case of the configure 1848 * element, they will be passed to the configure() method 1849 * of the parent object. 1850 * @param target The name of the processing instruction. 1851 * @param data The body of the processing instruction. 1852 */ 1853 @Override 1854 public void processingInstruction(String target, String data) { 1855 if (_currentCharData != null) { 1856 _currentCharData.append("<?"); 1857 _currentCharData.append(target); 1858 _currentCharData.append(" "); 1859 _currentCharData.append(data); 1860 _currentCharData.append("?>"); 1861 } 1862 } 1863 1864 /** Purge all records of models opened. This is here 1865 * for testing only. 1866 * @see #purgeModelRecord(URL) 1867 * @see #resetAll() 1868 */ 1869 public static void purgeAllModelRecords() { 1870 _imports = null; 1871 } 1872 1873 /** Purge any record of a model opened from the specified 1874 * URL. The record will not be purged if the model is 1875 * a class definition that has child instances. 1876 * Note that you may also need to call {@link #reset()} so 1877 * that the _toplevel is reset on any parser. 1878 * @param url The URL. 1879 * @see #parse(URL, URL) 1880 * @see #purgeAllModelRecords() 1881 */ 1882 public static void purgeModelRecord(URL url) { 1883 if (_imports != null && url != null) { 1884 // Don't do this if the url is of a class 1885 // and there are instances!!!! 1886 WeakReference reference = (WeakReference) _imports.get(url); 1887 if (reference != null) { 1888 Object modelToPurge = reference.get(); 1889 // Check to see whether the model to 1890 // purge is a class with instances. 1891 if (modelToPurge instanceof Instantiable) { 1892 boolean keepTheModel = false; 1893 List children = ((Instantiable) modelToPurge).getChildren(); 1894 if (children != null) { 1895 Iterator childrenIterator = children.iterator(); 1896 while (childrenIterator.hasNext()) { 1897 WeakReference child = (WeakReference) childrenIterator 1898 .next(); 1899 if (child != null && child.get() != null) { 1900 keepTheModel = true; 1901 break; 1902 } 1903 } 1904 } 1905 if (keepTheModel) { 1906 return; 1907 } 1908 } 1909 } 1910 1911 _imports.remove(url); 1912 } 1913 } 1914 1915 /** Purge any record of a model opened from the specified 1916 * file name. 1917 * 1918 * <p>Note that this method attempts to read the user.dir system 1919 * property, which is not generally available in applets. Hence 1920 * it is probably not a good idea to use this method in applet code, 1921 * since it will probably fail outright. 1922 * 1923 * <p> Note that you may also need to call {@link #reset()} so 1924 * that the _toplevel is reset. 1925 * 1926 * @param filename The file name from which to read MoML. 1927 * @exception MalformedURLException If the file name cannot be converted to a URL. 1928 * @exception SecurityException If the user.dir system property is 1929 * not available. 1930 * @see #parse(URL, String) 1931 * @see #purgeAllModelRecords() 1932 */ 1933 public static void purgeModelRecord(String filename) 1934 throws MalformedURLException { 1935 // Use the current working directory as a base. 1936 String cwd = StringUtilities.getProperty("user.dir"); 1937 1938 // Java's I/O is so lame that it can't find files in the current 1939 // working directory... 1940 File file = new File(new File(cwd), filename); 1941 purgeModelRecord(file.toURI().toURL()); 1942 } 1943 1944 /** Reset the MoML parser. 1945 * Note that this method does not purge records of the models 1946 * that have been parsed. To completely reset the MoMLParser, 1947 * see {@link #resetAll()}. 1948 * @see #resetAll() 1949 * @see #purgeModelRecord(String) 1950 * @see #purgeAllModelRecords() 1951 */ 1952 public void reset() { 1953 _attributes = new HashMap(); 1954 _configureNesting = 0; 1955 _containers = new Stack(); 1956 _groupCount = 0; 1957 _linkRequests = null; 1958 _linkRequestStack = new Stack(); 1959 _deleteRequests = null; 1960 _deleteRequestStack = new Stack(); 1961 _current = null; 1962 _docNesting = 0; 1963 _externalEntities = new Stack(); 1964 _modified = false; 1965 _namespace = _DEFAULT_NAMESPACE; 1966 _namespaces = new Stack(); 1967 _namespaceTranslations = new Stack(); 1968 _overwriteOverrides = 0; 1969 _skipRendition = false; 1970 _skipElementIsNew = false; 1971 _ifElementStack.clear(); 1972 _ifElementStack.add(Integer.valueOf(0)); 1973 _skipElement = 0; 1974 _toplevel = null; 1975 1976 // Reset undo specific members 1977 _resetUndo(); 1978 } 1979 1980 /** Reset the MoML parser, forgetting about any previously parsed 1981 * models. This method differs from {@link #reset()} in that 1982 * this method does as complete a reset of the MoMLParser 1983 * as possible. Note that the static MoMLFilters are not reset, 1984 * but the MoMLParser optionally used by the filters is reset. 1985 * @see #purgeModelRecord(String) 1986 * @see #purgeAllModelRecords() 1987 */ 1988 public void resetAll() { 1989 purgeAllModelRecords(); 1990 reset(); 1991 _workspace = new Workspace(); 1992 if (_filterMoMLParser != null) { 1993 MoMLParser.purgeAllModelRecords(); 1994 _filterMoMLParser.reset(); 1995 _filterMoMLParser = new MoMLParser(_workspace); 1996 } 1997 } 1998 1999 /** Resolve an external entity. If the first argument is the 2000 * name of the MoML PUBLIC DTD ("-//UC Berkeley//DTD MoML 1//EN"), 2001 * then return a StringReader 2002 * that will read the locally cached version of this DTD 2003 * (the public variable MoML_DTD_1). Otherwise, return null, 2004 * which has the effect of deferring to Ælfred for 2005 * resolution of the URI. Derived classes may return a 2006 * a modified URI (a string), an InputStream, or a Reader. 2007 * In the latter two cases, the input character stream is 2008 * provided. 2009 * @param publicID The public identifier, or null if none was supplied. 2010 * @param systemID The system identifier. 2011 * @return Null, indicating to use the default system identifier. 2012 */ 2013 @Override 2014 public Object resolveEntity(String publicID, String systemID) { 2015 if (publicID != null && publicID.equals(MoML_PUBLIC_ID_1)) { 2016 // This is the generic MoML DTD. 2017 return new StringReader(MoML_DTD_1); 2018 } else { 2019 return null; 2020 } 2021 } 2022 2023 /** Given the name of a MoML class and a source URL, check to see 2024 * whether this class has already been instantiated, and if so, 2025 * return the previous instance. If the source is non-null, then 2026 * this finds an instance that has been previously opened by this 2027 * application from the same URL. If the source is null and 2028 * the class name is absolute (starting with a period), then 2029 * look for the class in the current top level. If the source 2030 * is null and the class name is relative, then look for 2031 * the class relative to the current context. 2032 * @param name The name of the MoML class to search for. 2033 * @param source The URL source 2034 * @return If the class has already been instantiated, return 2035 * the previous instance, otherwise return null. 2036 * @exception Exception If thrown while searching for the class 2037 */ 2038 public ComponentEntity searchForClass(String name, String source) 2039 throws Exception { 2040 if (_imports != null && source != null) { 2041 WeakReference reference = (WeakReference) _imports.get(source); 2042 Object possibleCandidate = null; 2043 2044 if (reference != null) { 2045 possibleCandidate = reference.get(); 2046 2047 if (possibleCandidate == null) { 2048 _imports.remove(source); 2049 } 2050 } 2051 2052 if (possibleCandidate instanceof ComponentEntity) { 2053 ComponentEntity candidate = (ComponentEntity) possibleCandidate; 2054 2055 // Check that the candidate is a class. 2056 if (candidate.isClassDefinition()) { 2057 // Check that the class name matches. 2058 // Only the last part, after any periods has to match. 2059 String realClassName = name; 2060 int lastPeriod = name.lastIndexOf("."); 2061 2062 if (lastPeriod >= 0 && name.length() > lastPeriod + 1) { 2063 realClassName = name.substring(lastPeriod + 1); 2064 } 2065 2066 String candidateClassName = candidate.getClassName(); 2067 lastPeriod = candidateClassName.lastIndexOf("."); 2068 2069 if (lastPeriod >= 0 2070 && candidateClassName.length() > lastPeriod + 1) { 2071 candidateClassName = candidateClassName 2072 .substring(lastPeriod + 1); 2073 } 2074 2075 if (candidateClassName.equals(realClassName)) { 2076 return candidate; 2077 } 2078 } 2079 } 2080 } 2081 2082 // Source has not been previously loaded. 2083 // Only if the source is null can we have a matching previous instance. 2084 if (source == null) { 2085 ComponentEntity candidate = _searchForEntity(name, _current); 2086 2087 if (candidate != null) { 2088 // Check that it's a class. 2089 if (candidate.isClassDefinition()) { 2090 return candidate; 2091 } 2092 } 2093 } 2094 2095 return null; 2096 } 2097 2098 /** Set the context for parsing. This can be used to associate this 2099 * parser with a pre-existing model, which can then be modified 2100 * via incremental parsing. This calls reset() and sets the top-level 2101 * entity to the top-level of the specified object. 2102 * <p> 2103 * Callers should be careful about calling this method and resetting 2104 * the modified flag to false when parsing moml that has been modified 2105 * by the backward compatibility filter. 2106 * It is safer to do something like: 2107 * <pre> 2108 * boolean modified = isModified(); 2109 * MoMLParser newParser = new MoMLParser(...); 2110 * newParser.setContext(context); 2111 * setModified(modified); 2112 * </pre> 2113 * @param context The context for parsing. 2114 */ 2115 public void setContext(NamedObj context) { 2116 reset(); 2117 _toplevel = context.toplevel(); 2118 _current = context; 2119 _originalContext = context; 2120 } 2121 2122 /** 2123 * Set the static default class loading strategy that will be used by all instances of this class. 2124 * @param classLoadingStrategy The class loading strategy. 2125 * @see #getDefaultClassLoadingStrategy() 2126 */ 2127 public static void setDefaultClassLoadingStrategy( 2128 ClassLoadingStrategy classLoadingStrategy) { 2129 _defaultClassLoadingStrategy = classLoadingStrategy; 2130 } 2131 2132 /** Get the the current static _defaultClassLoadingStrategy instance. 2133 * 2134 * @return the current static _defaultClassLoadingStrategy instance. 2135 * @see #setDefaultClassLoadingStrategy(ClassLoadingStrategy) 2136 */ 2137 public static ClassLoadingStrategy getDefaultClassLoadingStrategy() { 2138 return _defaultClassLoadingStrategy; 2139 } 2140 2141 /** Set the error handler to handle parsing errors. 2142 * Note that this method is static. The specified error handler 2143 * will handle all errors for any instance of this class. 2144 * @param handler The ErrorHandler to call. 2145 * @see #getErrorHandler() 2146 */ 2147 public static void setErrorHandler(ErrorHandler handler) { 2148 _handler = handler; 2149 } 2150 2151 /** Set the icon loader for all MoMLParsers. 2152 * @param loader The IconLoader for all MoMLParsers. 2153 * @see #getIconLoader() 2154 */ 2155 public static void setIconLoader(IconLoader loader) { 2156 _iconLoader = loader; 2157 } 2158 2159 /** Set the list of MoMLFilters used to translate names. 2160 * Note that this method is static. The specified MoMLFilters 2161 * will filter all MoML for any instances of this class. 2162 * 2163 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 2164 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 2165 * 2166 * <p>To avoid leaking memory, it is best if the MoMLParser is 2167 * created in a separate Workspace and this method is not called, instead 2168 * call {@link #setMoMLFilters(List, Workspace)}:</p> 2169 * <pre> 2170 * Workspace workspace = new Workspace("MyWorkspace"); 2171 * MoMLParser parser = new MoMLParser(workspace); 2172 * List myFilters = BackwardCompatibility.allFilters(); 2173 * MoMLParser.setMoMLFilters(myFilters, workspace); 2174 * </pre> 2175 * 2176 * @param filterList The List of MoMLFilters. 2177 * @see #addMoMLFilter(MoMLFilter filter) 2178 * @see #addMoMLFilter(MoMLFilter filter, Workspace workspace) 2179 * @see #getMoMLFilters() 2180 * @see #setMoMLFilters(List filterList, Workspace workspace) 2181 */ 2182 public static void setMoMLFilters(List filterList) { 2183 MoMLParser.setMoMLFilters(filterList, new Workspace("MoMLFilter")); 2184 } 2185 2186 /** Set the list of MoMLFilters used to translate names. 2187 * Note that this method is static. The specified MoMLFilters 2188 * will filter all MoML for any instances of this class. 2189 * 2190 * <p>To avoid leaking memory, if addMoMLFilter(), addMoMLFilters() 2191 * or setMoMLFilters() is called, then call setMoMLFilters(null).</p> 2192 * 2193 * <p>To avoid leaking memory, it is best if the MoMLParser is 2194 * created in a separate Workspace:</p> 2195 * <pre> 2196 * Workspace workspace = new Workspace("MyWorkspace"); 2197 * MoMLParser parser = new MoMLParser(workspace); 2198 * List myFilters = BackwardCompatibility.allFilters(); 2199 * MoMLParser.setMoMLFilters(myFilters, workspace); 2200 * </pre> 2201 * 2202 * @param filterList The List of MoMLFilters. 2203 * @param workspace MoMLFilters are passed a MoMLParser that is optionally 2204 * used by a filter. This parameter determines the Workspace in which 2205 * that MoMLFilter is created. To avoid memory leaks, typically the 2206 * MoMLFilter that is used to parse a model is created in a new workspace. 2207 * The MoMLFilters are static, so we need to pass in the Workspace from 2208 * the top level MoMLFilter. 2209 * @see #addMoMLFilter(MoMLFilter filter) 2210 * @see #addMoMLFilter(MoMLFilter filter, Workspace workspace) 2211 * @see #getMoMLFilters() 2212 * @see #setMoMLFilters(List filterList) 2213 */ 2214 public static void setMoMLFilters(List filterList, Workspace workspace) { 2215 _filterList = filterList; 2216 if (_filterList == null) { 2217 _filterMoMLParser = null; 2218 } else { 2219 if (_filterMoMLParser == null) { 2220 // FIXME: it seems like the MoMLParser should be created 2221 // in a particular Workspace, not just the default workspace 2222 // so that we can unload the Workspace? However, since 2223 // the filters are static, we are probably ok. 2224 _filterMoMLParser = new MoMLParser(workspace); 2225 } 2226 } 2227 } 2228 2229 /** Record whether the parsing of the moml modified the data. 2230 * If a MoMLFilter modifies the model by returning a different 2231 * value, then the MoMLFilter should call this method with a true 2232 * argument. 2233 * @param modified True if the data was modified while parsing. 2234 * @see #isModified() 2235 * @see MoMLFilter 2236 */ 2237 public static void setModified(boolean modified) { 2238 // NOTE: To see who sets this true, uncomment this: 2239 //if (modified == true) (new Exception()).printStackTrace(); 2240 _modified = modified; 2241 } 2242 2243 /** Set the top-level entity. This can be used to associate this 2244 * parser with a pre-existing model, which can then be modified 2245 * via incremental parsing. This calls reset(). 2246 * @param toplevel The top-level to associate with this parser. 2247 * @see #getToplevel() 2248 */ 2249 public void setToplevel(NamedObj toplevel) { 2250 reset(); 2251 _toplevel = toplevel; 2252 } 2253 2254 /** 2255 * Set the current context as undoable. If set to true, the next MoML 2256 * parsed will be able to be undone via a call to undo(). 2257 * 2258 * @param undoable The new Undoable value 2259 * @since Ptolemy II 2.1 2260 */ 2261 public void setUndoable(boolean undoable) { 2262 _undoEnabled = undoable; 2263 } 2264 2265 /** Start a document. This method is called just before the parser 2266 * attempts to read the first entity (the root of the document). 2267 * It is guaranteed that this will be the first method called. 2268 * In this implementation, this method resets some private variables, 2269 * and if there is a top level model associated with this parser, 2270 * sets it so that change requests are deferred rather than 2271 * executed. The change requests will be executed as a batch 2272 * in endDocument(). 2273 * @see #endDocument() 2274 */ 2275 @Override 2276 public void startDocument() { 2277 _paramsToParse.clear(); 2278 _missingClasses = null; 2279 if (_scopeExtenders != null) { 2280 _scopeExtenders.clear(); 2281 } 2282 _unrecognized = null; 2283 2284 // We assume that the data being parsed is MoML, unless we 2285 // get a publicID that doesn't match. 2286 // Authorize the user interface to offer the user the option 2287 // of skipping error reporting. 2288 if (_handler != null) { 2289 _handler.enableErrorSkipping(true); 2290 } 2291 2292 if (_toplevel != null) { 2293 _previousDeferStatus = _toplevel.isDeferringChangeRequests(); 2294 _toplevel.setDeferringChangeRequests(true); 2295 } else { 2296 // Make sure a default is provided. 2297 _previousDeferStatus = false; 2298 } 2299 2300 _linkRequests = null; 2301 _deleteRequests = null; 2302 _linkRequestStack.clear(); 2303 _deleteRequestStack.clear(); 2304 } 2305 2306 /** Start an element. 2307 * This is called at the beginning of each XML 2308 * element. By the time it is called, all of the attributes 2309 * for the element will already have been reported using the 2310 * attribute() method. Unrecognized elements are ignored. 2311 * @param elementName The element type name. 2312 * @exception XmlException If the element produces an error 2313 * in constructing the model. 2314 */ 2315 @Override 2316 public void startElement(String elementName) throws XmlException { 2317 boolean pushedLinkRequests = false; 2318 boolean pushedDeleteRequests = false; 2319 boolean pushedUndoContexts = false; 2320 boolean exceptionThrown = false; 2321 _namespacesPushed = false; 2322 2323 try { 2324 if (((Integer) _ifElementStack.peek()).intValue() > 1) { 2325 _ifElementStack 2326 .push(((Integer) _ifElementStack.pop()).intValue() + 1); 2327 } else if (_skipElement <= 0) { 2328 // If we are not skipping an element, then adjust the 2329 // configuration nesting and doc nesting counts accordingly. 2330 // This was illustrated by having the RemoveGraphicalClasses 2331 // filter remove the SketchedSource from sources.xml, 2332 // See test 1.3 in filter/test/RemoveGraphicalClasses.tcl 2333 if (_configureNesting > 0 || _docNesting > 0) { 2334 // Inside a configure or doc tag. First, check to see 2335 // whether this is another configure or doc tag. 2336 // Then simply replicate the element in the current 2337 // character buffer. 2338 if (elementName.equals("configure")) { 2339 // Count configure tags so that they can nest. 2340 _configureNesting++; 2341 } else if (elementName.equals("doc")) { 2342 // Count doc tags so that they can nest. 2343 _docNesting++; 2344 } 2345 2346 _currentCharData.append(_getCurrentElement(elementName)); 2347 _attributes.clear(); 2348 _attributeNameList.clear(); 2349 return; 2350 } 2351 } 2352 2353 if (_skipRendition) { 2354 return; 2355 } 2356 2357 _undoEnabled = _undoEnabled && _isUndoableElement(elementName); 2358 if (_undoContext != null) { 2359 _undoContexts.push(_undoContext); 2360 pushedUndoContexts = true; 2361 _undoEnabled = _undoEnabled 2362 && _undoContext.hasUndoableChildren(); 2363 } 2364 2365 // Create a new current context 2366 _undoContext = new UndoContext(_undoEnabled); 2367 2368 if (_undoDebug) { 2369 System.out.println("Current start element: " + elementName); 2370 } 2371 2372 if (_skipElement > 0 2373 || ((Integer) _ifElementStack.peek()).intValue() > 1) { 2374 if (elementName.equals(_skipElementName)) { 2375 // If attribute() found an element to skip, then 2376 // the first time we startElement(), we do not 2377 // want to increment _skipElement again in 2378 // startElement() because we already did it in 2379 // attribute(). 2380 if (_skipElementIsNew) { 2381 // After this, _skipElement no longer new. 2382 _skipElementIsNew = false; 2383 } else { 2384 // Nested element name. Have to count so we properly 2385 // close the skipping. 2386 _skipElement++; 2387 } 2388 } 2389 2390 return; 2391 } 2392 2393 // NOTE: The elements are alphabetical below... 2394 // NOTE: I considered using reflection to invoke a set of 2395 // methods with names that match the element names. However, 2396 // since we can't count on the XML parser to enforce the DTD, 2397 // this seems dangerous. It could result in being able to write 2398 // an XML that would call methods of this class that are not 2399 // intended to be called, simply by putting in an element 2400 // whose name matches the method name. So instead, we do 2401 // a dumb if...then...else.if... chain with string comparisons. 2402 // FIXME: Instead of doing all these string comparisons, do 2403 // a hash lookup. 2404 ////////////////////////////////////////////////////////////// 2405 //// class 2406 if (elementName.equals("class")) { 2407 String className = (String) _attributes.get("extends"); 2408 String entityName = (String) _attributes.get("name"); 2409 String source = (String) _attributes.get("source"); 2410 2411 _checkForNull(entityName, "No name for element \"class\""); 2412 2413 // For undo purposes need to know if the entity existed 2414 // already 2415 Entity entity = _searchForEntity(entityName, _current); 2416 boolean existedAlready = entity != null; 2417 2418 if (!existedAlready) { 2419 String version = (String) _attributes.get("version"); 2420 VersionSpecification versionSpec = _buildVersionSpecification( 2421 version); 2422 NamedObj candidate = _createEntity(className, versionSpec, 2423 entityName, source, true); 2424 2425 if (candidate instanceof Entity) { 2426 entity = (Entity) candidate; 2427 } else { 2428 throw new IllegalActionException(_current, 2429 "Attempt to create a class named " + entityName 2430 + " from a class that " 2431 + "is not a subclass of Entity: " 2432 + className); 2433 } 2434 } 2435 2436 // NOTE: The entity may be at the top level, in 2437 // which case _deleteRequests is null. 2438 if (_deleteRequests != null) { 2439 _deleteRequestStack.push(_deleteRequests); 2440 pushedDeleteRequests = true; 2441 } 2442 2443 _deleteRequests = new LinkedList(); 2444 2445 // NOTE: The entity may be at the top level, in 2446 // which case _linkRequests is null. 2447 if (_linkRequests != null) { 2448 _linkRequestStack.push(_linkRequests); 2449 pushedLinkRequests = true; 2450 } 2451 2452 _linkRequests = new LinkedList(); 2453 2454 if (_current != null) { 2455 _pushContext(); 2456 } else if (_toplevel == null) { 2457 // NOTE: Used to set _toplevel to newEntity, but 2458 // this isn't quite right because the entity may have a 2459 // composite name. 2460 _toplevel = entity.toplevel(); 2461 2462 // Ensure that if any change requests occur as a 2463 // consequence of adding items to this top level, 2464 // that execution of those change requests is deferred 2465 // until endDocument(). 2466 _toplevel.setDeferringChangeRequests(true); 2467 2468 // As early as possible, set URL attribute. 2469 // This is needed in case any of the parameters 2470 // refer to files whose location is relative 2471 // to the URL location. 2472 if (_xmlFile != null) { 2473 // Add a URL attribute to the toplevel to 2474 // indicate where it was read from. 2475 URIAttribute attribute = new URIAttribute(_toplevel, 2476 "_uri"); 2477 attribute.setURL(_xmlFile); 2478 } 2479 } 2480 2481 boolean converted = false; 2482 2483 if (!existedAlready) { 2484 entity.setClassDefinition(true); 2485 2486 // Adjust the classname and superclass of the object. 2487 // NOTE: This used to set the class name to entity.getFullName(), 2488 // and superclass to className. Now that we've consolidated 2489 // these, we set the class name to the value of "extends" 2490 // attribute that was used to create this. 2491 entity.setClassName(className); 2492 } else { 2493 // If the object is not already a class, then convert 2494 // it to one. 2495 if (!entity.isClassDefinition()) { 2496 entity.setClassDefinition(true); 2497 converted = true; 2498 } 2499 } 2500 2501 _current = entity; 2502 2503 _namespace = _DEFAULT_NAMESPACE; 2504 2505 if (_undoEnabled) { 2506 // Handle the undo aspect. 2507 if (existedAlready) { 2508 if (!converted) { 2509 _undoContext.appendUndoMoML( 2510 "<class name=\"" + entityName + "\" >\n"); 2511 2512 // Need to continue undoing and use an end tag 2513 _undoContext.appendClosingUndoMoML("</class>\n"); 2514 } else { 2515 // Converting from entity to class, so reverse this. 2516 _undoContext.appendUndoMoML( 2517 "<entity name=\"" + entityName + "\" >\n"); 2518 2519 // Need to continue undoing and use an end tag 2520 _undoContext.appendClosingUndoMoML("</entity>\n"); 2521 } 2522 2523 _undoContext.setChildrenUndoable(true); 2524 } else { 2525 _undoContext.appendUndoMoML("<deleteEntity name=\"" 2526 + entityName + "\" />\n"); 2527 2528 // Do not need to continue generating undo MoML 2529 // as the deleteEntity takes care of all 2530 // contained MoML 2531 _undoContext.setChildrenUndoable(false); 2532 _undoContext.setUndoable(false); 2533 2534 // Prevent any further undo entries for this context. 2535 _undoEnabled = false; 2536 } 2537 } 2538 2539 ////////////////////////////////////////////////////////////// 2540 //// configure 2541 } else if (elementName.equals("configure")) { 2542 _checkClass(_current, Configurable.class, 2543 "Element \"configure\" found inside an element that " 2544 + "does not implement Configurable. It is: " 2545 + _current); 2546 _configureSource = (String) _attributes.get("source"); 2547 _currentCharData = new StringBuffer(); 2548 2549 // Count configure tags so that they can nest. 2550 _configureNesting++; 2551 2552 ////////////////////////////////////////////////////////////// 2553 //// deleteEntity 2554 } else if (elementName.equals("deleteEntity")) { 2555 String entityName = (String) _attributes.get("name"); 2556 _checkForNull(entityName, 2557 "No name for element \"deleteEntity\""); 2558 2559 // Link is stored and processed last, but before deletions. 2560 DeleteRequest request = new DeleteRequest(_DELETE_ENTITY, 2561 entityName, null); 2562 2563 // Only defer if we are in a class, entity, or model context, 2564 // which is equivalent to the _current being an instance of 2565 // InstantiableNamedObj. 2566 if (_deleteRequests != null 2567 && _current instanceof InstantiableNamedObj) { 2568 _deleteRequests.add(request); 2569 } else { 2570 // Very likely, the context is null, in which 2571 // case the following will throw an exception. 2572 // We defer to it in case somehow a link request 2573 // is being made at the top level with a non-null 2574 // context (e.g. via a change request). 2575 request.execute(); 2576 } 2577 2578 // NOTE: deleteEntity is not supposed to have anything 2579 // inside it, so we do not push the context. 2580 ////////////////////////////////////////////////////////////// 2581 //// deletePort 2582 } else if (elementName.equals("deletePort")) { 2583 String portName = (String) _attributes.get("name"); 2584 _checkForNull(portName, "No name for element \"deletePort\""); 2585 2586 // The entity attribute is optional. 2587 String entityName = (String) _attributes.get("entity"); 2588 2589 // Delete the corresponding ParameterPort, if any. 2590 /* No longer needed. Now handled by ParameterPort. EAL 6/13/15 2591 Port toDelete = null; 2592 try { 2593 toDelete = _searchForPort(portName); 2594 } catch (XmlException ex) { 2595 // Ignore, there is no port by that name. 2596 } 2597 // Find the corresponding ParameterPort and delete it 2598 if (toDelete != null) { 2599 NamedObj container = toDelete.getContainer(); 2600 if (container != null && container instanceof Entity) { 2601 Attribute attribute = ((Entity) container) 2602 .getAttribute(portName); 2603 if (attribute != null 2604 && attribute instanceof PortParameter) { 2605 DeleteRequest request = new DeleteRequest( 2606 _DELETE_PROPERTY, attribute.getName(), null); 2607 // Only defer if we are in a class, entity, or 2608 // model context, which is equivalent to the 2609 // _current being an instance of 2610 // InstantiableNamedObj. 2611 if (_deleteRequests != null 2612 && _current instanceof InstantiableNamedObj) { 2613 _deleteRequests.add(request); 2614 } else { 2615 // Very likely, the context is null, in which 2616 // case the following will throw an exception. 2617 // We defer to it in case somehow a link request 2618 // is being made at the top level with a non-null 2619 // context (e.g. via a change request). 2620 request.execute(); 2621 } 2622 } 2623 } 2624 } 2625 */ 2626 2627 // Link is stored and processed last, but before deletions. 2628 DeleteRequest request = new DeleteRequest(_DELETE_PORT, 2629 portName, entityName); 2630 2631 // Only defer if we are in a class, entity, or model context, 2632 // which is equivalent to the _current being an instance of 2633 // InstantiableNamedObj. 2634 if (_deleteRequests != null 2635 && _current instanceof InstantiableNamedObj) { 2636 _deleteRequests.add(request); 2637 } else { 2638 // Very likely, the context is null, in which 2639 // case the following will throw an exception. 2640 // We defer to it in case somehow a link request 2641 // is being made at the top level with a non-null 2642 // context (e.g. via a change request). 2643 request.execute(); 2644 } 2645 2646 // NOTE: deletePort is not supposed to have anything 2647 // inside it, so we do not push the context. 2648 ////////////////////////////////////////////////////////////// 2649 //// deleteProperty 2650 } else if (elementName.equals("deleteProperty")) { 2651 String propName = (String) _attributes.get("name"); 2652 _checkForNull(propName, 2653 "No name for element \"deleteProperty\""); 2654 2655 // Link is stored and processed last, but before deletions. 2656 DeleteRequest request = new DeleteRequest(_DELETE_PROPERTY, 2657 propName, null); 2658 2659 // Only defer if we are in a class, entity, or model context, 2660 // which is equivalent to the _current being an instance of 2661 // InstantiableNamedObj. 2662 if (_deleteRequests != null 2663 && _current instanceof InstantiableNamedObj) { 2664 _deleteRequests.add(request); 2665 } else { 2666 // Very likely, the context is null, in which 2667 // case the following will throw an exception. 2668 // We defer to it in case somehow a link request 2669 // is being made at the top level with a non-null 2670 // context (e.g. via a change request). 2671 request.execute(); 2672 } 2673 2674 // Find the corresponding PortParameter and delete it 2675 /* No longer needed. Handled by ParameterPort. EAL 6/13/15 2676 Attribute toDelete = _searchForAttribute(propName); 2677 NamedObj container = toDelete.getContainer(); 2678 if (container != null && container instanceof Entity) { 2679 Port port = ((Entity) container).getPort(propName); 2680 if (port != null && port instanceof ParameterPort) { 2681 request = new DeleteRequest(_DELETE_PORT, 2682 port.getName(), container.getFullName()); 2683 // Only defer if we are in a class, entity, or 2684 // model context, which is equivalent to the 2685 // _current being an instance of 2686 // InstantiableNamedObj. 2687 if (_deleteRequests != null 2688 && _current instanceof InstantiableNamedObj) { 2689 _deleteRequests.add(request); 2690 } else { 2691 // Very likely, the context is null, in which 2692 // case the following will throw an exception. 2693 // We defer to it in case somehow a link request 2694 // is being made at the top level with a non-null 2695 // context (e.g. via a change request). 2696 request.execute(); 2697 } 2698 } 2699 } 2700 */ 2701 2702 // NOTE: deleteProperty is not supposed to have anything 2703 // inside it, so we do not push the context. 2704 ////////////////////////////////////////////////////////////// 2705 //// deleteRelation 2706 } else if (elementName.equals("deleteRelation")) { 2707 String relationName = (String) _attributes.get("name"); 2708 _checkForNull(relationName, 2709 "No name for element \"deleteRelation\""); 2710 2711 // Link is stored and processed last, but before deletions. 2712 DeleteRequest request = new DeleteRequest(_DELETE_RELATION, 2713 relationName, null); 2714 2715 // Only defer if we are in a class, entity, or model context, 2716 // which is equivalent to the _current being an instance of 2717 // InstantiableNamedObj. 2718 if (_deleteRequests != null 2719 && _current instanceof InstantiableNamedObj) { 2720 _deleteRequests.add(request); 2721 } else { 2722 // Very likely, the context is null, in which 2723 // case the following will throw an exception. 2724 // We defer to it in case somehow a link request 2725 // is being made at the top level with a non-null 2726 // context (e.g. via a change request). 2727 request.execute(); 2728 } 2729 2730 // NOTE: deleteRelation is not supposed to have anything 2731 // inside it, so we do not push the context. 2732 2733 ////////////////////////////////////////////////////////////// 2734 //// director 2735 } else if (elementName.equals("director")) { 2736 // NOTE: The director element is deprecated. 2737 // Use a property instead. This is kept here so that 2738 // this parser can read older MoML files. 2739 // NOTE: We do not check for a previously existing director. 2740 // There is presumably no harm in just creating a new one. 2741 String className = (String) _attributes.get("class"); 2742 _checkForNull(className, "No class for element \"director\""); 2743 2744 String dirName = (String) _attributes.get("name"); 2745 _checkForNull(dirName, "No name for element \"director\""); 2746 _checkClass(_current, CompositeActor.class, 2747 "Element \"director\" found inside an element that " 2748 + "is not a CompositeActor. It is: " 2749 + _current); 2750 2751 Object[] arguments = new Object[2]; 2752 arguments[0] = _current; 2753 arguments[1] = dirName; 2754 2755 // NamedObj container = _current; 2756 _pushContext(); 2757 2758 String version = (String) _attributes.get("version"); 2759 VersionSpecification versionSpec = _buildVersionSpecification( 2760 version); 2761 2762 Class newClass = _loadClass(className, versionSpec); 2763 2764 // NOTE: No propagation occurs here... Hopefully, deprecated 2765 // elements are not used with class structures. 2766 _current = _createInstance(newClass, arguments); 2767 _namespace = _DEFAULT_NAMESPACE; 2768 2769 ////////////////////////////////////////////////////////////// 2770 //// display 2771 } else if (elementName.equals("display")) { 2772 String displayName = (String) _attributes.get("name"); 2773 if (_current != null) { 2774 2775 // Propagate. 2776 Iterator derivedObjects = _current.getDerivedList() 2777 .iterator(); 2778 String currentName = _current.getName(); 2779 while (derivedObjects.hasNext()) { 2780 NamedObj derived = (NamedObj) derivedObjects.next(); 2781 2782 // If the derived object has the same 2783 // name as the old name, then we assume it 2784 // should change. 2785 if (derived.getName().equals(currentName)) { 2786 if (displayName != null) { 2787 if (displayName.equals(currentName)) { 2788 // The displayName is the same as the 2789 // name, so it should be reset to null. 2790 derived.setDisplayName(null); 2791 } else { 2792 derived.setDisplayName(displayName); 2793 } 2794 } 2795 } 2796 } 2797 2798 // Now change the display name. 2799 String oldDisplayName = _current.getDisplayName(); 2800 if (displayName != null) { 2801 if (displayName.equals(currentName) 2802 || displayName.equals("")) { 2803 // The displayName is the same as the 2804 // name, so it should be reset to null. 2805 _current.setDisplayName(null); 2806 } else { 2807 _current.setDisplayName(displayName); 2808 } 2809 2810 // Handle the undo aspect if needed 2811 if (_undoEnabled) { 2812 // Simply create in the undo MoML another display element. 2813 _undoContext.appendUndoMoML( 2814 "<display name=\"" + StringUtilities 2815 .escapeForXML(oldDisplayName) 2816 + "\"/>\n"); 2817 2818 // Do not need to continue generating undo MoML 2819 // as rename does not have any child elements 2820 _undoContext.setChildrenUndoable(false); 2821 } 2822 } 2823 } 2824 2825 ////////////////////////////////////////////////////////////// 2826 //// doc 2827 } else if (elementName.equals("doc")) { 2828 _currentDocName = (String) _attributes.get("name"); 2829 _currentCharData = new StringBuffer(); 2830 2831 // Count doc tags so that they can nest. 2832 _docNesting++; 2833 2834 ////////////////////////////////////////////////////////////// 2835 //// entity 2836 } else if (elementName.equals("entity") 2837 || elementName.equals("model")) { 2838 // NOTE: The "model" element is deprecated. It is treated 2839 // exactly as an entity. 2840 String className = (String) _attributes.get("class"); 2841 String entityName = (String) _attributes.get("name"); 2842 _checkForNull(entityName, "No name for element \"entity\""); 2843 2844 String source = (String) _attributes.get("source"); 2845 2846 // For undo purposes need to know if the entity existed 2847 // already 2848 Entity entity = _searchForEntity(entityName, _current); 2849 boolean existedAlready = entity != null; 2850 boolean converted = false; 2851 2852 if (existedAlready) { 2853 // Check whether it was previously a class, in which case 2854 // it is being converted to an entity. 2855 if (entity.isClassDefinition()) { 2856 entity.setClassDefinition(false); 2857 converted = true; 2858 } 2859 } else { 2860 String version = (String) _attributes.get("version"); 2861 VersionSpecification versionSpec = _buildVersionSpecification( 2862 version); 2863 2864 NamedObj candidate = _createEntity(className, versionSpec, 2865 entityName, source, false); 2866 2867 if (candidate instanceof Entity) { 2868 entity = (Entity) candidate; 2869 entity.setClassName(className); 2870 } else { 2871 throw new IllegalActionException(_current, 2872 "Attempt to create an entity named " 2873 + entityName + " from a class that " 2874 + "is not a subclass of Entity: " 2875 + className); 2876 } 2877 } 2878 2879 // NOTE: The entity may be at the top level, in 2880 // which case _deleteRequests is null. 2881 if (_deleteRequests != null) { 2882 _deleteRequestStack.push(_deleteRequests); 2883 pushedDeleteRequests = true; 2884 } 2885 2886 _deleteRequests = new LinkedList(); 2887 2888 // NOTE: The entity may be at the top level, in 2889 // which case _linkRequests is null. 2890 if (_linkRequests != null) { 2891 _linkRequestStack.push(_linkRequests); 2892 pushedLinkRequests = true; 2893 } 2894 2895 _linkRequests = new LinkedList(); 2896 2897 if (_current != null) { 2898 _pushContext(); 2899 } else if (_toplevel == null) { 2900 // NOTE: We used to set _toplevel to newEntity, but 2901 // this isn't quite right because the entity may have a 2902 // composite name. 2903 _toplevel = entity.toplevel(); 2904 2905 // Ensure that if any change requests occur as a 2906 // consequence of adding items to this top level, 2907 // that execution of those change requests is deferred 2908 // until endDocument(). 2909 _toplevel.setDeferringChangeRequests(true); 2910 2911 // As early as possible, set URL attribute. 2912 // This is needed in case any of the parameters 2913 // refer to files whose location is relative 2914 // to the URL location. 2915 if (_xmlFile != null) { 2916 // Add a URL attribute to the toplevel to 2917 // indicate where it was read from. 2918 URIAttribute attribute = new URIAttribute(_toplevel, 2919 "_uri"); 2920 attribute.setURL(_xmlFile); 2921 } 2922 } 2923 2924 _current = entity; 2925 2926 _namespace = _DEFAULT_NAMESPACE; 2927 2928 if (_undoEnabled) { 2929 // Handle the undo aspect. 2930 if (existedAlready) { 2931 if (!converted) { 2932 _undoContext.appendUndoMoML( 2933 "<entity name=\"" + entityName + "\" >\n"); 2934 2935 // Need to continue undoing and use an end tag 2936 _undoContext.appendClosingUndoMoML("</entity>\n"); 2937 } else { 2938 // Converted from a class to an entity, so reverse this. 2939 _undoContext.appendUndoMoML( 2940 "<class name=\"" + entityName + "\" >\n"); 2941 2942 // Need to continue undoing and use an end tag 2943 _undoContext.appendClosingUndoMoML("</class>\n"); 2944 } 2945 2946 _undoContext.setChildrenUndoable(true); 2947 } else { 2948 _undoContext.appendUndoMoML("<deleteEntity name=\"" 2949 + entityName + "\" />\n"); 2950 2951 // Do not need to continue generating undo MoML 2952 // as the deleteEntity takes care of all 2953 // contained MoML 2954 _undoContext.setChildrenUndoable(false); 2955 _undoContext.setUndoable(false); 2956 2957 // Prevent any further undo entries for this context. 2958 _undoEnabled = false; 2959 } 2960 } 2961 2962 ////////////////////////////////////////////////////////////// 2963 //// group 2964 } else if (elementName.equals("group")) { 2965 _groupCount++; 2966 String groupName = (String) _attributes.get("name"); 2967 2968 if (groupName != null) { 2969 if (groupName.equals("doNotOverwriteOverrides") 2970 && _overwriteOverrides <= 0) { 2971 // E.g., if the top-level <group> has name 2972 // "doNotOverwriteOverrides", then this will be 2973 // set to 1, and any nested groups will not 2974 // affect it. 2975 _overwriteOverrides = _groupCount; 2976 } else { 2977 // Defining a namespace. 2978 _namespaces.push(_namespace); 2979 _namespaceTranslations.push(_namespaceTranslationTable); 2980 _namespacesPushed = true; 2981 2982 if (groupName.equals("auto")) { 2983 _namespace = _AUTO_NAMESPACE; 2984 _namespaceTranslationTable = new HashMap(); 2985 } else { 2986 _namespace = groupName; 2987 } 2988 } 2989 } else { 2990 _namespaces.push(_DEFAULT_NAMESPACE); 2991 _namespaceTranslations.push(_namespaceTranslationTable); 2992 _namespacesPushed = true; 2993 _namespace = _DEFAULT_NAMESPACE; 2994 _namespaceTranslationTable = new HashMap(); 2995 } 2996 2997 // Link and unlink requests are processed when the 2998 // group closes. 2999 // NOTE: The entity may be at the top level, in 3000 // which case _deleteRequests is null. 3001 if (_deleteRequests != null) { 3002 _deleteRequestStack.push(_deleteRequests); 3003 pushedDeleteRequests = true; 3004 } 3005 3006 _deleteRequests = new LinkedList(); 3007 3008 // NOTE: The entity may be at the top level, in 3009 // which case _linkRequests is null. 3010 if (_linkRequests != null) { 3011 _linkRequestStack.push(_linkRequests); 3012 pushedLinkRequests = true; 3013 } 3014 3015 _linkRequests = new LinkedList(); 3016 3017 // Handle the undo aspect. 3018 if (_undoEnabled) { 3019 // NOTE: for groups with namespaces, rely on the names 3020 // already being part of undo MoML names instead of 3021 // tracking the namespace prefix 3022 _undoContext.appendUndoMoML("<group>\n"); 3023 3024 // Need to continue undoing and use an end tag 3025 _undoContext.appendClosingUndoMoML("</group>\n"); 3026 _undoContext.setChildrenUndoable(true); 3027 } 3028 3029 ////////////////////////////////////////////////////////////// 3030 //// input 3031 } else if (elementName.equals("input")) { 3032 String source = (String) _attributes.get("source"); 3033 _checkForNull(source, "No source for element \"input\""); 3034 // Uncomment this to trace outputs 3035 //System.out.println("MoMLParser: input: " + source); 3036 3037 boolean skip = false; 3038 3039 if (source.equals( 3040 "ptolemy/configs/properties/propertiesAttributeLibrary.xml")) { 3041 // Certain models such as the ee149 models like 3042 // eecs149/src/reading/io/Models/TimerInterrupt.xml 3043 // had a StateLibrary entity that included 3044 // '<input source="ptolemy/configs/properties/propertiesAttributeLibrary.xml"...' 3045 // This file is part of the properties work that was removed. 3046 // It is a bit of a bug that FSM Editors have a StateLibrary entity that 3047 // does inputs at all. 3048 3049 // Our fix here is to skip the input and mark this as modified. 3050 skip = true; 3051 // FIXME: this does not seem to have any effect? 3052 setModified(true); 3053 } 3054 3055 if (inputFileNamesToSkip != null) { 3056 // If inputFileNamesToSkip contains a string 3057 // that matches the end of source, then skip 3058 // parsing the source file. We use this for testing 3059 // configurations that have optional parts like 3060 // Matlab or javacomm. 3061 Iterator inputFileNames = inputFileNamesToSkip.iterator(); 3062 3063 while (inputFileNames.hasNext()) { 3064 String inputFileName = (String) inputFileNames.next(); 3065 3066 if (source.endsWith(inputFileName)) { 3067 if (!_printedMacOSSkippingMessage 3068 && inputFileName.equals("backtrack.xml") 3069 && System.getProperty("os.name") 3070 .equals("Mac OS X")) { 3071 _printedMacOSSkippingMessage = true; 3072 System.out.println("MoMLParser: Skipping" 3073 + source + " under Mac OS X. " 3074 + "This message is printed only printed once per run."); 3075 } 3076 skip = true; 3077 break; 3078 } 3079 } 3080 } 3081 3082 if (!skip) { 3083 // NOTE: The base attribute has been deprecated. Ignore. 3084 // Read external file in the current context, but with 3085 // a new parser. 3086 boolean modified = isModified(); 3087 MoMLParser newParser = new MoMLParser(_workspace, 3088 _classLoader); 3089 3090 newParser.setContext(_current); 3091 setModified(modified); 3092 3093 _parse(newParser, _base, source); 3094 } 3095 3096 ////////////////////////////////////////////////////////////// 3097 //// link 3098 } else if (elementName.equals("link")) { 3099 String portName = (String) _attributes.get("port"); 3100 3101 // Port can be null if we are linking two vertices. 3102 // _checkForNull(portName, "No port for element \"link\""); 3103 // Relation attribute now optional 3104 String relationName = (String) _attributes.get("relation"); 3105 String insertAtSpec = (String) _attributes.get("insertAt"); 3106 String insertInsideAtSpec = (String) _attributes 3107 .get("insertInsideAt"); 3108 3109 // Link is stored and processed last, but before deletions. 3110 LinkRequest request; 3111 3112 if (portName != null) { 3113 request = new LinkRequest(portName, relationName, 3114 insertAtSpec, insertInsideAtSpec); 3115 } else { 3116 String relation1Name = (String) _attributes 3117 .get("relation1"); 3118 String relation2Name = (String) _attributes 3119 .get("relation2"); 3120 request = new LinkRequest(relation1Name, relation2Name); 3121 } 3122 3123 if (_linkRequests != null) { 3124 _linkRequests.add(request); 3125 } else { 3126 // Very likely, the context is null, in which 3127 // case the following will throw an exception. 3128 // We defer to it in case somehow a link request 3129 // is being made at the top level with a non-null 3130 // context (e.g. via a change request). 3131 request.execute(); 3132 } 3133 3134 ////////////////////////////////////////////////////////////// 3135 //// port 3136 } else if (elementName.equals("port")) { 3137 String className = (String) _attributes.get("class"); 3138 String portName = (String) _attributes.get("name"); 3139 _checkForNull(portName, "No name for element \"port\""); 3140 3141 _checkClass(_current, Entity.class, 3142 "Element \"port\" found inside an element that " 3143 + "is not an Entity. It is: " + _current); 3144 3145 Entity container = (Entity) _current; 3146 3147 Class newClass = null; 3148 3149 if (className != null && !className.trim().equals("")) { 3150 newClass = _loadClass(className, null); 3151 } 3152 3153 Port port = container.getPort(portName); 3154 3155 // Flag used to generate correct undo MoML 3156 boolean alreadyExisted = port != null; 3157 3158 if (port != null) { 3159 if (newClass != null) { 3160 // Previously existing port with the specified name. 3161 _checkClass(port, newClass, 3162 "port named \"" + portName 3163 + "\" exists and is not an instance of " 3164 + className); 3165 } 3166 } else { 3167 // No previously existing port with this name. 3168 // First check that there will be no name collision 3169 // when this is propagated. Note that we need to 3170 // include all derived objects, irrespective of whether 3171 // they are locally changed. 3172 List derivedList = container.getDerivedList(); 3173 Iterator derivedObjects = derivedList.iterator(); 3174 3175 while (derivedObjects.hasNext()) { 3176 Entity derived = (Entity) derivedObjects.next(); 3177 3178 if (derived.getPort(portName) != null) { 3179 throw new IllegalActionException(container, 3180 "Cannot create port because a subclass or instance " 3181 + "contains a port with the same name: " 3182 + derived.getPort(portName) 3183 .getFullName()); 3184 } 3185 } 3186 3187 if (newClass == null) { 3188 // Classname is not given. Invoke newPort() on the 3189 // container. 3190 port = container.newPort(portName); 3191 3192 if (_topObjectsCreated != null 3193 && container == _originalContext) { 3194 _topObjectsCreated.add(port); 3195 } 3196 3197 // Propagate. 3198 // NOTE: Propagated ports will not use newPort(), 3199 // but rather will use clone. Classes that override 3200 // newPort() to perform special actions will no longer 3201 // work, possibly! 3202 port.propagateExistence(); 3203 } else { 3204 // Classname is given. 3205 Object[] arguments = new Object[2]; 3206 arguments[0] = container; 3207 arguments[1] = portName; 3208 port = (Port) _createInstance(newClass, arguments); 3209 3210 // Propagate. 3211 port.propagateExistence(); 3212 } 3213 } 3214 3215 _pushContext(); 3216 _current = port; 3217 3218 _namespace = _DEFAULT_NAMESPACE; 3219 3220 // Handle the undo aspect if needed 3221 if (_undoEnabled) { 3222 if (alreadyExisted) { 3223 // Simply create in the undo MoML the same port 3224 _undoContext.appendUndoMoML( 3225 "<port name=\"" + portName + "\" "); 3226 3227 // Also add in the class if given 3228 if (className != null) { 3229 _undoContext.appendUndoMoML( 3230 "class=\"" + className + "\" "); 3231 } 3232 3233 _undoContext.appendUndoMoML(">\n"); 3234 3235 // Need to continue undoing and use an end tag 3236 _undoContext.appendClosingUndoMoML("</port>\n"); 3237 _undoContext.setChildrenUndoable(true); 3238 } else { 3239 // Need to delete the port in the undo MoML 3240 _undoContext.appendUndoMoML( 3241 "<deletePort name=\"" + portName + "\" />\n"); 3242 3243 // Do not need to continue generating undo MoML 3244 // as the deletePort takes care of all 3245 // contained MoML 3246 _undoContext.setChildrenUndoable(false); 3247 _undoContext.setUndoable(false); 3248 3249 // Prevent any further undo entries for this context. 3250 _undoEnabled = false; 3251 } 3252 } 3253 3254 // NOTE: The direction attribute is deprecated, but 3255 // supported nonetheless. This is not propagated, but 3256 // hopefully deprecated attributes are not used with 3257 // the class mechanism. 3258 if (port instanceof IOPort) { 3259 String direction = (String) _attributes.get("direction"); 3260 3261 if (direction != null) { 3262 IOPort ioport = (IOPort) port; 3263 boolean isOutput = direction.equals("output") 3264 || direction.equals("both"); 3265 boolean isInput = direction.equals("input") 3266 || direction.equals("both"); 3267 3268 // If this object is a derived object, then its I/O status 3269 // cannot be changed. 3270 if (alreadyExisted && ioport 3271 .getDerivedLevel() < Integer.MAX_VALUE) { 3272 if (ioport.isInput() != isInput 3273 || ioport.isOutput() != isOutput) { 3274 throw new IllegalActionException(ioport, 3275 "Cannot change whether this port is " 3276 + "an input or output. That property is " 3277 + "fixed by the class definition."); 3278 } 3279 } 3280 3281 ioport.setOutput(isOutput); 3282 ioport.setInput(isInput); 3283 } 3284 } 3285 3286 ////////////////////////////////////////////////////////////// 3287 //// property 3288 } else if (elementName.equals("property")) { 3289 String createIfNecessary = (String) _attributes 3290 .get("createIfNecessary"); 3291 String className = (String) _attributes.get("class"); 3292 String propertyName = (String) _attributes.get("name"); 3293 _checkForNull(propertyName, "No name for element \"property\""); 3294 if (createIfNecessary != null 3295 && createIfNecessary.equals("true") && _current != null 3296 && propertyName != null 3297 && _current.getAttribute(propertyName) != null) { 3298 // The createIfNecessary="true" and the property 3299 // already exists 3300 } else { 3301 // If the createIfNecessary property is true and 3302 // there already is a property with this name, we 3303 // don't handle this property 3304 String value = (String) _attributes.get("value"); 3305 if (propertyName == null) { 3306 throw new InternalErrorException( 3307 "FindBugs: propertyName must not be null when calling _handlePropertyName()"); 3308 } else { 3309 _handlePropertyElement(className, propertyName, value); 3310 } 3311 } 3312 3313 ////////////////////////////////////////////////////////////// 3314 //// relation 3315 } else if (elementName.equals("relation")) { 3316 String className = (String) _attributes.get("class"); 3317 String relationName = (String) _attributes.get("name"); 3318 _checkForNull(relationName, "No name for element \"relation\""); 3319 _checkClass(_current, CompositeEntity.class, 3320 "Element \"relation\" found inside an element that " 3321 + "is not a CompositeEntity. It is: " 3322 + _current); 3323 3324 CompositeEntity container = (CompositeEntity) _current; 3325 Class newClass = null; 3326 3327 if (className != null) { 3328 newClass = _loadClass(className, null); 3329 } 3330 3331 Relation relation = container.getRelation(relationName); 3332 3333 // Flag used to generate correct undo MoML 3334 boolean alreadyExisted = relation != null; 3335 3336 if (relation == null) { 3337 // No previous relation with this name. 3338 // First check that there will be no name collision 3339 // when this is propagated. Note that we need to 3340 // include all derived objects, irrespective of whether 3341 // they are locally changed. 3342 List derivedList = container.getDerivedList(); 3343 Iterator derivedObjects = derivedList.iterator(); 3344 3345 while (derivedObjects.hasNext()) { 3346 CompositeEntity derived = (CompositeEntity) derivedObjects 3347 .next(); 3348 3349 if (derived.getRelation(relationName) != null) { 3350 throw new IllegalActionException(container, 3351 "Cannot create relation because a subclass or instance " 3352 + "contains a relation with the same name: " 3353 + derived.getRelation(relationName) 3354 .getFullName()); 3355 } 3356 } 3357 3358 NamedObj newRelation = null; 3359 _pushContext(); 3360 3361 if (newClass == null) { 3362 // No classname. Use the newRelation() method. 3363 newRelation = container.newRelation(relationName); 3364 3365 // Mark the contents of the new entity as being derived objects. 3366 // If we wouldn't do this the default attributes would be saved. 3367 _markContentsDerived(newRelation, 0); 3368 3369 if (_topObjectsCreated != null 3370 && container == _originalContext) { 3371 _topObjectsCreated.add(newRelation); 3372 } 3373 3374 // Propagate. 3375 // NOTE: Propagated relations will not use newRelation(), 3376 // but rather will use clone. Classes that rely 3377 // on newRelation(), will no longer work, possibly! 3378 newRelation.propagateExistence(); 3379 } else { 3380 Object[] arguments = new Object[2]; 3381 arguments[0] = _current; 3382 arguments[1] = relationName; 3383 newRelation = _createInstance(newClass, arguments); 3384 3385 // Propagate. 3386 newRelation.propagateExistence(); 3387 } 3388 3389 _namespace = _DEFAULT_NAMESPACE; 3390 _current = newRelation; 3391 3392 } else { 3393 // Previously existing relation with the specified name. 3394 if (newClass != null) { 3395 _checkClass(relation, newClass, 3396 "relation named \"" + relationName 3397 + "\" exists and is not an instance of " 3398 + className); 3399 } 3400 3401 _pushContext(); 3402 3403 _current = relation; 3404 _namespace = _DEFAULT_NAMESPACE; 3405 } 3406 3407 // Handle the undo aspect if needed 3408 if (_undoEnabled) { 3409 if (alreadyExisted) { 3410 // Simply create in the undo MoML the same relation 3411 _undoContext.appendUndoMoML( 3412 "<relation name=\"" + relationName + "\" "); 3413 3414 // Also add in the class if given 3415 if (className != null) { 3416 _undoContext.appendUndoMoML( 3417 "class=\"" + className + "\" "); 3418 } 3419 3420 _undoContext.appendUndoMoML(">\n"); 3421 3422 // Need to continue undoing and use an end tag 3423 _undoContext.appendClosingUndoMoML("</relation>\n"); 3424 _undoContext.setChildrenUndoable(true); 3425 } else { 3426 // Need to delete the realtion in the undo MoML 3427 _undoContext.appendUndoMoML("<deleteRelation name=\"" 3428 + relationName + "\" />\n"); 3429 3430 // Do not need to continue generating undo MoML 3431 // as the deleteRelation takes care of all 3432 // contained MoML 3433 _undoContext.setChildrenUndoable(false); 3434 _undoContext.setUndoable(false); 3435 3436 // Prevent any further undo entries for this context. 3437 _undoEnabled = false; 3438 } 3439 } 3440 3441 ////////////////////////////////////////////////////////////// 3442 //// rename 3443 } else if (elementName.equals("rename")) { 3444 String newName = (String) _attributes.get("name"); 3445 _checkForNull(newName, "No new name for element \"rename\""); 3446 3447 if (_current != null) { 3448 String oldName = _current.getName(); 3449 3450 // Ensure that derived objects aren't changed. 3451 if (!oldName.equals(newName) 3452 && _current.getDerivedLevel() < Integer.MAX_VALUE) { 3453 throw new IllegalActionException(_current, 3454 "Cannot change the name to " + newName 3455 + ". The name is fixed by the class definition."); 3456 } 3457 3458 // Propagate. Note that a rename in a derived class 3459 // could cause a NameDuplicationException. We have to 3460 // be able to unroll the changes if that occurs. 3461 Iterator derivedObjects = _current.getDerivedList() 3462 .iterator(); 3463 Set changedName = new HashSet(); 3464 HashMap changedClassName = new HashMap(); 3465 NamedObj derived = null; 3466 3467 try { 3468 while (derivedObjects.hasNext()) { 3469 derived = (NamedObj) derivedObjects.next(); 3470 3471 // If the derived object has the same 3472 // name as the old name, then we assume it 3473 // should change. 3474 if (derived.getName().equals(oldName)) { 3475 derived.setName(newName); 3476 changedName.add(derived); 3477 } 3478 3479 // Also need to modify the class name of 3480 // the instance or derived class if the 3481 // class or base class changes its name. 3482 if (derived instanceof Instantiable) { 3483 Instantiable parent = ((Instantiable) derived) 3484 .getParent(); 3485 3486 // This relies on the depth-first search 3487 // order of the getDerivedList() method 3488 // to be sure that the base class will 3489 // already be in the changedName set if 3490 // its name will change. 3491 if (parent != null && (parent == _current 3492 || changedName.contains(parent))) { 3493 String previousClassName = derived 3494 .getClassName(); 3495 int last = previousClassName 3496 .lastIndexOf(oldName); 3497 3498 if (last < 0) { 3499 throw new InternalErrorException( 3500 "Expected instance " 3501 + derived.getFullName() 3502 + " to have class name ending with " 3503 + oldName 3504 + " but its class name is " 3505 + previousClassName); 3506 } 3507 3508 String newClassName = newName; 3509 3510 if (last > 0) { 3511 newClassName = previousClassName 3512 .substring(0, last) + newName; 3513 } 3514 3515 derived.setClassName(newClassName); 3516 changedClassName.put(derived, 3517 previousClassName); 3518 } 3519 } 3520 } 3521 } catch (NameDuplicationException ex) { 3522 // Unravel the name changes before 3523 // rethrowing the exception. 3524 Iterator toUndo = changedName.iterator(); 3525 3526 while (toUndo.hasNext()) { 3527 NamedObj revert = (NamedObj) toUndo.next(); 3528 revert.setName(oldName); 3529 } 3530 3531 Iterator classNameFixes = changedClassName.entrySet() 3532 .iterator(); 3533 3534 while (classNameFixes.hasNext()) { 3535 Map.Entry revert = (Map.Entry) classNameFixes 3536 .next(); 3537 NamedObj toFix = (NamedObj) revert.getKey(); 3538 String previousClassName = (String) revert 3539 .getValue(); 3540 toFix.setClassName(previousClassName); 3541 } 3542 3543 throw new IllegalActionException(_current, ex, 3544 "Propagation to instance and/or derived class causes" 3545 + "name duplication: " 3546 + derived.getFullName()); 3547 } 3548 3549 _current.setName(newName); 3550 3551 // Handle the undo aspect if needed 3552 if (_undoEnabled) { 3553 // First try and rename in the parent context. 3554 // NOTE: this is a bit of a hack but is the only way 3555 // I could see of doing the rename without having to 3556 // change the semantics or location of the rename 3557 // element 3558 UndoContext parentContext = (UndoContext) _undoContexts 3559 .peek(); 3560 parentContext.applyRename(newName); 3561 3562 // Simply create in the undo MoML another rename 3563 _undoContext.appendUndoMoML( 3564 "<rename name=\"" + oldName + "\" />\n"); 3565 3566 // Do not need to continue generating undo MoML 3567 // as rename does not have any child elements 3568 _undoContext.setChildrenUndoable(false); 3569 } 3570 3571 // If _current is a class definition, then find 3572 // subclasses and instances and propagate the 3573 // change to the name of the 3574 // object they refer to. 3575 if (_current instanceof Instantiable 3576 && ((Instantiable) _current).isClassDefinition()) { 3577 List deferredFrom = ((Instantiable) _current) 3578 .getChildren(); 3579 3580 if (deferredFrom != null) { 3581 Iterator deferrers = deferredFrom.iterator(); 3582 3583 while (deferrers.hasNext()) { 3584 WeakReference reference = (WeakReference) deferrers 3585 .next(); 3586 InstantiableNamedObj deferrer = (InstantiableNamedObj) reference 3587 .get(); 3588 3589 if (deferrer != null) { 3590 // Got a live one. 3591 // Need to determine whether the name is 3592 // absolute or relative. 3593 String replacementName = newName; 3594 3595 if (deferrer.getClassName() 3596 .startsWith(".")) { 3597 replacementName = _current 3598 .getFullName(); 3599 } 3600 3601 deferrer.setClassName(replacementName); 3602 } 3603 } 3604 } 3605 } 3606 } 3607 3608 ////////////////////////////////////////////////////////////// 3609 //// rendition 3610 } else if (elementName.equals("rendition")) { 3611 // NOTE: The rendition element is deprecated. 3612 // Use an icon property instead. 3613 // This ignores everything inside it. 3614 _skipRendition = true; 3615 3616 ////////////////////////////////////////////////////////////// 3617 //// unlink 3618 } else if (elementName.equals("unlink")) { 3619 String portName = (String) _attributes.get("port"); 3620 3621 // Port may not be specified if we are unlinking two vertices. 3622 // _checkForNull(portName, "No port for element \"unlink\""); 3623 String relationName = (String) _attributes.get("relation"); 3624 String indexSpec = (String) _attributes.get("index"); 3625 String insideIndexSpec = (String) _attributes 3626 .get("insideIndex"); 3627 3628 // Unlink is stored and processed last. 3629 UnlinkRequest request; 3630 3631 if (portName != null) { 3632 request = new UnlinkRequest(portName, relationName, 3633 indexSpec, insideIndexSpec); 3634 } else { 3635 String relation1Name = (String) _attributes 3636 .get("relation1"); 3637 String relation2Name = (String) _attributes 3638 .get("relation2"); 3639 request = new UnlinkRequest(relation1Name, relation2Name); 3640 } 3641 3642 if (_linkRequests != null) { 3643 _linkRequests.add(request); 3644 } else { 3645 // Very likely, the context is null, in which 3646 // case the following will throw an exception. 3647 // We defer to it in case somehow a link request 3648 // is being made at the top level with a non-null 3649 // context (e.g. via a change request). 3650 request.execute(); 3651 } 3652 3653 ////////////////////////////////////////////////////////////// 3654 //// vertex 3655 } else if (elementName.equals("vertex")) { 3656 String vertexName = (String) _attributes.get("name"); 3657 _checkForNull(vertexName, "No name for element \"vertex\""); 3658 3659 _checkClass(_current, Relation.class, 3660 "Element \"vertex\" found inside an element that " 3661 + "is not a Relation. It is: " + _current); 3662 3663 // For undo need to know if a previous vertex attribute 3664 // with this name existed, and if so its expression 3665 Vertex previous = (Vertex) _current.getAttribute(vertexName); 3666 String previousValue = null; 3667 3668 if (previous != null) { 3669 previousValue = previous.getExpression(); 3670 } 3671 3672 // No need to check for name collision on propagated 3673 // objects because Vertex is a singleton. 3674 Vertex vertex = previous; 3675 3676 // Create a new vertex only if it didn't previously exist. 3677 if (vertex == null) { 3678 vertex = new Vertex((Relation) _current, vertexName); 3679 3680 // Propagate. 3681 vertex.propagateExistence(); 3682 } 3683 3684 // Deal with setting the location. 3685 String value = (String) _attributes.get("value"); 3686 3687 // If value is null or same as before, then there is 3688 // nothing to do. 3689 if (value != null && !value.equals(previousValue)) { 3690 vertex.setExpression(value); 3691 3692 // Propagate to derived classes and instances. 3693 try { 3694 // Propagate. This has the side effect of marking the 3695 // object overridden. 3696 vertex.propagateValue(); 3697 _paramsToParse.add(vertex); 3698 } catch (IllegalActionException ex) { 3699 // Propagation failed. Restore previous value. 3700 vertex.setExpression(previousValue); 3701 throw ex; 3702 } 3703 } 3704 3705 _pushContext(); 3706 3707 _current = vertex; 3708 _namespace = _DEFAULT_NAMESPACE; 3709 3710 if (_undoEnabled) { 3711 _undoContext.appendUndoMoML( 3712 "<vertex name=\"" + vertexName + "\" "); 3713 3714 if (previousValue != null) { 3715 _undoContext.appendUndoMoML( 3716 "value=\"" + previousValue + "\" "); 3717 } 3718 3719 _undoContext.appendUndoMoML(">\n"); 3720 3721 // The Vertex element can have children 3722 _undoContext.setChildrenUndoable(true); 3723 _undoContext.appendClosingUndoMoML("</vertex>\n"); 3724 } 3725 3726 ////////////////////////////////////////////////////////////// 3727 //// if 3728 } else if (elementName.equals("if")) { 3729 String name = _current.uniqueName("_tempVariable"); 3730 Class variableClass = Class 3731 .forName("ptolemy.data.expr.Variable"); 3732 Object[] arguments = new Object[2]; 3733 arguments[0] = _current; 3734 arguments[1] = name; 3735 Settable variable = (Settable) _createInstance(variableClass, 3736 arguments); 3737 3738 String expression = (String) _attributes.get("test"); 3739 if (expression == null) { 3740 throw new IllegalActionException(_current, 3741 "<if> element must have the \"test\" property " 3742 + "which specifies the expression to test."); 3743 } 3744 variable.setExpression(expression); 3745 3746 Object token = variableClass.getMethod("getToken", new Class[0]) 3747 .invoke(variable, new Object[0]); 3748 Class tokenClass = token.getClass(); 3749 Boolean value = (Boolean) tokenClass 3750 .getMethod("booleanValue", new Class[0]) 3751 .invoke(token, new Object[0]); 3752 3753 ((Attribute) variable).setContainer(null); 3754 3755 if (!value.booleanValue()) { 3756 _ifElementStack.push(Integer.valueOf(2)); 3757 } 3758 } else { 3759 // Unrecognized element name. Collect it. 3760 if (_unrecognized == null) { 3761 _unrecognized = new LinkedList(); 3762 } 3763 3764 _unrecognized.add(elementName); 3765 } 3766 3767 ////////////////////////////////////////////////////////////// 3768 //// failure 3769 } catch (InvocationTargetException ex) { 3770 exceptionThrown = true; 3771 3772 // A constructor or method invoked via reflection has 3773 // triggered an exception. 3774 if (_handler != null) { 3775 int reply = _handler.handleError( 3776 _getCurrentElement(elementName), _current, 3777 ex.getTargetException()); 3778 3779 if (reply == ErrorHandler.CONTINUE) { 3780 _attributes.clear(); 3781 _attributeNameList.clear(); 3782 _skipElement = 1; 3783 _skipElementName = elementName; 3784 return; 3785 } else if (reply == ErrorHandler.CANCEL) { 3786 // NOTE: Since we have to throw an XmlException for 3787 // the exception to be properly handled, we communicate 3788 // that it is a user cancellation with the special 3789 // string pattern "*** Canceled." in the message. 3790 throw new XmlException("*** Canceled.", 3791 _currentExternalEntity(), _getLineNumber(), 3792 _getColumnNumber()); 3793 } 3794 } 3795 3796 // No handler. 3797 throw new XmlException("XML element \"" + elementName 3798 + "\" triggers exception:\n " + ex.getTargetException(), 3799 _currentExternalEntity(), _getLineNumber(), 3800 _getColumnNumber(), ex.getTargetException()); 3801 } catch (Exception ex) { 3802 exceptionThrown = true; 3803 3804 if (_handler != null) { 3805 int reply = _handler.handleError( 3806 _getCurrentElement(elementName), _current, ex); 3807 3808 if (reply == ErrorHandler.CONTINUE) { 3809 _attributes.clear(); 3810 _attributeNameList.clear(); 3811 _skipElement = 1; 3812 _skipElementName = elementName; 3813 return; 3814 } else if (reply == ErrorHandler.CANCEL) { 3815 // Restore the status of change requests. 3816 // Execute any change requests that might have been queued 3817 // as a consequence of this change request. 3818 if (_toplevel != null) { 3819 // Set the top level back to the default 3820 // found in startDocument. 3821 _toplevel.setDeferringChangeRequests( 3822 _previousDeferStatus); 3823 _toplevel.executeChangeRequests(); 3824 } 3825 3826 // NOTE: Since we have to throw an XmlException for 3827 // the exception to be properly handled, we communicate 3828 // that it is a user cancellation with the special 3829 // string pattern "*** Canceled." in the message. 3830 throw new XmlException("*** Canceled.", 3831 _currentExternalEntity(), _getLineNumber(), 3832 _getColumnNumber()); 3833 } 3834 } 3835 3836 // There is no handler. 3837 // Restore the status of change requests. 3838 // Execute any change requests that might have been queued 3839 // as a consequence of this change request. 3840 if (_toplevel != null) { 3841 // Set the top level back to the default 3842 // found in startDocument. 3843 _toplevel.setDeferringChangeRequests(_previousDeferStatus); 3844 _toplevel.executeChangeRequests(); 3845 } 3846 3847 if (ex instanceof XmlException) { 3848 throw (XmlException) ex; 3849 } else { 3850 throw new XmlException( 3851 "XML element \"" + elementName 3852 + "\" triggers exception.", 3853 _currentExternalEntity(), _getLineNumber(), 3854 _getColumnNumber(), ex); 3855 } 3856 } finally { 3857 _attributes.clear(); 3858 _attributeNameList.clear(); 3859 3860 // If an exception was thrown, then restore all stacks 3861 // by popping off them anything that was pushed. 3862 if (exceptionThrown) { 3863 if (pushedDeleteRequests) { 3864 try { 3865 _deleteRequests = (List) _deleteRequestStack.pop(); 3866 } catch (EmptyStackException ex) { 3867 // We are back at the top level. 3868 _deleteRequests = null; 3869 } 3870 } 3871 3872 if (pushedLinkRequests) { 3873 try { 3874 _linkRequests = (List) _linkRequestStack.pop(); 3875 } catch (EmptyStackException ex) { 3876 // We are back at the top level. 3877 _linkRequests = null; 3878 } 3879 } 3880 3881 if (_namespacesPushed) { 3882 try { 3883 _namespace = (String) _namespaces.pop(); 3884 3885 _namespaceTranslationTable = (Map) _namespaceTranslations 3886 .pop(); 3887 } catch (EmptyStackException ex) { 3888 _namespace = _DEFAULT_NAMESPACE; 3889 } 3890 } 3891 3892 if (pushedUndoContexts) { 3893 try { 3894 _undoContext = (UndoContext) _undoContexts.pop(); 3895 } catch (EmptyStackException ex) { 3896 // This should not occur, but if it does, 3897 // we don't want _undoContext set to null. 3898 // Leave it as it is so we don't lose undo 3899 // information. 3900 } 3901 } 3902 } 3903 } 3904 } 3905 3906 /** Handle the start of an external entity. This pushes the stack so 3907 * that error reporting correctly reports the external entity that 3908 * causes the error. 3909 * @param systemID The URI for the external entity. 3910 */ 3911 @Override 3912 public void startExternalEntity(String systemID) { 3913 // NOTE: The Microstar XML parser incorrectly passes the 3914 // HTML file for the first external entity, rather than 3915 // XML file. So error messages typically refer to the wrong file. 3916 _externalEntities.push(systemID); 3917 } 3918 3919 /** Get the top objects list. The top objects list 3920 * is a list of top-level objects that this parser has 3921 * created. 3922 * @return The list of top objects created since 3923 * clearTopObjectsList() was called, or null if it has 3924 * not been called. 3925 * @see #clearTopObjectsList() 3926 */ 3927 public List topObjectsCreated() { 3928 return _topObjectsCreated; 3929 } 3930 3931 /////////////////////////////////////////////////////////////////// 3932 //// public members //// 3933 3934 /** The standard MoML DTD, represented as a string. This is used 3935 * to parse MoML data when a compatible PUBLIC DTD is specified. 3936 * NOTE: This DTD includes a number of elements that are deprecated. 3937 * They are included here for backward compatibility. See the MoML 3938 * chapter of the Ptolemy II design document for a view of the 3939 * current (nondeprecated) DTD. 3940 * CHANGE from Triquetrum : add version attributes for director, entity & property 3941 */ 3942 public static String MoML_DTD_1 = "<!ELEMENT model (class | configure | deleteEntity | deletePort | deleteRelation | director | display | doc | entity | group | import | input | link | property | relation | rename | rendition | unlink)*> <!ATTLIST model name CDATA #REQUIRED class CDATA #IMPLIED> <!ELEMENT class (class | configure | deleteEntity | deletePort | deleteRelation | director | display | doc | entity | group | import | input | link | port | property | relation | rename | rendition | unlink)*> <!ATTLIST class name CDATA #REQUIRED extends CDATA #IMPLIED source CDATA #IMPLIED> <!ELEMENT configure (#PCDATA)> <!ATTLIST configure source CDATA #IMPLIED> <!ELEMENT deleteEntity EMPTY> <!ATTLIST deleteEntity name CDATA #REQUIRED> <!ELEMENT deletePort EMPTY> <!ATTLIST deletePort name CDATA #REQUIRED> <!ELEMENT deleteProperty EMPTY> <!ATTLIST deleteProperty name CDATA #REQUIRED> <!ELEMENT deleteRelation EMPTY> <!ATTLIST deleteRelation name CDATA #REQUIRED> <!ELEMENT director (configure | doc | property)*> <!ATTLIST director name CDATA \"director\" class CDATA #REQUIRED version CDATA #IMPLIED> <!ELEMENT display EMPTY> <!ATTLIST display name CDATA #REQUIRED> <!ELEMENT doc (#PCDATA)> <!ATTLIST doc name CDATA #IMPLIED> <!ELEMENT entity (class | configure | deleteEntity | deletePort | deleteRelation | director | display | doc | entity | group | import | input | link | port | property | relation | rename | rendition | unlink)*> <!ATTLIST entity name CDATA #REQUIRED class CDATA #IMPLIED source CDATA #IMPLIED version CDATA #IMPLIED> <!ELEMENT group ANY> <!ATTLIST group name CDATA #IMPLIED> <!ELEMENT import EMPTY> <!ATTLIST import source CDATA #REQUIRED base CDATA #IMPLIED> <!ELEMENT input EMPTY> <!ATTLIST input source CDATA #REQUIRED base CDATA #IMPLIED> <!ELEMENT link EMPTY> <!ATTLIST link insertAt CDATA #IMPLIED insertInsideAt CDATA #IMPLIED port CDATA #IMPLIED relation CDATA #IMPLIED relation1 CDATA #IMPLIED relation2 CDATA #IMPLIED vertex CDATA #IMPLIED> <!ELEMENT location EMPTY> <!ATTLIST location value CDATA #REQUIRED> <!ELEMENT port (configure | display | doc | property | rename)*> <!ATTLIST port class CDATA #IMPLIED name CDATA #REQUIRED> <!ELEMENT property (configure | display | doc | property | rename)*> <!ATTLIST property class CDATA #IMPLIED name CDATA #REQUIRED value CDATA #IMPLIED version CDATA #IMPLIED> <!ELEMENT relation (configure | display | doc | property | rename | vertex)*> <!ATTLIST relation name CDATA #REQUIRED class CDATA #IMPLIED> <!ELEMENT rename EMPTY> <!ATTLIST rename name CDATA #REQUIRED> <!ELEMENT rendition (configure | location | property)*> <!ATTLIST rendition class CDATA #REQUIRED> <!ELEMENT unlink EMPTY> <!ATTLIST unlink index CDATA #IMPLIED insideIndex CDATA #IMPLIED port CDATA #REQUIRED relation CDATA #IMPLIED> <!ELEMENT vertex (configure | display | doc | location | property | rename)*> <!ATTLIST vertex name CDATA #REQUIRED pathTo CDATA #IMPLIED value CDATA #IMPLIED>"; 3943 3944 // NOTE: The master file for the above DTD is at 3945 // $PTII/ptolemy/moml/MoML_1.dtd. If modified, it needs to be also 3946 // updated at ptweb/xml/dtd/MoML_1.dtd. 3947 3948 /** The public ID for version 1 MoML. */ 3949 public static String MoML_PUBLIC_ID_1 = "-//UC Berkeley//DTD MoML 1//EN"; 3950 3951 /** List of Strings that name files to be skipped. 3952 * This variable is used primarily for testing configurations. 3953 * The value of this variable is a List of Strings, where each 3954 * element names a file name that should _not_ be loaded if 3955 * it is encountered in an input statement. 3956 */ 3957 public static List<String> inputFileNamesToSkip = null; 3958 3959 /////////////////////////////////////////////////////////////////// 3960 //// protected methods //// 3961 3962 /** Get the the URI for the current external entity. 3963 * @return A string giving the URI of the external entity being read, 3964 * or null if none. 3965 */ 3966 protected String _currentExternalEntity() { 3967 try { 3968 return (String) _externalEntities.peek(); 3969 } catch (EmptyStackException ex) { 3970 return null; 3971 } 3972 } 3973 3974 /////////////////////////////////////////////////////////////////// 3975 //// private methods //// 3976 3977 /** Add all (deeply) contained instances of Settable to the 3978 * _paramsToParse list, which will ensure that they get validated. 3979 * @param object The object to be scanned for Settables. 3980 */ 3981 private void _addParamsToParamsToParse(NamedObj object) { 3982 Iterator objects = object.lazyContainedObjectsIterator(); 3983 3984 while (objects.hasNext()) { 3985 NamedObj containedObject = (NamedObj) objects.next(); 3986 3987 if (containedObject instanceof Settable) { 3988 _paramsToParse.add((Settable) containedObject); 3989 } 3990 3991 _addParamsToParamsToParse(containedObject); 3992 } 3993 } 3994 3995 /** Attempt to find a MoML class from an external file. 3996 * If there is no source defined, then search for the file 3997 * relative to the classpath. 3998 * @param className The class name. 3999 * @param versionSpec 4000 * @param source The source as specified in the XML. 4001 * @return The class definition. 4002 */ 4003 private ComponentEntity _attemptToFindMoMLClass(String className, 4004 VersionSpecification versionSpec, String source) throws Exception { 4005 String classAsFile = null; 4006 String altClassAsFile = null; 4007 ComponentEntity reference = null; 4008 // From Triquetrum : also consider loading actor classes via the pluggable _classLoadingStrategy, 4009 // to allow OSGi-based dynamic loading strategies. 4010 try { 4011 VersionSpecification _vSpec = versionSpec != null ? versionSpec 4012 : _defaultVersionSpecification; 4013 reference = _defaultClassLoadingStrategy 4014 .loadActorOrientedClass(className, _vSpec); 4015 } catch (Exception e) { 4016 // ignore here, just means we need to look further to find the moml class 4017 } 4018 if (reference != null) { 4019 return reference; 4020 } 4021 4022 if (source == null) { 4023 // No source defined. Use the classpath. 4024 // First, replace all periods in the class name 4025 // with slashes, and then append a ".xml". 4026 // NOTE: This should perhaps be handled by the 4027 // class loader, but it seems rather complicated to 4028 // do that. 4029 // Search for the .xml file before searching for the .moml 4030 // file. .moml files are obsolete, and we should probably 4031 // not bother searching for them at all. 4032 classAsFile = className.replace('.', '/') + ".xml"; 4033 4034 // RIM uses .moml files, so leave them in. 4035 altClassAsFile = className.replace('.', '/') + ".moml"; 4036 } else { 4037 // Source is given. 4038 classAsFile = source; 4039 } 4040 // First check to see whether the object has been previously loaded. 4041 URL url = null; 4042 try { 4043 url = fileNameToURL(classAsFile, _base); 4044 if (_imports != null) { 4045 WeakReference possiblePrevious = (WeakReference) _imports 4046 .get(url); 4047 NamedObj previous = null; 4048 if (possiblePrevious != null) { 4049 previous = (NamedObj) possiblePrevious.get(); 4050 if (previous == null) { 4051 _imports.remove(url); 4052 } 4053 } 4054 if (previous instanceof ComponentEntity) { 4055 // NOTE: In theory, we should not even have to 4056 // check whether the file has been updated, because 4057 // if changes were made to model since it was loaded, 4058 // they should have been propagated. 4059 return (ComponentEntity) previous; 4060 } 4061 } 4062 } catch (Exception ex) { 4063 // An exception will be thrown if the class is not 4064 // found under the specified file name. Below we 4065 // will try again under the alternate file name. 4066 } 4067 4068 // Read external model definition in a new parser, 4069 // rather than in the current context. 4070 MoMLParser newParser = new MoMLParser(_workspace, _classLoader); 4071 4072 NamedObj candidateReference = null; 4073 4074 try { 4075 candidateReference = _findOrParse(newParser, _base, classAsFile, 4076 className, source); 4077 } catch (Exception ex2) { 4078 url = null; 4079 // Try the alternate file, if it's not null. 4080 if (altClassAsFile != null) { 4081 try { 4082 url = fileNameToURL(altClassAsFile, _base); 4083 } catch (Exception ex) { 4084 // Throw the original exception, which is likely to have 4085 // the real reason that the file is missing. 4086 // For example, if loading a .xml file fails because of 4087 // a missing class, then we should report that message 4088 // instead of looking for a .moml file. 4089 // See test 32.1 in test/MoMLParser.tcl that reads in 4090 // test/AltFileNameExceptionTest.xml 4091 // It would be nice to have both messages displayed, but 4092 // it would be ugly. 4093 4094 // FIXME: The tricky question is: if loading both the .xml 4095 // and the .moml file fail, then what should be reported? 4096 // If the .xml file is present and loading it fails, then 4097 // any errors associated with the loading should be reported. 4098 // If the .xml file is not present and the .moml file is 4099 // present, then any errors associated with loading the 4100 // .moml file should be reported. 4101 // If neither file is present, then a FileNotFoundException 4102 // should be thrown that lists both files. 4103 throw ex2; 4104 } 4105 // First check to see whether the object has been previously loaded. 4106 if (_imports != null) { 4107 WeakReference possiblePrevious = (WeakReference) _imports 4108 .get(url); 4109 NamedObj previous = null; 4110 if (possiblePrevious != null) { 4111 previous = (NamedObj) possiblePrevious.get(); 4112 if (previous == null) { 4113 _imports.remove(url); 4114 } 4115 } 4116 if (previous instanceof ComponentEntity) { 4117 // NOTE: In theory, we should not even have to 4118 // check whether the file has been updated, because 4119 // if changes were made to model since it was loaded, 4120 // they should have been propagated. 4121 return (ComponentEntity) previous; 4122 } 4123 } 4124 try { 4125 candidateReference = _findOrParse(newParser, _base, 4126 altClassAsFile, className, source); 4127 classAsFile = altClassAsFile; 4128 } catch (Exception ex3) { 4129 // Cannot find a class definition. 4130 // Unfortunately exception chaining does not work here 4131 // since we really want to know what ex2 and ex3 4132 // both were. 4133 throw new XmlException( 4134 "Could not find '" + classAsFile + "' or '" 4135 + altClassAsFile + "' using base '" + _base 4136 + "': ", 4137 _currentExternalEntity(), _getLineNumber(), 4138 _getColumnNumber(), ex2); 4139 } 4140 } else { 4141 // No alternative. Rethrow exception. 4142 throw ex2; 4143 } 4144 } 4145 4146 if (candidateReference instanceof ComponentEntity) { 4147 reference = (ComponentEntity) candidateReference; 4148 } else { 4149 throw new XmlException( 4150 "File " + classAsFile 4151 + " does not define a ComponentEntity.", 4152 _currentExternalEntity(), _getLineNumber(), 4153 _getColumnNumber()); 4154 } 4155 4156 // Check that the classname matches the name of the 4157 // reference. 4158 String referenceName = reference.getName(); 4159 4160 if (!className.equals(referenceName) 4161 && !className.endsWith("." + referenceName)) { 4162 // The className might reference an inner class defined in 4163 // the reference. Try to find that. 4164 if (reference instanceof CompositeEntity) { 4165 if (className.startsWith(referenceName + ".")) { 4166 reference = ((CompositeEntity) reference).getEntity( 4167 className.substring(referenceName.length() + 1)); 4168 } else { 4169 reference = null; 4170 } 4171 } else { 4172 reference = null; 4173 } 4174 if (reference == null) { 4175 throw new XmlException( 4176 "File " + classAsFile 4177 + " does not define a class named " + className, 4178 _currentExternalEntity(), _getLineNumber(), 4179 _getColumnNumber()); 4180 } 4181 } 4182 4183 // Load an associated icon, if there is one. 4184 _loadIconForClass(className, reference); 4185 4186 // Record the import to avoid repeated reading. 4187 if (_imports == null) { 4188 _imports = new HashMap(); 4189 } 4190 // NOTE: The index into the HashMap is the URL, not 4191 // its string representation. The URL class overrides 4192 // equal() so that it returns true if two URLs refer 4193 // to the same file, regardless of whether they have 4194 // the same string representation. 4195 // NOTE: The value in the HashMap is a weak reference 4196 // so that we don't keep all models ever created just 4197 // because of this _imports field. If there are no 4198 // references to the model other than the one in 4199 // _imports, it can be garbage collected. 4200 _imports.put(url, new WeakReference(reference)); 4201 4202 return reference; 4203 } 4204 4205 /** 4206 * Parses the given version string and builds a version specification instance. 4207 * 4208 * @param version in some text format, e.g. 11.0.1 4209 * @return the parsed version specification 4210 * @exception XmlException if the version string is not in a supported format 4211 */ 4212 private VersionSpecification _buildVersionSpecification(String version) 4213 throws XmlException { 4214 VersionSpecification versionSpec = null; 4215 try { 4216 versionSpec = (version != null ? VersionSpecification.parse(version) 4217 : null); 4218 } catch (Exception e) { 4219 throw new XmlException("Invalid version spec " + version, 4220 _currentExternalEntity(), _getLineNumber(), 4221 _getColumnNumber(), e); 4222 } 4223 return versionSpec; 4224 } 4225 4226 // If the first argument is not an instance of the second, 4227 // throw an exception with the given message. 4228 private void _checkClass(Object object, Class correctClass, String msg) 4229 throws XmlException { 4230 if (!correctClass.isInstance(object)) { 4231 throw new XmlException(msg, _currentExternalEntity(), 4232 _getLineNumber(), _getColumnNumber()); 4233 } 4234 } 4235 4236 // If the argument is null, throw an exception with the given message. 4237 private void _checkForNull(Object object, String message) 4238 throws XmlException { 4239 if (object == null) { 4240 throw new XmlException(message, _currentExternalEntity(), 4241 _getLineNumber(), _getColumnNumber()); 4242 } 4243 } 4244 4245 /** Create a new entity from the specified class name, give 4246 * it the specified entity name, and specify that its container 4247 * is the current container object. If the current container 4248 * already contains an entity with the specified name and class, 4249 * then return that entity. If the class name matches 4250 * a class that has been previously defined in the scope 4251 * (or with an absolute name), then that class is instantiated. 4252 * Otherwise, the class name is interpreted as a Java class name 4253 * and we attempt to construct the entity. If instantiating a Java 4254 * class doesn't work, then we look for a MoML file on the 4255 * classpath that defines a class by this name. The file 4256 * is assumed to be named "foo.xml", where "foo" is the name 4257 * of the class. Moreover, the classname is assumed to have 4258 * no periods (since a MoML name does not allow periods, 4259 * this is reasonable). If _current is not an instance 4260 * of CompositeEntity, then an XML exception is thrown. 4261 * If an object is created and we are propagating, then that 4262 * object is marked as a derived object. 4263 * The third argument, if non-null, gives a URL to import 4264 * to create a reference class from which to instantiate this 4265 * entity. 4266 * 4267 * @param className 4268 * @param versionSpec 4269 * @param entityName 4270 * @param source 4271 * @param isClass True to create a class definition, false to create 4272 * an instance. 4273 * @return The created NamedObj 4274 * @exception Exception If anything goes wrong. 4275 */ 4276 private NamedObj _createEntity(String className, 4277 VersionSpecification versionSpec, String entityName, String source, 4278 boolean isClass) throws Exception { 4279 if (_current != null && !(_current instanceof CompositeEntity)) { 4280 throw new XmlException( 4281 "Cannot create an entity inside " 4282 + "of another that is not a CompositeEntity " 4283 + "(Container is '" + _current + "').", 4284 _currentExternalEntity(), _getLineNumber(), 4285 _getColumnNumber()); 4286 } 4287 4288 CompositeEntity container = (CompositeEntity) _current; 4289 ComponentEntity previous = _searchForEntity(entityName, _current); 4290 Class newClass = null; 4291 ComponentEntity reference = null; 4292 4293 if (className != null) { 4294 // A class name is given. 4295 reference = searchForClass(className, source); 4296 4297 // If no source is specified and no reference was found, 4298 // search for a class definition in context. 4299 if (reference == null && source == null) { 4300 // Allow the class name to be local in the current context 4301 // or defined in scope. Search for a class definition that 4302 // matches in the current context. 4303 reference = _searchForClassInContext(className, /* source*/ 4304 null); 4305 } 4306 4307 if (reference == null || !reference.isClassDefinition()) { 4308 // No previously defined class with this name. 4309 // First attempt to instantiate a Java class. 4310 // If we throw an error or exception be sure to save the 4311 // original error message before we go off and try to fix the 4312 // error. Sometimes, the original error message is the true 4313 // cause of the problem, and we should always provide the user 4314 // with the cause of the original error in the unlikely event 4315 // that our error correction fails 4316 try { 4317 newClass = _loadClass(className, versionSpec); 4318 } catch (Exception ex) { 4319 // NOTE: Java sometimes throws ClassNotFoundException 4320 // and sometimes NullPointerException when the class 4321 // does not exist. Hence the broad catch here. 4322 try { 4323 reference = _attemptToFindMoMLClass(className, 4324 versionSpec, source); 4325 } catch (Exception ex2) { 4326 // If we are running inside an applet, then 4327 // we may end up getting a SecurityException, 4328 // so we want to be sure to not throw away ex2 4329 _updateMissingClasses(className); 4330 throw new IllegalActionException(null, ex2, 4331 "Cannot find class: " + className 4332 + ". In Ptolemy, classes are typically Java .class files. " 4333 + "Entities like actors may instead be defined within a .xml " 4334 + "file. In any case, the class was not found. " 4335 + "If the class uses a third party package, " 4336 + "then the class would be present only if the third " 4337 + "party package was found at compile time. It may be " 4338 + "necessary to upgrade Java or install the third party package, " 4339 + "reconfigure and recompile."); 4340 } 4341 } catch (Error error) { 4342 // Java might throw a ClassFormatError, but 4343 // we usually get and XmlException 4344 // NOTE: The following error message is for 4345 // the programmer, not for the user. EAL 4346 StringBuffer errorMessage = new StringBuffer(); 4347 4348 if (error instanceof ExceptionInInitializerError) { 4349 // Running a Python applet may cause 4350 // an ExceptionInInitializerError 4351 // There was a problem in the initializer, but 4352 // we can get the original exception that was 4353 // thrown. 4354 Throwable staticThrowable = ((ExceptionInInitializerError) error) 4355 .getCause(); 4356 4357 String causeDescription = ""; 4358 if (staticThrowable == null) { 4359 // The SerialComm actor may have a null cause 4360 causeDescription = KernelException 4361 .stackTraceToString(error); 4362 } else { 4363 causeDescription = KernelException 4364 .stackTraceToString(staticThrowable); 4365 } 4366 // I think we should report the cause and a stack 4367 // trace for all the exceptions thrown here, 4368 // but it sure makes the output ugly. 4369 // Instead, I just debug from here -cxh 4370 errorMessage.append("ExceptionInInitializerError: " 4371 + "Caused by:\n " + causeDescription); 4372 } else { 4373 // If there is a class format error in the 4374 // code generator, then we may end up obscuring 4375 // that error, requiring debugging here. 4376 // We use error.toString() here instead of 4377 // error.getMessage() so that the name of the 4378 // actual class that caused the error is reported. 4379 // This is critical if the problem is a class not 4380 // found error. If we use error.getMessage() 4381 // and try to open up 4382 // actor/lib/comm/demo/SerialPort/SerialPort.xml 4383 // when the Java Serial Comm API is not installed, 4384 // we get 4385 // Error encountered in: 4386 // <entity name="SerialComm" class="ptolemy.actor.lib... 4387 // -- ptolemy.actor.lib.comm.SerialComm: 4388 // javax/comm/SerialPortEventListener 4389 // ptolemy.actor.lib.comm.SerialComm: XmlException: 4390 // Could not find 'ptolemy/actor/lib/comm/SerialComm.xml'.. 4391 // If we use toString(), we get: 4392 // Error encountered in: 4393 // <entity name="SerialComm" class="ptolemy.actor.lib.. 4394 // -- ptolemy.actor.lib.comm.SerialComm: 4395 // java.lang.NoClassDefFoundError: javax/comm/SerialPortEventListener 4396 // ptolemy.actor.lib.comm.SerialComm: XmlException: 4397 // Could not find 'ptolemy/actor/lib/comm/SerialComm.xml'.. 4398 // It is critical that the error include the 4399 // NoClassDefFoundError string -cxh 4400 errorMessage.append( 4401 className + ": \n " + error.toString() + "\n"); 4402 } 4403 4404 try { 4405 reference = _attemptToFindMoMLClass(className, 4406 versionSpec, source); 4407 } catch (XmlException ex2) { 4408 _updateMissingClasses(className); 4409 throw new Exception("-- " + errorMessage.toString() 4410 + className + ": XmlException:\n" 4411 + ex2.getMessage()); 4412 } catch (ClassFormatError ex3) { 4413 _updateMissingClasses(className); 4414 throw new Exception("-- :" + errorMessage.toString() 4415 + className + ": ClassFormatError: " 4416 + "found invalid Java class file.\n" 4417 + ex3.getMessage()); 4418 } catch (Exception ex4) { 4419 _updateMissingClasses(className); 4420 throw new Exception( 4421 "-- " + errorMessage.toString() + className 4422 + ": Exception:\n" + ex4.getMessage()); 4423 } 4424 } 4425 } 4426 } 4427 4428 if (previous != null) { 4429 if (newClass != null) { 4430 _checkClass(previous, newClass, "entity named \"" + entityName 4431 + "\" exists and is not an instance of " + className); 4432 } 4433 4434 return previous; 4435 } 4436 4437 // No previous entity. Class name is required. 4438 _checkForNull(className, "Cannot create entity without a class name."); 4439 4440 // Next check to see whether the class extends a named entity. 4441 if (reference == null 4442 || !reference.isClassDefinition() && newClass != null) { 4443 4444 // Not a named entity. Instantiate a Java class. 4445 if (_current != null) { 4446 // Not a top-level entity. 4447 // First check that there will be no name collision 4448 // when this is propagated. Note that we need to 4449 // include all derived objects, irrespective of whether 4450 // they are locally changed. 4451 List derivedList = container.getDerivedList(); 4452 Iterator derivedObjects = derivedList.iterator(); 4453 4454 while (derivedObjects.hasNext()) { 4455 CompositeEntity derived = (CompositeEntity) derivedObjects 4456 .next(); 4457 4458 // The following call, if derived is lazy, will trigger 4459 // its expansion. However, derived may contain an instance 4460 // of the same class we are now trying to define, so 4461 // the populate will delegate to this class definition, 4462 // which will result in the class getting defined, 4463 // and the collidingEntity being non-null. 4464 // Entity collidingEntity = derived.getEntity(entityName); 4465 // Hence, we have to scroll through the list of entities 4466 // lazily, avoiding populating. 4467 if (derived.getEntity(entityName) != null) { 4468 // If the derived is within an EntityLibrary, 4469 // then don't throw an exception. To 4470 // replicate this, create an EntityLibrary 4471 // within the UserLibrary, save and then try 4472 // to edit the UserLibrary. 4473 boolean derivedIsNotWithinEntityLibrary = true; 4474 CompositeEntity derivedContainer = derived; 4475 while (derivedContainer != null 4476 && (derivedIsNotWithinEntityLibrary = !(derivedContainer instanceof EntityLibrary))) { 4477 derivedContainer = (CompositeEntity) derivedContainer 4478 .getContainer(); 4479 } 4480 if (derivedIsNotWithinEntityLibrary) { 4481 throw new IllegalActionException(container, 4482 "Cannot create entity named \"" + entityName 4483 + "\" because a subclass or instance in \"" 4484 + container.getFullName() 4485 + "\" contains an entity with the same name \"" 4486 + derived.getEntity(entityName) 4487 .getFullName() 4488 + "\". Note that this can happen when actor oriented class " 4489 + "definitions are LazyTypedCompositeActors."); 4490 } 4491 } 4492 4493 // Here's a possible solution to the above 4494 // See actor/lib/test/auto/LazyAOCTestLazy.xml and LazyAOCTestNonLazy.xml 4495 // List<ComponentEntity> possibleCollidingEntities = derived.lazyEntityList(); 4496 // for (ComponentEntity possibleCollidingEntity : possibleCollidingEntities) { 4497 // if (possibleCollidingEntity.getName().equals(entityName)) { 4498 // previous = _searchForEntity(entityName, _current); 4499 // throw new IllegalActionException( 4500 // container, 4501 // "Cannot create entity named \"" + entityName 4502 // + "\" because a subclass or instance in \"" 4503 // + container.getFullName() 4504 // + "\" contains an entity with the same name \"" 4505 // + derived.getEntity(entityName).getFullName() + "\"."); 4506 // } 4507 // } 4508 } 4509 4510 _checkClass(_current, CompositeEntity.class, 4511 "Cannot create an entity inside an element that " 4512 + "is not a CompositeEntity. It is: " 4513 + _current); 4514 4515 Object[] arguments = new Object[2]; 4516 4517 arguments[0] = _current; 4518 arguments[1] = entityName; 4519 4520 NamedObj newEntity = _createInstance(newClass, arguments); 4521 4522 // Propagate existence, and then mark each newly created object as a class 4523 // if this is a class. 4524 List<InstantiableNamedObj> impliedObjects = newEntity 4525 .propagateExistence(); 4526 if (isClass) { 4527 for (InstantiableNamedObj impliedObject : impliedObjects) { 4528 impliedObject.setClassDefinition(true); 4529 } 4530 } 4531 4532 _loadIconForClass(className, newEntity); 4533 4534 _addParamsToParamsToParse(newEntity); 4535 4536 return newEntity; 4537 } else { 4538 // Top-level entity. Instantiate in the workspace. 4539 // Note that there cannot possibly be any propagation here. 4540 Object[] arguments = new Object[1]; 4541 arguments[0] = _workspace; 4542 4543 NamedObj result = _createInstance(newClass, arguments); 4544 result.setName(entityName); 4545 _loadIconForClass(className, result); 4546 return result; 4547 } 4548 } else { 4549 // Extending a previously defined entity. Check to see that 4550 // it was defined to be a class definition. 4551 if (!reference.isClassDefinition()) { 4552 throw new MissingClassException( 4553 "Attempt to extend an entity that " + "is not a class: " 4554 + reference.getFullName() + " className: " 4555 + className + " entityName: " + entityName 4556 + " source: " + source, 4557 reference.getFullName(), _currentExternalEntity(), 4558 _getLineNumber(), _getColumnNumber()); 4559 } 4560 4561 // First check that there will be no name collision 4562 // when this is propagated. Note that we need to 4563 // include all derived objects, irrespective of whether 4564 // they are locally changed. 4565 // If the container is null, then we can't possibly get 4566 // a name collision. 4567 List derivedList = null; 4568 4569 if (container != null) { 4570 derivedList = container.getDerivedList(); 4571 4572 Iterator derivedObjects = derivedList.iterator(); 4573 4574 while (derivedObjects.hasNext()) { 4575 CompositeEntity derived = (CompositeEntity) derivedObjects 4576 .next(); 4577 4578 // The following call, if derived is lazy, will trigger 4579 // its expansion. However, derived may contain an instance 4580 // of the same class we are now trying to define, so 4581 // the populate will delegate to this class definition, 4582 // which will result in the class getting defined, 4583 // and the collidingEntity being non-null. 4584 // Entity collidingEntity = derived.getEntity(entityName); 4585 // Hence, we have to scroll through the list of entities 4586 // lazily, avoiding populating. 4587 if (derived.getEntity(entityName) != null) { 4588 throw new IllegalActionException(container, 4589 "Cannot create entity named \"" + entityName 4590 + "\" because a subclass or instance in \"" 4591 + container.getFullName() 4592 + "\" contains an entity with the same name \"" 4593 + derived.getEntity(entityName) 4594 .getFullName() 4595 + "\". Note that this can happen when actor oriented class " 4596 + "definitions are LazyTypedCompositeActors."); 4597 } 4598 // Here's a possible solution to the above 4599 // See actor/lib/test/auto/LazyAOCTestLazy.xml and LazyAOCTestNonLazy.xml 4600 // List<ComponentEntity> possibleCollidingEntities = derived.lazyEntityList(); 4601 // for (ComponentEntity possibleCollidingEntity : possibleCollidingEntities) { 4602 // if (possibleCollidingEntity.getName().equals(entityName)) { 4603 // previous = _searchForEntity(entityName, _current); 4604 // throw new IllegalActionException( 4605 // container, 4606 // "Cannot create entity named \"" + entityName 4607 // + "\" because a subclass or instance in \"" 4608 // + container.getFullName() 4609 // + "\" contains an entity with the same name \"" 4610 // + derived.getEntity(entityName).getFullName() + "\"."); 4611 // } 4612 // } 4613 } 4614 } 4615 4616 // Instantiate it. 4617 ComponentEntity newEntity = (ComponentEntity) reference 4618 .instantiate(container, entityName); 4619 4620 // If we are keeping track of objects created... 4621 if (_topObjectsCreated != null && container == _originalContext) { 4622 _topObjectsCreated.add(newEntity); 4623 } 4624 4625 // The original reference object may have had a URIAttribute, 4626 // but the new one should not. The clone would have copied 4627 // it. The URIAttribute refers to the file in which the 4628 // component is defined, but the new entity is defined 4629 // in whatever file its container is defined. Leaving the 4630 // URIAttribute in the clone results in "look inside" 4631 // opening the clone but making it look as if it is the 4632 // original. 4633 // FIXME: This violates the derivation invariant. 4634 URIAttribute modelURI = (URIAttribute) newEntity 4635 .getAttribute("_uri", URIAttribute.class); 4636 4637 if (modelURI != null) { 4638 modelURI.setContainer(null); 4639 } 4640 4641 // Mark contents as needing evaluation. 4642 _markParametersToParse(newEntity); 4643 4644 // Set the class name as specified in this method call. 4645 // This overrides what InstantiableNamedObj does. The reason 4646 // we want to do that is that InstantiableNamedObj uses the 4647 // name of the object that we cloned as the classname. 4648 // But this may not provide enough information to 4649 // instantiate the class. 4650 newEntity.setClassName(className); 4651 4652 // Propagate. 4653 Iterator propagatedInstances = newEntity.propagateExistence() 4654 .iterator(); 4655 4656 while (propagatedInstances.hasNext()) { 4657 ComponentEntity propagatedEntity = (ComponentEntity) propagatedInstances 4658 .next(); 4659 4660 // If this is a class definition, then newly created instances should be too. 4661 if (isClass) { 4662 propagatedEntity.setClassDefinition(true); 4663 } 4664 // Get rid of URI attribute that may have been cloned. 4665 // FIXME: Should that be done in the clone method 4666 // for URIAttribute? Doesn't this violate the 4667 // derivation invariant? 4668 URIAttribute propagatedURI = (URIAttribute) propagatedEntity 4669 .getAttribute("_uri", URIAttribute.class); 4670 4671 if (propagatedURI != null) { 4672 propagatedURI.setContainer(null); 4673 } 4674 } 4675 4676 return newEntity; 4677 } 4678 } 4679 4680 /** Create an instance of the specified class name by finding a 4681 * constructor that matches the specified arguments. The specified 4682 * class must be NamedObj or derived, or a ClassCastException will 4683 * be thrown. NOTE: This mechanism does not support instantiation 4684 * of inner classes, since those take an additional argument (the 4685 * first argument), which is the enclosing class. Static inner 4686 * classes, however, work fine. 4687 * This method marks the contents of what it creates as derived objects, 4688 * since they are defined in the Java code of the constructor. 4689 * If we are currently propagating, then it also marks the new 4690 * instance itself as a derived object. 4691 * @param newClass The class. 4692 * @param arguments The constructor arguments. 4693 * @exception Exception If no matching constructor is found, or if 4694 * invoking the constructor triggers an exception. 4695 */ 4696 private NamedObj _createInstance(Class newClass, Object[] arguments) 4697 throws Exception { 4698 Constructor[] constructors = newClass.getConstructors(); 4699 4700 for (Constructor constructor : constructors) { 4701 Class[] parameterTypes = constructor.getParameterTypes(); 4702 4703 if (parameterTypes.length != arguments.length) { 4704 continue; 4705 } 4706 4707 boolean match = true; 4708 4709 for (int j = 0; j < parameterTypes.length; j++) { 4710 if (!parameterTypes[j].isInstance(arguments[j])) { 4711 match = false; 4712 break; 4713 } 4714 } 4715 4716 if (match) { 4717 NamedObj newEntity = (NamedObj) constructor 4718 .newInstance(arguments); 4719 4720 // Mark the contents of the new entity as being derived objects. 4721 _markContentsDerived(newEntity, 0); 4722 4723 // If we are keeping track of objects created... 4724 if (_topObjectsCreated != null 4725 && arguments[0] == _originalContext) { 4726 _topObjectsCreated.add(newEntity); 4727 } 4728 4729 // If the entity implements ScopeExtender, then add it to the list. 4730 if (newEntity instanceof ScopeExtender) { 4731 if (_scopeExtenders == null) { 4732 _scopeExtenders = new LinkedList<ScopeExtender>(); 4733 } 4734 _scopeExtenders.add((ScopeExtender) newEntity); 4735 } 4736 4737 return newEntity; 4738 } 4739 } 4740 4741 // If we get here, then there is no matching constructor. 4742 // Generate a StringBuffer containing what we were looking for. 4743 StringBuffer argumentBuffer = new StringBuffer(); 4744 4745 for (int i = 0; i < arguments.length; i++) { 4746 argumentBuffer.append(arguments[i].getClass() + " = \"" 4747 + arguments[i].toString() + "\""); 4748 4749 if (i < arguments.length - 1) { 4750 argumentBuffer.append(", "); 4751 } 4752 } 4753 4754 throw new XmlException( 4755 "Cannot find a suitable constructor (" + arguments.length 4756 + " args) (" + argumentBuffer + ") for '" 4757 + newClass.getName() + "'", 4758 _currentExternalEntity(), _getLineNumber(), _getColumnNumber()); 4759 } 4760 4761 /** Delete the entity after verifying that it is contained (deeply) 4762 * by the current environment. If no object is found, then do 4763 * nothing and return null. This is because deletion of a class 4764 * may result in deletion of other objects that make this particular 4765 * delete call redundant. 4766 * @param entityName The relative or absolute name of the 4767 * entity to delete. 4768 * @return The deleted object, or null if none was found. 4769 * @exception Exception If there is no such entity or if the entity 4770 * is defined in the class definition. 4771 */ 4772 private NamedObj _deleteEntity(String entityName) throws Exception { 4773 ComponentEntity toDelete = _searchForEntity(entityName, _current); 4774 4775 if (toDelete == null) { 4776 return null; 4777 } 4778 4779 // Ensure that derived objects aren't changed. 4780 if (toDelete.getDerivedLevel() < Integer.MAX_VALUE) { 4781 throw new IllegalActionException(toDelete, 4782 "Cannot delete. This entity is part of the class definition."); 4783 } 4784 4785 // NOTE: not enough to simply record the MoML of the deleted entity 4786 // as any links connected to its ports will also be deleted. 4787 // Construct the undo MoML as we go to ensure: (1) that 4788 // the undo occurs in the opposite order of all deletions, and 4789 // (2) that if a failure to delete occurs at any point, then 4790 // the current undo only represents as far as the failure got. 4791 StringBuffer undoMoML = new StringBuffer(); 4792 4793 // Propagate. The name might be absolute and have 4794 // nothing to do with the current context. So 4795 // we look for its derived objects, not the context's derived objects. 4796 // We have to do this before actually deleting it, 4797 // which also has the side effect of triggering errors 4798 // before the deletion. 4799 // Note that deletion and undo need to occur in the opposite 4800 // order, so we first create the undo in the order given 4801 // by the derived list, then do the deletion in the 4802 // opposite order. 4803 try { 4804 Iterator derivedObjects = toDelete.getDerivedList().iterator(); 4805 4806 // NOTE: Deletion needs to occur in the reverse order from 4807 // what appears in the derived list. So first we construct 4808 // a reverse order list. The reason for this is that subclasses 4809 // appear before instances in the derived list, and we 4810 // need to delete the instances before we delete the 4811 // the subclasses. 4812 List reverse = new LinkedList(); 4813 4814 while (derivedObjects.hasNext()) { 4815 reverse.add(0, derivedObjects.next()); 4816 } 4817 4818 derivedObjects = reverse.iterator(); 4819 4820 while (derivedObjects.hasNext()) { 4821 ComponentEntity derived = (ComponentEntity) derivedObjects 4822 .next(); 4823 4824 // Generate Undo commands. 4825 // Note that deleting an entity inside a class definition 4826 // may generate deletions in derived classes or instances. 4827 // The undo does not need to restore these (they are implied), 4828 // but if the deleted entities have overridden parameters, 4829 // then have to restore the overrides. If the derived class 4830 // or instance is inside a different top level model, then 4831 // the MoML to do this cannot be in the same MoML with the 4832 // main undo because its execution context needs to be 4833 // different. Hence, we collection additional MoMLUndoEntry 4834 // instances to carry out these overrides. 4835 // Note that we have to be careful to not re-establish 4836 // links, as these will duplicate the original links 4837 // (and, in fact, will throw an exception saying 4838 // that establishing links between ports defined 4839 // in the base class is not allowed). The following method 4840 // takes care of that. 4841 // Have to get this _before_ deleting. 4842 String toUndo = _getUndoForDeleteEntity(derived); 4843 NamedObj derivedToplevel = derived.toplevel(); 4844 4845 // Remove the derived object. 4846 derived.setContainer(null); 4847 4848 // If the top level for the derived object is the same 4849 // as the top level for the object being deleted, then 4850 // we can simply insert this into the undo MoML. 4851 // Otherwise, it has to execute in a different context. 4852 if (derivedToplevel == _current.toplevel()) { 4853 // Undo needs to occur in the opposite order because when 4854 // we redo we need to create subclasses before instances. 4855 undoMoML.insert(0, toUndo); 4856 } else { 4857 MoMLUndoEntry entry = new MoMLUndoEntry(derivedToplevel, 4858 toUndo); 4859 _undoForOverrides.add(0, entry); 4860 } 4861 } 4862 4863 // Have to get this _before_ deleting. 4864 String toUndo = _getUndoForDeleteEntity(toDelete); 4865 toDelete.setContainer(null); 4866 undoMoML.insert(0, toUndo); 4867 } finally { 4868 if (_undoEnabled) { 4869 undoMoML.insert(0, "<group>"); 4870 undoMoML.append("</group>\n"); 4871 _undoContext.appendUndoMoML(undoMoML.toString()); 4872 } 4873 } 4874 4875 return toDelete; 4876 } 4877 4878 /** Delete the port after verifying that it is contained (deeply) 4879 * by the current environment. If no object is found, then do 4880 * nothing and return null. This is because deletion of a class 4881 * may result in deletion of other objects that make this particular 4882 * delete call redundant. 4883 * @param portName The relative or absolute name of the 4884 * port to delete. 4885 * @param entityName Optional name of the entity that contains 4886 * the port (or null to use the current context). 4887 * @return The deleted object, or null if none is found. 4888 * @exception Exception If there is no such port or if the port 4889 * is defined in the class definition. 4890 */ 4891 private Port _deletePort(String portName, String entityName) 4892 throws Exception { 4893 Port toDelete = null; 4894 Entity portContainer = null; 4895 4896 if (entityName == null) { 4897 toDelete = _searchForPort(portName); 4898 4899 if (toDelete != null) { 4900 portContainer = (Entity) toDelete.getContainer(); 4901 } 4902 } else { 4903 portContainer = _searchForEntity(entityName, _current); 4904 4905 if (portContainer != null) { 4906 toDelete = portContainer.getPort(portName); 4907 } 4908 } 4909 4910 if (toDelete == null) { 4911 return null; 4912 } 4913 4914 if (portContainer == null) { 4915 throw new XmlException("No container for the port: " + portName, 4916 _currentExternalEntity(), _getLineNumber(), 4917 _getColumnNumber()); 4918 } 4919 4920 // Ensure that derived objects aren't changed. 4921 if (toDelete.getDerivedLevel() < Integer.MAX_VALUE) { 4922 throw new IllegalActionException(toDelete, 4923 "Cannot delete. This port is part of the class definition."); 4924 } 4925 4926 // Propagate and generate undo MoML. 4927 // NOTE: not enough to simply record the MoML of the deleted port 4928 // as any links connected to it will also be deleted 4929 // and derived ports will have to have similar undo MoML 4930 // so that connections get remade. 4931 // Construct the undo MoML as we go to ensure: (1) that 4932 // the undo occurs in the opposite order of all deletions, and 4933 // (2) that if a failure to delete occurs at any point, then 4934 // the current undo only represents as far as the failure got. 4935 StringBuffer undoMoML = new StringBuffer(); 4936 4937 // Propagate. The name might be absolute and have 4938 // nothing to do with the current context. So 4939 // we look for its derived objects, not the context's derived objects. 4940 // We have to do this before actually deleting it, 4941 // which also has the side effect of triggering errors 4942 // before the deletion. 4943 // Note that deletion and undo need to occur in the opposite 4944 // order. This is because connections need to removed in the instances 4945 // before the subclasses. 4946 try { 4947 Iterator derivedObjects = toDelete.getDerivedList().iterator(); 4948 4949 // NOTE: Deletion needs to occur in the reverse order from 4950 // what appears in the derived list. So first we construct 4951 // a reverse order list. 4952 List reverse = new LinkedList(); 4953 4954 while (derivedObjects.hasNext()) { 4955 reverse.add(0, derivedObjects.next()); 4956 } 4957 4958 derivedObjects = reverse.iterator(); 4959 4960 while (derivedObjects.hasNext()) { 4961 Port derived = (Port) derivedObjects.next(); 4962 4963 // Create the undo MoML. 4964 // Note that this is necessary because the ports 4965 // will have customized connections to the instances. 4966 // Have to get this _before_ deleting. 4967 // Put at the _start_ of the undo MoML, to ensure 4968 // reverse order from the deletion. 4969 // NOTE: This describes links to the 4970 // derived port. Amazingly, the order 4971 // seems to be exactly right so that links 4972 // that will propagate on undo are no longer 4973 // present. So it seems to generate exactly 4974 // the right undo to not end up with duplicate connections! 4975 // Some care is needed, however. If the implied port 4976 // is inside a different top level model, then 4977 // the MoML to do this cannot be in the same MoML with the 4978 // main undo because its execution context needs to be 4979 // different. Hence, we collection additional MoMLUndoEntry 4980 // instances to carry out these overrides. 4981 // Have to get this before doing the deletion. 4982 // FIXME: Actually, this does seem to generate duplicate connections! 4983 // To replicate: In a new model, create an instance of Sinewave. 4984 // Look inside and delete the output port. Then undo. 4985 // This triggers an exception because it tries to recreate 4986 // the link in the derived object. 4987 String toUndo = _getUndoForDeletePort(derived); 4988 NamedObj derivedToplevel = derived.toplevel(); 4989 4990 derived.setContainer(null); 4991 4992 // If the top level for the derived object is the same 4993 // as the top level for the object being deleted, then 4994 // we can simply insert this into the undo MoML. 4995 // Otherwise, it has to execute in a different context. 4996 if (derivedToplevel == _current.toplevel()) { 4997 undoMoML.insert(0, toUndo); 4998 } else { 4999 MoMLUndoEntry entry = new MoMLUndoEntry(derivedToplevel, 5000 toUndo); 5001 _undoForOverrides.add(0, entry); 5002 } 5003 } 5004 5005 // Have to get this _before_ deleting. 5006 String toUndo = _getUndoForDeletePort(toDelete); 5007 toDelete.setContainer(null); 5008 undoMoML.insert(0, toUndo); 5009 } finally { 5010 if (_undoEnabled) { 5011 undoMoML.insert(0, "<group>"); 5012 undoMoML.append("</group>\n"); 5013 _undoContext.appendUndoMoML(undoMoML.toString()); 5014 } 5015 } 5016 5017 return toDelete; 5018 } 5019 5020 /** Delete an attribute after verifying that it is contained (deeply) 5021 * by the current environment. If no object is found, then do 5022 * nothing and return null. This is because deletion of a class 5023 * may result in deletion of other objects that make this particular 5024 * delete call redundant. 5025 * @param attributeName The relative or absolute name of the 5026 * attribute to delete. 5027 * @return The deleted object, or null if none is found. 5028 * @exception Exception If there is no such attribute or if the attribute 5029 * is defined in the class definition. 5030 */ 5031 private Attribute _deleteProperty(String attributeName) throws Exception { 5032 Attribute toDelete = _searchForAttribute(attributeName); 5033 5034 if (toDelete == null) { 5035 return null; 5036 } 5037 5038 // Ensure that derived objects aren't changed. 5039 if (toDelete.getDerivedLevel() < Integer.MAX_VALUE) { 5040 throw new IllegalActionException(toDelete, 5041 "Cannot delete. This attribute is part of the class definition."); 5042 } 5043 5044 // Propagate and generate undo MoML. 5045 // NOTE: not enough to simply record the MoML of the deleted attribute 5046 // as derived attributes may have overridden the values. 5047 // Construct the undo MoML as we go to ensure: (1) that 5048 // the undo occurs in the opposite order of all deletions, and 5049 // (2) that if a failure to delete occurs at any point, then 5050 // the current undo only represents as far as the failure got. 5051 StringBuffer undoMoML = new StringBuffer(); 5052 5053 // Propagate. The name might be absolute and have 5054 // nothing to do with the current context. So 5055 // we look for its derived objects, not the context's derived objects. 5056 // We have to do this before actually deleting it, 5057 // which also has the side effect of triggering errors 5058 // before the deletion. 5059 try { 5060 // NOTE: Deletion can occur in the the same order as 5061 // what appears in the derived list. This is because attributes 5062 // cannot be classes, so there are no subclasses, and there are 5063 // no connections to remove. 5064 Iterator derivedObjects = toDelete.getDerivedList().iterator(); 5065 5066 String toUndo = _getUndoForDeleteAttribute(toDelete); 5067 5068 NamedObj container = toDelete.getContainer(); 5069 container.attributeDeleted(toDelete); 5070 toDelete.setContainer(null); 5071 undoMoML.append(toUndo); 5072 while (derivedObjects.hasNext()) { 5073 Attribute derived = (Attribute) derivedObjects.next(); 5074 5075 // Generate Undo commands. 5076 // Note that deleting an attribute inside a class definition 5077 // may generate deletions in derived classes or instances. 5078 // The undo does not need to restore these (they are implied), 5079 // but if the deleted attributes have overridden parameters, 5080 // then we have to restore the overrides. If the derived class 5081 // or instance is inside a different top level model, then 5082 // the MoML to do this cannot be in the same MoML with the 5083 // main undo because its execution context needs to be 5084 // different. Hence, we collection additional MoMLUndoEntry 5085 // instances to carry out these overrides. 5086 // Have to get this _before_ deleting. 5087 toUndo = _getUndoForDeleteAttribute(derived); 5088 NamedObj derivedToplevel = derived.toplevel(); 5089 5090 // Remove the derived object. 5091 derived.setContainer(null); 5092 5093 // If the top level for the derived object is the same 5094 // as the top level for the object being deleted, then 5095 // we can simply insert this into the undo MoML. 5096 // Otherwise, it has to execute in a different context. 5097 if (derivedToplevel == _current.toplevel()) { 5098 // Put at the _start_ of the undo MoML, to ensure 5099 // reverse order from the deletion. 5100 undoMoML.append(toUndo); 5101 } else { 5102 MoMLUndoEntry entry = new MoMLUndoEntry(derivedToplevel, 5103 toUndo); 5104 _undoForOverrides.add(entry); 5105 } 5106 5107 } 5108 } finally { 5109 if (_undoEnabled) { 5110 undoMoML.insert(0, "<group>"); 5111 undoMoML.append("</group>\n"); 5112 _undoContext.appendUndoMoML(undoMoML.toString()); 5113 } 5114 } 5115 5116 return toDelete; 5117 } 5118 5119 /** Delete the relation after verifying that it is contained (deeply) 5120 * by the current environment. If no object is found, then do 5121 * nothing and return null. This is because deletion of a class 5122 * may result in deletion of other objects that make this particular 5123 * delete call redundant. 5124 * @param relationName The relative or absolute name of the 5125 * relation to delete. 5126 * @return The deleted object, or null if none is found. 5127 * @exception Exception If there is no such relation or if the relation 5128 * is defined in the class definition. 5129 */ 5130 private Relation _deleteRelation(String relationName) throws Exception { 5131 ComponentRelation toDelete = _searchForRelation(relationName); 5132 5133 if (toDelete == null) { 5134 return null; 5135 } 5136 5137 if (toDelete.getDerivedLevel() < Integer.MAX_VALUE) { 5138 throw new IllegalActionException(toDelete, 5139 "Cannot delete. This relation is part of the class definition."); 5140 } 5141 5142 // Propagate and generate undo MoML. 5143 // NOTE: not enough to simply record the MoML of the deleted relation 5144 // as any links connected to it will also be deleted 5145 // and derived relations will have to have similar undo MoML 5146 // so that connections get remade. 5147 // Construct the undo MoML as we go to ensure: (1) that 5148 // the undo occurs in the opposite order of all deletions, and 5149 // (2) that if a failure to delete occurs at any point, then 5150 // the current undo only represents as far as the failure got. 5151 StringBuffer undoMoML = new StringBuffer(); 5152 5153 // Propagate. The name might be absolute and have 5154 // nothing to do with the current context. So 5155 // we look for its derived objects, not the context's derived objects. 5156 // We have to do this before actually deleting it, 5157 // which also has the side effect of triggering errors 5158 // before the deletion. 5159 // Note that deletion and undo need to occur in the opposite 5160 // order. 5161 try { 5162 Iterator derivedObjects = toDelete.getDerivedList().iterator(); 5163 5164 // NOTE: Deletion needs to occur in the reverse order from 5165 // what appears in the derived objects list. So first we construct 5166 // a reverse order list. This is because connections in 5167 // instances need to be removed before connections 5168 // in subclasses. 5169 List reverse = new LinkedList(); 5170 5171 while (derivedObjects.hasNext()) { 5172 reverse.add(0, derivedObjects.next()); 5173 } 5174 5175 derivedObjects = reverse.iterator(); 5176 5177 while (derivedObjects.hasNext()) { 5178 ComponentRelation derived = (ComponentRelation) derivedObjects 5179 .next(); 5180 5181 // Create the undo MoML. 5182 // Note that this is necessary because the relations 5183 // will have customized connections to the instances 5184 // and may have parameters that are overridden. 5185 // Put at the _start_ of the undo MoML, to ensure 5186 // reverse order from the deletion. 5187 // Some care is needed, however. If the implied port 5188 // is inside a different top level model, then 5189 // the MoML to do this cannot be in the same MoML with the 5190 // main undo because its execution context needs to be 5191 // different. Hence, we collection additional MoMLUndoEntry 5192 // instances to carry out these overrides. 5193 // Have to get this before doing the deletion. 5194 // FIXME: Actually, this seems to generate duplicate connections! 5195 // To replicate: In a new model, create an instance of Sinewave. 5196 // Look inside and delete the output port. Then undo. 5197 // This triggers an exception because it tries to recreate 5198 // the link in the derived object. 5199 // Have to get this _before_ deleting. 5200 String toUndo = _getUndoForDeleteRelation(derived); 5201 NamedObj derivedToplevel = derived.toplevel(); 5202 5203 derived.setContainer(null); 5204 5205 // If the top level for the derived object is the same 5206 // as the top level for the object being deleted, then 5207 // we can simply insert this into the undo MoML. 5208 // Otherwise, it has to execute in a different context. 5209 if (derivedToplevel == _current.toplevel()) { 5210 undoMoML.insert(0, toUndo); 5211 } else { 5212 MoMLUndoEntry entry = new MoMLUndoEntry(derivedToplevel, 5213 toUndo); 5214 _undoForOverrides.add(0, entry); 5215 } 5216 } 5217 5218 // Have to get this _before_ deleting. 5219 String toUndo = _getUndoForDeleteRelation(toDelete); 5220 toDelete.setContainer(null); 5221 undoMoML.insert(0, toUndo); 5222 } finally { 5223 if (_undoEnabled) { 5224 undoMoML.insert(0, "<group>"); 5225 undoMoML.append("</group>\n"); 5226 _undoContext.appendUndoMoML(undoMoML.toString()); 5227 } 5228 } 5229 5230 return toDelete; 5231 } 5232 5233 /** Expand all the scope extenders that were encountered during parsing. 5234 * Then, after expanding them all, validate them all. 5235 */ 5236 private void _expandScopeExtenders() throws IllegalActionException { 5237 if (_scopeExtenders != null) { 5238 for (ScopeExtender extender : _scopeExtenders) { 5239 extender.expand(); 5240 } 5241 // The above will create the parameters of the scope extender, but 5242 // not evaluate their expressions. 5243 // The following evaluates their expressions. 5244 // This has to be done as a separate pass because a scope extender 5245 // may have parameters whose values depend on parameters in another 5246 // scope extender. 5247 for (ScopeExtender extender : _scopeExtenders) { 5248 extender.validate(); 5249 } 5250 } 5251 } 5252 5253 /** Use the specified parser to parse the file or URL, 5254 * which contains MoML, using the specified base to find the URL. 5255 * If the URL has been previously parsed by this application, 5256 * then return the instance that was the result of the previous 5257 * parse. 5258 * If the URL cannot be found relative to this base, then it 5259 * is searched for relative to the current working directory 5260 * (if this is permitted with the current security restrictions), 5261 * and then relative to the classpath. 5262 * @param parser The parser to use. 5263 * @param base The base URL for relative references, or null if 5264 * not known. 5265 * @param file The file or URL from which to read MoML. 5266 * @param className The class name to assign if the file is 5267 * parsed anew. 5268 * @param source The source file to assign if the file is 5269 * parsed anew, or null to not assign one. 5270 * @return The top-level composite entity of the Ptolemy II model. 5271 * @exception Exception If the parser fails. 5272 */ 5273 private NamedObj _findOrParse(MoMLParser parser, URL base, String file, 5274 String className, String source) throws Exception { 5275 URL previousXmlFile = parser._xmlFile; 5276 5277 // Cache the modified flag so that if the file 5278 // we are opening is modified we don't accidentally 5279 // mark container file as modified. 5280 // Wireless SmartParking.xml had this problem because 5281 // LotSensor.xml has backward compat changes 5282 boolean modified = isModified(); 5283 parser._setXmlFile(fileNameToURL(file, base)); 5284 5285 try { 5286 NamedObj toplevel = parser.parse(parser._xmlFile, parser._xmlFile); 5287 5288 // NOTE: This might be a relative file reference, which 5289 // won't be of much use if a MoML file is moved. 5290 // But we don't want absolute file names wired in 5291 // either. So we record the source as originally 5292 // specified, since it was sufficient to find this. 5293 // Note that the source may be null. 5294 toplevel.setSource(source); 5295 5296 // Record the import to avoid repeated reading 5297 if (_imports == null) { 5298 _imports = new HashMap(); 5299 } 5300 5301 // NOTE: The index into the HashMap is the URL, not 5302 // its string representation. The URL class overrides 5303 // equal() so that it returns true if two URLs refer 5304 // to the same file, regardless of whether they have 5305 // the same string representation. 5306 // NOTE: The value in the HashMap is a weak reference 5307 // so that we don't keep all models ever created just 5308 // because of this _imports field. If there are no 5309 // references to the model other than the one in 5310 // _imports, it can be garbage collected. 5311 _imports.put(parser._xmlFile, new WeakReference(toplevel)); 5312 5313 return toplevel; 5314 } catch (CancelException ex) { 5315 // Parse operation cancelled. 5316 return null; 5317 } finally { 5318 parser._setXmlFile(previousXmlFile); 5319 setModified(modified); 5320 } 5321 } 5322 5323 /** Return the column number from the XmlParser. 5324 * @return the column number from the XmlParser. Return -1 if 5325 * _xmlParser is null. 5326 */ 5327 private int _getColumnNumber() { 5328 // _parser can be null if a method is called outside of a callback 5329 // for XmlParser. All calls should go through parser(URL, Reader) 5330 if (_xmlParser == null) { 5331 return -1; 5332 } 5333 return _xmlParser.getColumnNumber(); 5334 } 5335 5336 // Construct a string representing the current XML element. 5337 private String _getCurrentElement(String elementName) { 5338 StringBuffer result = new StringBuffer(); 5339 result.append("<"); 5340 result.append(elementName); 5341 5342 // Put the attributes into the character data. 5343 Iterator attributeNames = _attributeNameList.iterator(); 5344 5345 while (attributeNames.hasNext()) { 5346 String name = (String) attributeNames.next(); 5347 String value = (String) _attributes.get(name); 5348 5349 if (value != null) { 5350 // Note that we have to escape the value again, 5351 // so that it is properly parsed. 5352 result.append(" "); 5353 result.append(name); 5354 result.append("=\""); 5355 result.append(StringUtilities.escapeForXML(value)); 5356 result.append("\""); 5357 } 5358 } 5359 5360 result.append(">"); 5361 return result.toString(); 5362 } 5363 5364 /** Return the line number from the XmlParser. 5365 * @return the line number from the XmlParser. Return -1 if 5366 * _xmlParser is null. 5367 */ 5368 private int _getLineNumber() { 5369 // _xmlParser can be null if a method is called outside of a callback 5370 // for XmlParser. All calls should go through parser(URL, Reader) 5371 if (_xmlParser == null) { 5372 return -1; 5373 } 5374 return _xmlParser.getLineNumber(); 5375 } 5376 5377 /** Return the port corresponding to the specified port name in the 5378 * specified composite entity. If the port belongs directly to the 5379 * composite entity, then the argument is a simple name. If the 5380 * port belongs to a component entity, then the name is the entity 5381 * name, a period, and the port name. 5382 * Throw an exception if there is no such port. 5383 * The returned value is never null. 5384 * @param portspec The relative port name. 5385 * @param context The context in which to look for the port. 5386 * @return The port. 5387 * @exception XmlException If no such port is found. 5388 */ 5389 private ComponentPort _getPort(String portspec, CompositeEntity context) 5390 throws XmlException { 5391 ComponentPort port = (ComponentPort) context.getPort(portspec); 5392 _checkForNull(port, "No port named \"" + portspec + "\" in " 5393 + context.getFullName()); 5394 return port; 5395 } 5396 5397 /** Return the MoML commands to undo deleting the specified attribute 5398 * from the current context. 5399 * @param toDelete The component to delete. 5400 */ 5401 private String _getUndoForDeleteAttribute(Attribute toDelete) 5402 throws IOException { 5403 // Set the context to the immediate container. 5404 StringBuffer moml = new StringBuffer( 5405 UndoContext.moveContextStart(_current, toDelete)); 5406 5407 int depth = toDelete.depthInHierarchy() - _current.depthInHierarchy(); 5408 5409 // Is it possible that _current doesn't contain toDelete? 5410 if (!_current.deepContains(toDelete)) { 5411 depth = 0; 5412 } 5413 5414 // Add in the description. 5415 // Need to use the depth to determine how much MoML to export. 5416 StringWriter buffer = new StringWriter(); 5417 toDelete.exportMoML(buffer, depth); 5418 moml.append(buffer.toString()); 5419 5420 // Finally move back to context if needed 5421 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5422 5423 // If there is no body, don't return the context either. 5424 if (buffer.toString().trim().equals("")) { 5425 return ""; 5426 } 5427 5428 return moml.toString(); 5429 } 5430 5431 /** Return the MoML commands to undo deleting the specified entity 5432 * from the current context. Unless the container implements 5433 * the marker interface HandlesInternalLinks, this will generate 5434 * undo MoML that takes care of re-creating any links that get 5435 * broken as a result of deleting the specified entity. 5436 * If both ends of the link are defined in the base class, 5437 * however, then the link is not reported. 5438 * @param toDelete The component to delete. 5439 */ 5440 private String _getUndoForDeleteEntity(ComponentEntity toDelete) 5441 throws IOException { 5442 // Set the context to the immediate container. 5443 StringBuffer moml = new StringBuffer( 5444 UndoContext.moveContextStart(_current, toDelete)); 5445 5446 int depth = toDelete.depthInHierarchy() - _current.depthInHierarchy(); 5447 5448 // It possible that _current doesn't contain toDelete. 5449 // In particular, _current may be the container of an object 5450 // from which toDelete is derived. If this is the case, we still 5451 // want to generate the undo MoML as if toDelete were contained 5452 // by _current because, presumably, we will also be generating 5453 // undo MoML for the deletion of the master object inside _current. 5454 // Thus, we want the MoML generated here to only include overrides. 5455 if (depth < 0) { 5456 depth = 0; 5457 } 5458 5459 // Add in the description. 5460 // Need to use the depth to determine how much MoML to export. 5461 StringWriter buffer = new StringWriter(); 5462 toDelete.exportMoML(buffer, depth); 5463 moml.append(buffer.toString()); 5464 5465 CompositeEntity container = (CompositeEntity) toDelete.getContainer(); 5466 5467 if (container instanceof HandlesInternalLinks) { 5468 // If there is no body, don't return the context either. 5469 if (buffer.toString().trim().equals("")) { 5470 return ""; 5471 } 5472 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5473 return moml.toString(); 5474 } 5475 5476 // Now create the undo that will recreate any links 5477 // the are deleted as a side effect. 5478 // This is a little tricky, because if the entity 5479 // is defined in the base class, then we only want 5480 // to include links that override the base class. 5481 HashSet filter = new HashSet(); 5482 if (toDelete.getDerivedLevel() == Integer.MAX_VALUE) { 5483 // The entity is not derived, so all links should be included. 5484 // NOTE: cannot use the relationlist as returned as it is 5485 // unmodifiable and we need to add in the entity being deleted. 5486 filter.addAll(toDelete.linkedRelationList()); 5487 } else { 5488 // The entity is derived, so we should only include 5489 // relations that are not derived. 5490 Iterator relations = toDelete.linkedRelationList().iterator(); 5491 while (relations.hasNext()) { 5492 Relation relation = (Relation) relations.next(); 5493 if (relation != null) { 5494 if (relation.getDerivedLevel() == Integer.MAX_VALUE) { 5495 filter.add(relation); 5496 } 5497 } 5498 } 5499 } 5500 filter.add(toDelete); 5501 5502 String links = container.exportLinks(0, filter); 5503 // The parent container can do the filtering and generate the MoML. 5504 moml.append(links); 5505 5506 // Finally move back to context if needed 5507 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5508 5509 // If there is no body, don't return the context either. 5510 if (buffer.toString().trim().equals("") && links.trim().equals("")) { 5511 return ""; 5512 } 5513 5514 return moml.toString(); 5515 } 5516 5517 /** Return the MoML commands to undo deleting the specified port 5518 * from the current context. Unless the container implements 5519 * the marker interface HandlesInternalLinks, this will generate 5520 * undo MoML that takes care of re-creating any inside links that get 5521 * broken as a result of deleting the specified port. Unless the 5522 * container of the container implements HandlesInternalLinks, 5523 * this will also generate undo to recreate the outside links. 5524 * @param toDelete The component to delete. 5525 */ 5526 private String _getUndoForDeletePort(Port toDelete) throws IOException { 5527 // Set the context to the immediate container. 5528 StringBuffer moml = new StringBuffer( 5529 UndoContext.moveContextStart(_current, toDelete)); 5530 5531 int depth = toDelete.depthInHierarchy() - _current.depthInHierarchy(); 5532 5533 // Is it possible that _current doesn't contain toDelete? 5534 if (!_current.deepContains(toDelete)) { 5535 depth = 0; 5536 } 5537 // Keep track of whether any moml code is actually generated. 5538 boolean bodyEmpty = true; 5539 5540 // Add in the description. 5541 // Need to use the depth to determine how much MoML to export. 5542 StringWriter buffer = new StringWriter(); 5543 toDelete.exportMoML(buffer, depth); 5544 if (!buffer.toString().trim().equals("")) { 5545 moml.append(buffer.toString()); 5546 bodyEmpty = false; 5547 } 5548 5549 NamedObj container = toDelete.getContainer(); 5550 5551 // Now create the undo that will recreate any links 5552 // the are deleted as a side effect. 5553 HashSet filter = new HashSet(); 5554 // NOTE: cannot use the relationlist as returned as it is 5555 // unmodifiable and we need to add in the entity being deleted. 5556 filter.addAll(toDelete.linkedRelationList()); 5557 if (container != null && !(container instanceof HandlesInternalLinks)) { 5558 if (toDelete instanceof ComponentPort) { 5559 filter.addAll(((ComponentPort) toDelete).insideRelationList()); 5560 } 5561 } 5562 filter.add(toDelete); 5563 5564 // Generate the undo MoML for the inside links, if there are any. 5565 if (container instanceof CompositeEntity) { 5566 String links = ((CompositeEntity) container).exportLinks(depth, 5567 filter); 5568 if (!links.trim().equals("")) { 5569 moml.append(links); 5570 bodyEmpty = false; 5571 } 5572 } 5573 5574 // Move back to context if needed. 5575 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5576 5577 // The undo MoML for the outside links is trickier. 5578 // We have to move up in the hierarchy, so we need to generate 5579 // an absolute context. 5580 if (container != null) { 5581 NamedObj containerContainer = container.getContainer(); 5582 5583 if (containerContainer instanceof CompositeEntity 5584 && !(containerContainer instanceof HandlesInternalLinks)) { 5585 // Set the context to the container's container. 5586 moml.append(UndoContext.moveContextStart(_current, container)); 5587 // In theory, depth should not be zero, but just in case... 5588 if (depth == 0) { 5589 depth = 1; 5590 } 5591 String links = ((CompositeEntity) containerContainer) 5592 .exportLinks(depth - 1, filter); 5593 if (!links.trim().equals("")) { 5594 moml.append(links); 5595 bodyEmpty = false; 5596 } 5597 moml.append(UndoContext.moveContextEnd(_current, container)); 5598 } 5599 } 5600 // If there is no body, don't return MoML with just the context. 5601 if (bodyEmpty) { 5602 return ""; 5603 } 5604 5605 return moml.toString(); 5606 } 5607 5608 /** Return the MoML commands to undo deleting the specified relation 5609 * from the current context. Unless the container implements 5610 * the marker interface HandlesInternalLinks, this will generate 5611 * undo MoML that takes care of re-creating any links that get 5612 * broken as a result of deleting the specified relation. 5613 * @param toDelete The component to delete. 5614 */ 5615 private String _getUndoForDeleteRelation(ComponentRelation toDelete) 5616 throws IOException { 5617 // Set the context to the immediate container. 5618 StringBuffer moml = new StringBuffer( 5619 UndoContext.moveContextStart(_current, toDelete)); 5620 5621 // Add in the description. 5622 // By specifying the depth to be the depth in the hierarchy, 5623 // we ensure that if the object is implied, then no MoML 5624 // is exported, unless to override parameter values. 5625 int depth = toDelete.depthInHierarchy() - _current.depthInHierarchy(); 5626 5627 // Is it possible that _current doesn't contain toDelete? 5628 if (!_current.deepContains(toDelete)) { 5629 depth = 0; 5630 } 5631 5632 // Add in the description. 5633 // Need to use the depth to determine how much MoML to export. 5634 StringWriter buffer = new StringWriter(); 5635 toDelete.exportMoML(buffer, depth); 5636 moml.append(buffer.toString()); 5637 5638 CompositeEntity container = (CompositeEntity) toDelete.getContainer(); 5639 5640 if (container instanceof HandlesInternalLinks) { 5641 // If there is no body, don't return the context either. 5642 if (buffer.toString().trim().equals("")) { 5643 return ""; 5644 } 5645 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5646 return moml.toString(); 5647 } 5648 5649 // Generate undo to recreate the links. 5650 // This is a little tricky, because if the relation 5651 // is defined in the base class, then we only want 5652 // to include links that override the base class. 5653 HashSet filter = new HashSet(); 5654 if (toDelete.getDerivedLevel() == Integer.MAX_VALUE) { 5655 // The entity is not derived, so all links should be included. 5656 // NOTE: cannot use the relationlist as returned as it is 5657 // unmodifiable and we need to add in the entity being deleted. 5658 filter.addAll(toDelete.linkedObjectsList()); 5659 } else { 5660 // The relation is derived, so we should only include 5661 // relations that are not derived. 5662 Iterator objects = toDelete.linkedObjectsList().iterator(); 5663 while (objects.hasNext()) { 5664 NamedObj portOrRelation = (NamedObj) objects.next(); 5665 if (portOrRelation != null) { 5666 if (portOrRelation.getDerivedLevel() == Integer.MAX_VALUE) { 5667 filter.add(portOrRelation); 5668 } 5669 } 5670 } 5671 } 5672 filter.add(toDelete); 5673 5674 // The parent container can do the filtering and generate the 5675 // MoML. 5676 String links = container.exportLinks(0, filter); 5677 moml.append(links); 5678 if (buffer.toString().trim().equals("") && links.trim().equals("")) { 5679 // No body. 5680 return ""; 5681 } 5682 5683 // Move back to context if needed. 5684 moml.append(UndoContext.moveContextEnd(_current, toDelete)); 5685 5686 return moml.toString(); 5687 } 5688 5689 /** Create a property and/or set its value. 5690 * @param className The class name field, if present. 5691 * @param propertyName The property name field. 5692 * @param value The value, if present. 5693 * @exception Exception If something goes wrong. 5694 */ 5695 private void _handlePropertyElement(String className, String propertyName, 5696 final String value) throws Exception { 5697 // First handle special properties that are not translated 5698 // into Ptolemy II attributes. 5699 // Note that we have to push something on to the 5700 // stack so that we can pop it off later. 5701 // An xml version of the FSM ABP demo tickled this bug 5702 boolean isIOPort = _current instanceof IOPort; 5703 5704 if (propertyName.equals("multiport") && isIOPort) { 5705 // Special properties that affect the behaviour of a port 5706 // NOTE: UNDO: Consider refactoring these clauses 5707 // to remove the duplicate values 5708 // The previous value is needed to generate undo MoML 5709 IOPort currentIOPort = (IOPort) _current; 5710 5711 // The mere presense of a named property "multiport" 5712 // makes the enclosing port a multiport, unless it 5713 // has value false. 5714 // Get the previous value to use when generating the 5715 // undo MoML 5716 boolean previousValue = currentIOPort.isMultiport(); 5717 5718 // Default for new value is true, unless it is explicitly false. 5719 boolean newValue = true; 5720 5721 if (value != null && value.trim().toLowerCase(Locale.getDefault()) 5722 .equals("false")) { 5723 newValue = false; 5724 } 5725 5726 // If this object is a derived object, then its I/O status 5727 // cannot be changed. 5728 if (_current.getDerivedLevel() < Integer.MAX_VALUE 5729 // FindBugs reports a problem with the cast, but 5730 // isIOPort is set by checking whether _current is 5731 // an instanceof IOPort, so the warning is superfluous 5732 && ((IOPort) _current).isMultiport() != newValue) { 5733 throw new IllegalActionException(_current, 5734 "Cannot change whether this port is " 5735 + "a multiport. That property is fixed by " 5736 + "the class definition."); 5737 } 5738 5739 // FindBugs reports a problem with the cast, but 5740 // isIOPort is set by checking whether _current is 5741 // an instanceof IOPort, so the warning is superfluous 5742 ((IOPort) _current).setMultiport(newValue); 5743 5744 // Propagate. 5745 Iterator derivedObjects = _current.getDerivedList().iterator(); 5746 5747 while (derivedObjects.hasNext()) { 5748 IOPort derived = (IOPort) derivedObjects.next(); 5749 derived.setMultiport(newValue); 5750 } 5751 5752 _pushContext(); 5753 _current = _current.getAttribute(propertyName); 5754 _namespace = _DEFAULT_NAMESPACE; 5755 5756 // Handle undo 5757 if (_undoEnabled) { 5758 _undoContext.appendUndoMoML( 5759 "<property name=\"" + propertyName + "\" value=\""); 5760 5761 // Use what was there before. 5762 _undoContext.appendUndoMoML(previousValue + "\" >\n"); 5763 5764 // Continue undoing and also use an end tag as a 5765 // property can contain other properties 5766 _undoContext.setChildrenUndoable(true); 5767 _undoContext.appendClosingUndoMoML("</property>\n"); 5768 } 5769 } else if (propertyName.equals("output") && isIOPort) { 5770 // Special properties that affect the behaviour of a port 5771 // NOTE: UNDO: Consider refactoring these clauses 5772 // to remove the duplicate values 5773 // The previous value is needed to generate undo MoML 5774 IOPort currentIOPort = (IOPort) _current; 5775 5776 // Get the previous value to use when generating the 5777 // undo MoML 5778 boolean previousValue = currentIOPort.isOutput(); 5779 5780 // Default for new value is true, unless it is explicitly false. 5781 boolean newValue = true; 5782 5783 if (value != null && value.trim().toLowerCase(Locale.getDefault()) 5784 .equals("false")) { 5785 newValue = false; 5786 } 5787 5788 // If this object is a derived object, then its I/O status 5789 // cannot be changed. 5790 if (_current.getDerivedLevel() < Integer.MAX_VALUE 5791 // FindBugs reports a problem with the cast, but 5792 // isIOPort is set by checking whether _current is 5793 // an instanceof IOPort, so the warning is superfluous 5794 && ((IOPort) _current).isOutput() != newValue) { 5795 throw new IllegalActionException(_current, 5796 "Cannot change whether this port is " 5797 + "an output. That property is fixed by " 5798 + "the class definition."); 5799 } 5800 5801 // FindBugs reports a problem with the cast, but 5802 // isIOPort is set by checking whether _current is 5803 // an instanceof IOPort, so the warning is superfluous 5804 ((IOPort) _current).setOutput(newValue); 5805 5806 // Propagate. 5807 Iterator derivedObjects = _current.getDerivedList().iterator(); 5808 5809 while (derivedObjects.hasNext()) { 5810 IOPort derived = (IOPort) derivedObjects.next(); 5811 derived.setOutput(newValue); 5812 } 5813 5814 _pushContext(); 5815 5816 _current = _current.getAttribute(propertyName); 5817 _namespace = _DEFAULT_NAMESPACE; 5818 5819 // Handle undo 5820 if (_undoEnabled) { 5821 _undoContext.appendUndoMoML( 5822 "<property name=\"" + propertyName + "\" value=\""); 5823 5824 // Use what was there before 5825 _undoContext.appendUndoMoML(previousValue + "\" >\n"); 5826 5827 // Continue undoing and also use an end tag as a 5828 // property can contain other properties 5829 _undoContext.setChildrenUndoable(true); 5830 _undoContext.appendClosingUndoMoML("</property>\n"); 5831 } 5832 } else if (propertyName.equals("input") && isIOPort) { 5833 // Special properties that affect the behaviour of a port 5834 // NOTE: UNDO: Consider refactoring these clauses 5835 // to remove the duplicate values 5836 // The previous value is needed to generate undo MoML 5837 IOPort currentIOPort = (IOPort) _current; 5838 5839 // Get the previous value to use when generating the 5840 // undo MoML 5841 boolean previousValue = currentIOPort.isInput(); 5842 5843 // Default for new value is true, unless it is explicitly false. 5844 boolean newValue = true; 5845 5846 if (value != null && value.trim().toLowerCase(Locale.getDefault()) 5847 .equals("false")) { 5848 newValue = false; 5849 } 5850 5851 // If this object is a derived object, then its I/O status 5852 // cannot be changed. 5853 if (_current.getDerivedLevel() < Integer.MAX_VALUE 5854 // FindBugs reports a problem with the cast, but 5855 // isIOPort is set by checking whether _current is 5856 // an instanceof IOPort, so the warning is superfluous 5857 && ((IOPort) _current).isInput() != newValue) { 5858 throw new IllegalActionException(_current, 5859 "Cannot change whether this port is " 5860 + "an input. That property is fixed by " 5861 + "the class definition."); 5862 } 5863 5864 // FindBugs reports a problem with the cast, but 5865 // isIOPort is set by checking whether _current is 5866 // an instanceof IOPort, so the warning is superfluous 5867 ((IOPort) _current).setInput(newValue); 5868 5869 // Propagate. 5870 Iterator derivedObjects = _current.getDerivedList().iterator(); 5871 5872 while (derivedObjects.hasNext()) { 5873 IOPort derived = (IOPort) derivedObjects.next(); 5874 derived.setInput(newValue); 5875 } 5876 5877 _pushContext(); 5878 _current = _current.getAttribute(propertyName); 5879 _namespace = _DEFAULT_NAMESPACE; 5880 5881 // Handle undo 5882 if (_undoEnabled) { 5883 _undoContext.appendUndoMoML( 5884 "<property name=\"" + propertyName + "\" value=\""); 5885 5886 // Use what was there before 5887 _undoContext.appendUndoMoML(previousValue + "\" >\n"); 5888 5889 // Continue undoing and also use an end tag as a 5890 // property can contain other properties 5891 _undoContext.setChildrenUndoable(true); 5892 _undoContext.appendClosingUndoMoML("</property>\n"); 5893 } 5894 } else { 5895 // Ordinary attribute. 5896 NamedObj property = null; 5897 5898 if (_current != null) { 5899 property = _current.getAttribute(propertyName); 5900 } 5901 5902 Class newClass = null; 5903 5904 if (className != null) { 5905 try { 5906 newClass = _loadClass(className, null); 5907 } catch (ClassNotFoundException ex) { 5908 throw new XmlException( 5909 "Failed to find class '" + className + "'", 5910 _currentExternalEntity(), _getLineNumber(), 5911 _getColumnNumber(), ex); 5912 } catch (NoClassDefFoundError ex) { 5913 throw new XmlException( 5914 "Failed to find class '" + className + "'", 5915 _currentExternalEntity(), _getLineNumber(), 5916 _getColumnNumber(), ex); 5917 } catch (SecurityException ex) { 5918 // An applet might throw this. 5919 throw new XmlException( 5920 "Failed to find class '" + className + "'", 5921 _currentExternalEntity(), _getLineNumber(), 5922 _getColumnNumber(), ex); 5923 } 5924 } 5925 5926 // If there is a previous property with this name 5927 // (property is not null), then we check that the 5928 // property is an instance of the specified class. 5929 // If it is, then we set the value of the property. 5930 // Otherwise, we try to replace it, something that 5931 // will only work if it is a singleton (it might throw 5932 // NameDuplicationException). 5933 boolean previouslyExisted = property != null; 5934 5935 // Even if the object previously existed, if the 5936 // class does not match, we may create a new object. 5937 boolean createdNew = false; 5938 5939 // Also need the previous value, if any, to generate undo MoML. 5940 String oldClassName = null; 5941 String oldValue = null; 5942 5943 if (previouslyExisted) { 5944 oldClassName = property.getClass().getName(); 5945 5946 if (property instanceof Settable) { 5947 Settable settable = (Settable) property; 5948 oldValue = settable.getExpression(); 5949 } 5950 } 5951 5952 if (!previouslyExisted 5953 || newClass != null && !newClass.isInstance(property)) { 5954 // The following will result in a 5955 // NameDuplicationException if there is a previous 5956 // property and it is not a singleton. 5957 try { 5958 // No previously existing attribute with this name, 5959 // or the class name of the previous entity doesn't 5960 // match. 5961 boolean isNewClassSetToAttribute = false; 5962 if (newClass == null) { 5963 isNewClassSetToAttribute = true; 5964 newClass = Attribute.class; 5965 } 5966 5967 // An attribute is not usually a top-level element, 5968 // but it might be (e.g. when editing icons). 5969 if (_current == null) { 5970 // If we want to be able to edit icons, we 5971 // have to allow this. 5972 // Invoke the constructor. 5973 Object[] arguments = new Object[2]; 5974 arguments[0] = _workspace; 5975 arguments[1] = propertyName; 5976 property = _createInstance(newClass, arguments); 5977 if (_topObjectsCreated != null 5978 && _current == _originalContext) { 5979 _topObjectsCreated.add(property); 5980 } 5981 _toplevel = property; 5982 } else { 5983 // First check that there will be no name collision 5984 // when this is propagated. Note that we need to 5985 // include all derived objects, irrespective of whether 5986 // they are locally changed. 5987 List derivedList = _current.getDerivedList(); 5988 Iterator derivedObjects = derivedList.iterator(); 5989 5990 while (derivedObjects.hasNext()) { 5991 NamedObj derived = (NamedObj) derivedObjects.next(); 5992 Attribute other = derived 5993 .getAttribute(propertyName); 5994 5995 if (other != null 5996 && !(other instanceof Singleton)) { 5997 // If the derived is within an EntityLibrary, 5998 // then don't throw an exception. To 5999 // replicate this, create an EntityLibrary 6000 // within the UserLibrary, save and then try 6001 // to edit the UserLibrary. 6002 boolean derivedIsNotWithinEntityLibrary = true; 6003 NamedObj derivedContainer = derived; 6004 while (derivedContainer != null 6005 && (derivedIsNotWithinEntityLibrary = !(derivedContainer instanceof EntityLibrary))) { 6006 derivedContainer = derivedContainer 6007 .getContainer(); 6008 } 6009 if (derivedIsNotWithinEntityLibrary) { 6010 throw new IllegalActionException(_current, 6011 "Cannot create attribute because a subclass or instance " 6012 + "contains an attribute with the same name: " 6013 + derived.getAttribute( 6014 propertyName) 6015 .getFullName()); 6016 } 6017 } 6018 } 6019 6020 // Invoke the constructor. 6021 Object[] arguments = new Object[2]; 6022 arguments[0] = _current; 6023 arguments[1] = propertyName; 6024 property = _createInstance(newClass, arguments); 6025 6026 // When loading icons, the attribute may be the top-level object 6027 // in the MoML being parsed. 6028 if (_topObjectsCreated != null 6029 && _current == _originalContext) { 6030 _topObjectsCreated.add(property); 6031 } 6032 6033 // Why was this restricted to Directors?? Efficiency? 6034 // If this creates an efficiency problem, then we should 6035 // create a marker interface that Director and WebServer, 6036 // at least, implement. 6037 // if (property instanceof ptolemy.actor.Director) { 6038 if (className != null) { 6039 // className can be null, to replicate: 6040 // (cd $PTII/doc; make test) 6041 _loadIconForClass(className, property); 6042 } 6043 // Check that the result is an instance of Attribute. 6044 if (!(property instanceof Attribute)) { 6045 // NOTE: Need to get rid of the object. 6046 // Unfortunately, setContainer() is not defined, 6047 // so we have to find the right class. 6048 if (property instanceof ComponentEntity) { 6049 ((ComponentEntity) property).setContainer(null); 6050 } else if (property instanceof Port) { 6051 ((Port) property).setContainer(null); 6052 } else if (property instanceof ComponentRelation) { 6053 ((ComponentRelation) property) 6054 .setContainer(null); 6055 } 6056 6057 throw new XmlException("Property \"" 6058 + property.getFullName() + "\" is not an " 6059 + "instance of Attribute, it is a \"" 6060 + property.getClass().getName() + "\"." 6061 + (property instanceof Entity 6062 ? "The property is an instance " 6063 + "of the Entity class, " 6064 + "so if you were using " 6065 + "the GUI, try \"Instantiate" 6066 + "Entity\" or \"Instantiate" 6067 + "Component\"." 6068 : "") 6069 + (isNewClassSetToAttribute 6070 ? " The class \"" + className 6071 + "\" was not found in the " 6072 + "classpath." 6073 : ""), 6074 _currentExternalEntity(), _getLineNumber(), 6075 _getColumnNumber()); 6076 } 6077 6078 // Propagate. 6079 property.propagateExistence(); 6080 } 6081 6082 if (value != null) { 6083 if (property == null) { 6084 throw new XmlException( 6085 "Property does not exist: " + propertyName 6086 + "\n", 6087 _currentExternalEntity(), _getLineNumber(), 6088 _getColumnNumber()); 6089 } 6090 6091 if (!(property instanceof Settable)) { 6092 throw new XmlException( 6093 "Property cannot be assigned a value: " 6094 + property.getFullName() 6095 + " (instance of " 6096 + property.getClass().toString() 6097 + ")\n", 6098 _currentExternalEntity(), _getLineNumber(), 6099 _getColumnNumber()); 6100 } 6101 6102 Settable settable = (Settable) property; 6103 settable.setExpression(value); 6104 _paramsToParse.add(settable); 6105 6106 // Propagate. This has the side effect of marking 6107 // the object overridden. 6108 property.propagateValue(); 6109 } 6110 6111 createdNew = true; 6112 } catch (NameDuplicationException ex) { 6113 // Ignore, so we can try to set the value. 6114 // The createdNew variable will still be false. 6115 } 6116 } 6117 6118 if (!createdNew) { 6119 // Previously existing property with this name, 6120 // whose class name exactly matches, or the class 6121 // name does not match, but a NameDuplicationException 6122 // was thrown (meaning the attribute was not 6123 // a singleton). 6124 // If value is null and the property already 6125 // exists, then there is nothing to do. 6126 if (value != null) { 6127 if (!(property instanceof Settable)) { 6128 throw new XmlException( 6129 "Property is not an " + "instance of Settable, " 6130 + "so can't set the value.", 6131 _currentExternalEntity(), _getLineNumber(), 6132 _getColumnNumber()); 6133 } 6134 6135 Settable settable = (Settable) property; 6136 //String previousValue = settable.getExpression(); 6137 6138 // If we are within a group named "doNotOverwriteOverrides", 6139 // then set the expression only if either the parameter did not 6140 // previously exist or its value has not been overridden. 6141 if (_overwriteOverrides <= 0 || !previouslyExisted 6142 || !property.isOverridden()) { 6143 // NOTE: It is not correct to do nothing even 6144 // if the value is not changed. If the value of 6145 // of an instance parameter is explicitly set, 6146 // and that value happens to be the same as the 6147 // value in the base class, then it should keep 6148 // that value even if the base class later changes. 6149 // if (!value.equals(previousValue)) { 6150 settable.setExpression(value); 6151 6152 // Propagate. This has the side effect of marking 6153 // the object overridden. 6154 property.propagateValue(); 6155 6156 _paramsToParse.add(settable); 6157 } 6158 } 6159 } 6160 6161 _pushContext(); 6162 _current = property; 6163 6164 _namespace = _DEFAULT_NAMESPACE; 6165 6166 // Handle the undo aspect if needed 6167 if (_undoEnabled) { 6168 if (!previouslyExisted) { 6169 // Need to delete the property in the undo MoML 6170 _undoContext.appendUndoMoML("<deleteProperty name=\"" 6171 + propertyName + "\" />\n"); 6172 6173 // Do not need to continue generating undo MoML 6174 // as the deleteProperty takes care of all 6175 // contained MoML 6176 _undoContext.setChildrenUndoable(false); 6177 _undoContext.setUndoable(false); 6178 6179 // Prevent any further undo entries for this context. 6180 _undoEnabled = false; 6181 } else { 6182 // Simply generate the same as was there before. 6183 _undoContext.appendUndoMoML( 6184 "<property name=\"" + property.getName() + "\" "); 6185 _undoContext 6186 .appendUndoMoML("class=\"" + oldClassName + "\" "); 6187 6188 if (oldValue != null) { 6189 // Escape the value for xml so that if 6190 // the property the user typed in was 6191 // a Parameter "foo", we do not have 6192 // problems. To replicate this, 6193 // create a Const with a value "foo" 6194 // and then change it to 2 and then 6195 // try undo. 6196 _undoContext.appendUndoMoML("value=\"" 6197 + StringUtilities.escapeForXML(oldValue) 6198 + "\" "); 6199 } 6200 6201 _undoContext.appendUndoMoML(">\n"); 6202 6203 // Add the closing element 6204 _undoContext.appendClosingUndoMoML("</property>\n"); 6205 6206 // Need to continue generating undo MoML as a 6207 // property can have other children 6208 _undoContext.setChildrenUndoable(true); 6209 } 6210 } 6211 } 6212 } 6213 6214 /** Return true if the link between the specified port and 6215 * relation is part of the class definition. It is part of the 6216 * class definition if either the port and the relation are 6217 * at the same level of hierarchy and are both derived objects, or if 6218 * the relation and the container of the port are both class 6219 * elements. If the relation is null, then this return true 6220 * if the port and its container are derived objects. 6221 * NOTE: This is not perfect, since a link could have been 6222 * created between these elements in a subclass. 6223 * @param context The context containing the link. 6224 * @param port The port. 6225 * @param relation The relation. 6226 * @return True if the link is part of the class definition. 6227 */ 6228 private boolean _isLinkInClass(NamedObj context, Port port, 6229 Relation relation) { 6230 // If the context is the container of the port, then 6231 // this is an inside link. In that case, even if this 6232 // is a level-crossing link, the port itself has to be a 6233 // derived object. Otherwise, we check to see whether the 6234 // container of the port is a derived object. 6235 int portContainerLevel = port.getContainer().getDerivedLevel(); 6236 int portLevel = port.getDerivedLevel(); 6237 6238 // The decision is slightly different if the port is immediately 6239 // contained by the context because in that case we presume 6240 // it is an inside link. Since an inside link can only occur 6241 // with a relation deeply contained by the container of the 6242 // port, there is no need to check whether both the relation 6243 // and the link are derived by the same container. If they are 6244 // both derived, then they are both derived. 6245 if (port.getContainer() == context) { 6246 return portLevel < Integer.MAX_VALUE && (relation == null 6247 || relation.getDerivedLevel() < Integer.MAX_VALUE); 6248 } 6249 boolean portIsInClass = port.getContainer() == context 6250 ? portLevel < Integer.MAX_VALUE 6251 : portContainerLevel < Integer.MAX_VALUE; 6252 if (portIsInClass) { 6253 if (relation == null) { 6254 // Inserting a blank link in a multiport. 6255 // NOTE: This used to return true, which would 6256 // trigger an exception. But why would this be disallowed? 6257 return false; 6258 } 6259 int relationLevel = relation.getDerivedLevel(); 6260 if (relationLevel < Integer.MAX_VALUE) { 6261 // Check that the container above at which these two objects 6262 // are implied is the same container. 6263 NamedObj relationContainer = relation; 6264 while (relationLevel > 0) { 6265 relationContainer = relationContainer.getContainer(); 6266 relationLevel--; 6267 // It's not clear to me how this occur, but if relationCaontiner 6268 // is null, then clearly there is no common container that 6269 // implies the two objects. 6270 if (relationContainer == null) { 6271 return false; 6272 } 6273 } 6274 NamedObj portContainer = port; 6275 // Handle inside links slightly differently from outside links. 6276 if (port.getContainer() == context) { 6277 // Inside link. 6278 while (portLevel > 0) { 6279 portContainer = portContainer.getContainer(); 6280 portLevel--; 6281 // It's not clear to me how this occur, but if relationCaontiner 6282 // is null, then clearly there is no common container that 6283 // implies the two objects. 6284 if (portContainer == null) { 6285 return false; 6286 } 6287 } 6288 } else { 6289 // Outside link. 6290 portContainer = port.getContainer(); 6291 while (portContainerLevel > 0) { 6292 // It's not clear to me how this occur, but if relationCaontiner 6293 // is null, then clearly there is no common container that 6294 // implies the two objects. 6295 if (portContainer == null) { 6296 return false; 6297 } 6298 portContainer = portContainer.getContainer(); 6299 portContainerLevel--; 6300 } 6301 } 6302 if (relationContainer == portContainer) { 6303 return true; 6304 } 6305 } 6306 } 6307 return false; 6308 } 6309 6310 /** Return true if the link between the specified relations 6311 * is part of the class definition. It is part of the 6312 * class definition if both are derived objects, and the 6313 * container whose parent-child relationship makes them 6314 * derived is the same container. 6315 * NOTE: This is not perfect, since a link could have been 6316 * created between these elements in a subclass. 6317 * @param context The context containing the link. 6318 * @param relation1 The first relation. 6319 * @param relation2 The second relation. 6320 * @return True if the link is part of the class definition. 6321 */ 6322 private boolean _isLinkInClass(NamedObj context, Relation relation1, 6323 Relation relation2) { 6324 // Careful: It is possible for both relations to be derived 6325 // but the link not be defined within the same class definition. 6326 int relation1Level = relation1.getDerivedLevel(); 6327 int relation2Level = relation2.getDerivedLevel(); 6328 if (relation1Level < Integer.MAX_VALUE 6329 && relation2Level < Integer.MAX_VALUE) { 6330 // Check that the container above at which these two objects 6331 // are implied is the same container. 6332 NamedObj relation1Container = relation1; 6333 while (relation1Level > 0) { 6334 relation1Container = relation1Container.getContainer(); 6335 relation1Level--; 6336 // It's not clear to me how this occur, but if relationCaontiner 6337 // is null, then clearly there is no common container that 6338 // implies the two objects. 6339 if (relation1Container == null) { 6340 return false; 6341 } 6342 } 6343 NamedObj relation2Container = relation2; 6344 while (relation2Level > 0) { 6345 relation2Container = relation2Container.getContainer(); 6346 relation2Level--; 6347 // It's not clear to me how this occur, but if relationCaontiner 6348 // is null, then clearly there is no common container that 6349 // implies the two objects. 6350 if (relation2Container == null) { 6351 return false; 6352 } 6353 } 6354 if (relation1Container == relation2Container) { 6355 return true; 6356 } 6357 } 6358 return false; 6359 } 6360 6361 /** Return whether or not the given element name is undoable. NOTE: we need 6362 * this method as the list of actions on namespaces and _current does not 6363 * apply to elements such as "link" 6364 * @param elementName Description of Parameter 6365 * @return Description of the Returned Value 6366 * @since Ptolemy 2.1 6367 */ 6368 private boolean _isUndoableElement(String elementName) { 6369 // The following serves as documentation of sorts for which 6370 // elements usage is undoable 6371 // NOTE: property appears first for reasons of efficency. 6372 if (elementName.equals("property") || elementName.equals("class") 6373 || elementName.equals("doc") 6374 || elementName.equals("deleteEntity") 6375 || elementName.equals("deletePort") 6376 || elementName.equals("deleteProperty") 6377 || elementName.equals("deleteRelation") 6378 || elementName.equals("display") || elementName.equals("entity") 6379 || elementName.equals("group") || elementName.equals("link") 6380 || elementName.equals("port") || elementName.equals("relation") 6381 || elementName.equals("rename") || elementName.equals("unlink") 6382 || elementName.equals("vertex")) { 6383 return true; 6384 } 6385 6386 return false; 6387 } 6388 6389 private Class _loadClass(String className, VersionSpecification versionSpec) 6390 throws ClassNotFoundException { 6391 // If no specific version info was in the model's MOML, and a default version spec was set, we need to use that one. 6392 // This is especially important for submodels based on actor-oriented-classes, where we want to maintain some consistency 6393 // between a related "group" of MOMLs (the parent models and their submodels). 6394 VersionSpecification _vSpec = versionSpec != null ? versionSpec 6395 : _defaultVersionSpecification; 6396 try { 6397 return _defaultClassLoadingStrategy.loadJavaClass(className, 6398 _vSpec); 6399 } catch (ClassNotFoundException e) { 6400 // System.out.println("Did not find "+className+" "+_vSpec + " via " + _defaultClassLoadingStrategy); 6401 if (_classLoader != null) { 6402 return _classLoader.loadClass(className); 6403 } else { 6404 throw e; 6405 } 6406 } 6407 } 6408 6409 /** If the file with the specified name exists, parse it in 6410 * the context of the specified instance. If it does not 6411 * exist, do nothing. If the file creates new objects, 6412 * then mark those objects as a class 6413 * element with the same depth as the context. 6414 * @param fileName The file name. 6415 * @param context The context into which to load the file. 6416 * @return True if a file was found. 6417 * @exception Exception If the file exists but cannot be read 6418 * for some reason. 6419 */ 6420 private boolean _loadFileInContext(String fileName, NamedObj context) 6421 throws Exception { 6422 if (_classLoader == null) { 6423 throw new InternalErrorException("_classloader is null? " 6424 + "If you are using Eclipse, then perhaps the ptII project is in the boothpath? " 6425 + "Check Run -> Run Configurations... -> Classpath and be sure that the ptII project " 6426 + "is not in the Bootstrap Entries section."); 6427 } 6428 URL xmlFile = _classLoader.getResource(fileName); 6429 6430 if (xmlFile == null) { 6431 return false; 6432 } 6433 6434 InputStream input = FileUtilities.openStreamFollowingRedirects(xmlFile); 6435 6436 // Read the external file in the current context, but with 6437 // a new parser. I'm not sure why the new parser is needed, 6438 // but the "input" element handler does the same thing. 6439 // NOTE: Should we keep the parser to re-use? 6440 boolean modified = isModified(); 6441 MoMLParser newParser = new MoMLParser(_workspace, _classLoader); 6442 6443 // setContext() calls reset(), which sets the modified 6444 // flag to false. Thus, we cache the value of the modified 6445 // flag. 6446 // See test 13.2 in 6447 // $PTII/ptolemy/moml/filter/test/BackwardCompatibility.tcl 6448 // which has a backward compatibility problem and loads a filter. 6449 newParser.setContext(context); 6450 setModified(modified); 6451 6452 // Create a list to keep track of objects created. 6453 newParser._topObjectsCreated = new LinkedList(); 6454 6455 // We don't need the results of the parse because 6456 // the context for the parser has been set so the 6457 // objects are already in the hierarchy. 6458 /*NamedObj result = */newParser.parse(_base, fileName, input); 6459 6460 // Have to mark the contents derived objects, so that 6461 // the icon is not exported with the MoML export. 6462 Iterator objects = newParser._topObjectsCreated.iterator(); 6463 6464 while (objects.hasNext()) { 6465 NamedObj newObject = (NamedObj) objects.next(); 6466 newObject.setDerivedLevel(1); 6467 _markContentsDerived(newObject, 1); 6468 } 6469 6470 return true; 6471 } 6472 6473 /** Look for a MoML file associated with the specified class 6474 * name, and if it exists, parse it in the context of the 6475 * specified instance. 6476 * If {@link #setIconLoader(IconLoader)} has been called with 6477 * a non-null argument, then invoke the 6478 * {@link ptolemy.moml.IconLoader#loadIconForClass(String, NamedObj)} 6479 * method. If {@link #setIconLoader(IconLoader)} has <b>not</b> 6480 * been called, or was called with a null argument, then 6481 * The file name is constructed from 6482 * the class name by replacing periods with file separators 6483 * ("/") and appending "Icon.xml". So, for example, for 6484 * the class name "ptolemy.actor.lib.Ramp", if there is a 6485 * file "ptolemy/actor/lib/RampIcon.xml" in the classpath 6486 * then that file be read. 6487 * @param className The class name. 6488 * @param context The context into which to load the file. 6489 * @return True if a file was found. 6490 * @exception Exception If the file exists but cannot be read 6491 * for some reason or if there is some other problem loading 6492 * the icon. 6493 * @see #setIconLoader(IconLoader) 6494 * @see ptolemy.moml.IconLoader 6495 */ 6496 private boolean _loadIconForClass(String className, NamedObj context) 6497 throws Exception { 6498 if (_iconLoader != null) { 6499 return _iconLoader.loadIconForClass(className, context); 6500 } else { 6501 // Default behavior if no icon loader has been specified. 6502 String fileName = className.replace('.', '/') + "Icon.xml"; 6503 return _loadFileInContext(fileName, context); 6504 } 6505 } 6506 6507 /** Mark the contents as being derived objects at a depth 6508 * one greater than the depth argument, and then recursively 6509 * mark their contents derived. 6510 * This makes them not export MoML, and prohibits name and 6511 * container changes. Normally, the argument is an Entity, 6512 * but this method will accept any NamedObj. 6513 * This method also adds all (deeply) contained instances 6514 * of Settable to the _paramsToParse list, which ensures 6515 * that they will be validated. 6516 * @param object The instance that is defined by a class. 6517 * @param depth The depth (normally 0). 6518 */ 6519 private void _markContentsDerived(NamedObj object, int depth) { 6520 // NOTE: It is necessary to mark objects deeply contained 6521 // so that we can disable deletion and name changes. 6522 // While we are at it, we add any 6523 // deeply contained Settables to the _paramsToParse list. 6524 Iterator objects = object.lazyContainedObjectsIterator(); 6525 6526 while (objects.hasNext()) { 6527 NamedObj containedObject = (NamedObj) objects.next(); 6528 containedObject.setDerivedLevel(depth + 1); 6529 6530 if (containedObject instanceof Settable) { 6531 _paramsToParse.add((Settable) containedObject); 6532 } 6533 6534 _markContentsDerived(containedObject, depth + 1); 6535 } 6536 } 6537 6538 /** Mark deeply contained parameters as needing validation. 6539 * This method adds all (deeply) contained instances 6540 * of Settable to the _paramsToParse list, which ensures 6541 * that they will be validated. 6542 * @param object The instance to mark. 6543 */ 6544 private void _markParametersToParse(NamedObj object) { 6545 Iterator objects = object.lazyContainedObjectsIterator(); 6546 6547 while (objects.hasNext()) { 6548 NamedObj containedObject = (NamedObj) objects.next(); 6549 6550 if (containedObject instanceof Settable) { 6551 _paramsToParse.add((Settable) containedObject); 6552 } 6553 6554 _markParametersToParse(containedObject); 6555 } 6556 } 6557 6558 /** Use the specified parser to parse the file or URL, 6559 * which contains MoML, using the specified base to find the URL. 6560 * If the URL cannot be found relative to this base, then it 6561 * is searched for relative to the current working directory 6562 * (if this is permitted with the current security restrictions), 6563 * and then relative to the classpath. 6564 * @param parser The parser to use. 6565 * @param base The base URL for relative references, or null if 6566 * not known. 6567 * @param source The URL from which to read MoML. 6568 * @return The top-level composite entity of the Ptolemy II model. 6569 * @exception Exception If the parser fails. 6570 */ 6571 private NamedObj _parse(MoMLParser parser, URL base, String source) 6572 throws Exception { 6573 _setXmlFile(fileNameToURL(source, base)); 6574 6575 InputStream input = null; 6576 6577 try { 6578 input = FileUtilities.openStreamFollowingRedirects(_xmlFile); 6579 try { 6580 NamedObj toplevel = parser.parse(_xmlFile, input); 6581 input.close(); 6582 return toplevel; 6583 } catch (CancelException ex) { 6584 // Parse operation cancelled. 6585 return null; 6586 } 6587 } finally { 6588 if (input != null) { 6589 try { 6590 input.close(); 6591 } catch (Throwable throwable) { 6592 System.out.println("Ignoring failure to close stream " 6593 + "on " + _xmlFile); 6594 throwable.printStackTrace(); 6595 } 6596 } 6597 6598 _setXmlFile(null); 6599 } 6600 } 6601 6602 /** Process a link command between two relations. 6603 * @param relation1Name The first relation name. 6604 * @param relation2Name The second relation name. 6605 * @exception XmlException 6606 * @exception IllegalActionException 6607 */ 6608 private void _processLink(String relation1Name, String relation2Name) 6609 throws XmlException, IllegalActionException { 6610 _checkClass(_current, CompositeEntity.class, 6611 "Element \"link\" found inside an element that " 6612 + "is not a CompositeEntity. It is: " + _current); 6613 6614 // Check that required arguments are given 6615 if (relation1Name == null || relation2Name == null) { 6616 throw new XmlException("Element link requires two relations.", 6617 _currentExternalEntity(), _getLineNumber(), 6618 _getColumnNumber()); 6619 } 6620 6621 CompositeEntity context = (CompositeEntity) _current; 6622 6623 // Get relations. 6624 ComponentRelation relation1 = context.getRelation(relation1Name); 6625 _checkForNull(relation1, "No relation named \"" + relation1Name 6626 + "\" in " + context.getFullName()); 6627 6628 // Get relations. 6629 ComponentRelation relation2 = context.getRelation(relation2Name); 6630 _checkForNull(relation2, "No relation named \"" + relation2Name 6631 + "\" in " + context.getFullName()); 6632 6633 // Ensure that derived objects aren't changed. 6634 // We have to prohit adding links between class 6635 // elements because this operation cannot be undone, and 6636 // it will not be persistent. 6637 if (_isLinkInClass(context, relation1, relation2)) { 6638 throw new IllegalActionException(relation1, relation2, 6639 "Cannot link relations when both" 6640 + " are part of the class definition."); 6641 } 6642 6643 relation1.link(relation2); 6644 6645 // Propagate. Get the derived list for relation1, 6646 // then use its container as the context in which to 6647 // find relation2. 6648 Iterator derivedObjects = relation1.getDerivedList().iterator(); 6649 6650 while (derivedObjects.hasNext()) { 6651 ComponentRelation derivedRelation1 = (ComponentRelation) derivedObjects 6652 .next(); 6653 CompositeEntity derivedContext = (CompositeEntity) derivedRelation1 6654 .getContainer(); 6655 ComponentRelation derivedRelation2 = derivedContext 6656 .getRelation(relation2Name); 6657 derivedRelation1.link(derivedRelation2); 6658 } 6659 6660 // Handle the undo aspect. 6661 if (_undoEnabled) { 6662 // Generate a link in the undo only if one or the other relation is 6663 // not derived. If they are both derived, then the link belongs to 6664 // the class definition and should not be undone in undo. 6665 if (relation1.getDerivedLevel() == Integer.MAX_VALUE 6666 || relation2.getDerivedLevel() == Integer.MAX_VALUE) { 6667 // Enclose in a group to prevent deferral on undo. 6668 _undoContext.appendUndoMoML("<group><unlink relation1=\"" 6669 + relation1Name + "\" relation2=\"" + relation2Name 6670 + "\" /></group>\n"); 6671 } 6672 } 6673 } 6674 6675 /** Process a link command between a port and a relation. 6676 * @param portName The port name. 6677 * @param relationName The relation name. 6678 * @param insertAtSpec The place to insert. 6679 * @param insertInsideAtSpec The place to insert inside. 6680 * @exception XmlException 6681 * @exception IllegalActionException 6682 */ 6683 private void _processLink(String portName, String relationName, 6684 String insertAtSpec, String insertInsideAtSpec) 6685 throws XmlException, IllegalActionException { 6686 _checkClass(_current, CompositeEntity.class, 6687 "Element \"link\" found inside an element that " 6688 + "is not a CompositeEntity. It is: " + _current); 6689 6690 int countArgs = 0; 6691 6692 // Check that one of the required arguments is given 6693 if (insertAtSpec != null) { 6694 countArgs++; 6695 } 6696 6697 if (insertInsideAtSpec != null) { 6698 countArgs++; 6699 } 6700 6701 if (relationName != null) { 6702 countArgs++; 6703 } 6704 6705 if (countArgs == 0) { 6706 throw new XmlException( 6707 "Element link requires at least one of " 6708 + "an insertAt, an insertInsideAt, or a relation.", 6709 _currentExternalEntity(), _getLineNumber(), 6710 _getColumnNumber()); 6711 } 6712 6713 if (insertAtSpec != null && insertInsideAtSpec != null) { 6714 throw new XmlException( 6715 "Element link requires at most one of " 6716 + "insertAt and insertInsideAt, not both.", 6717 _currentExternalEntity(), _getLineNumber(), 6718 _getColumnNumber()); 6719 } 6720 6721 CompositeEntity context = (CompositeEntity) _current; 6722 6723 // Parse port 6724 ComponentPort port = _getPort(portName, context); 6725 6726 // Save to help generate undo MoML 6727 int origNumOutsideLinks = port.numLinks(); 6728 int origNumInsideLinks = port.numInsideLinks(); 6729 6730 // Get relation if given 6731 ComponentRelation relation = null; 6732 6733 if (relationName != null) { 6734 Relation tmpRelation = context.getRelation(relationName); 6735 _checkForNull(tmpRelation, "No relation named \"" + relationName 6736 + "\" in " + context.getFullName()); 6737 relation = (ComponentRelation) tmpRelation; 6738 } 6739 6740 // Ensure that derived objects aren't changed. 6741 // We have to prohibit adding links between class 6742 // elements because this operation cannot be undone, and 6743 // it will not be persistent. 6744 if (_isLinkInClass(context, port, relation)) { 6745 throw new IllegalActionException(port, 6746 "Cannot link a port to a relation when both" 6747 + " are part of the class definition."); 6748 } 6749 6750 // Get the index if given 6751 int insertAt = -1; 6752 6753 if (insertAtSpec != null) { 6754 insertAt = Integer.parseInt(insertAtSpec); 6755 } 6756 6757 // Get the inside index if given 6758 int insertInsideAt = -1; 6759 6760 if (insertInsideAtSpec != null) { 6761 insertInsideAt = Integer.parseInt(insertInsideAtSpec); 6762 } 6763 6764 if (insertAtSpec != null) { 6765 port.insertLink(insertAt, relation); 6766 } else if (insertInsideAtSpec != null) { 6767 port.insertInsideLink(insertInsideAt, relation); 6768 } else { 6769 port.link(relation); 6770 } 6771 6772 // Propagate. Get the derived list for the relation, 6773 // then use its container as the context in which to 6774 // find the port. NOTE: The relation can be null 6775 // (to insert an empty link in a multiport), so 6776 // we have two cases to consider. 6777 if (relation != null) { 6778 Iterator derivedObjects = relation.getDerivedList().iterator(); 6779 6780 while (derivedObjects.hasNext()) { 6781 ComponentRelation derivedRelation = (ComponentRelation) derivedObjects 6782 .next(); 6783 CompositeEntity derivedContext = (CompositeEntity) derivedRelation 6784 .getContainer(); 6785 ComponentPort derivedPort = _getPort(portName, derivedContext); 6786 6787 // NOTE: Duplicate the above logic exactly. 6788 if (insertAtSpec != null) { 6789 derivedPort.insertLink(insertAt, derivedRelation); 6790 } else if (insertInsideAtSpec != null) { 6791 derivedPort.insertInsideLink(insertInsideAt, 6792 derivedRelation); 6793 } else { 6794 derivedPort.link(derivedRelation); 6795 } 6796 } 6797 } else { 6798 Iterator derivedObjects = port.getDerivedList().iterator(); 6799 6800 while (derivedObjects.hasNext()) { 6801 ComponentPort derivedPort = (ComponentPort) derivedObjects 6802 .next(); 6803 6804 // NOTE: Duplicate the above logic exactly. 6805 if (insertAtSpec != null) { 6806 derivedPort.insertLink(insertAt, null); 6807 } else if (insertInsideAtSpec != null) { 6808 derivedPort.insertInsideLink(insertInsideAt, null); 6809 } else { 6810 // This one probably shouldn't occur. 6811 derivedPort.link(null); 6812 } 6813 } 6814 } 6815 6816 // Handle the undo aspect. 6817 if (_undoEnabled) { 6818 // NOTE: always unlink using an index 6819 // NOTE: do not use a relation name as that unlinks 6820 // all links to that relation from the given port 6821 if (relation == null) { 6822 // Generate a link in the undo only if the port is 6823 // not derived. If it is derived, then the link belongs to 6824 // the class definition and should not be undone in undo. 6825 if (port.getDerivedLevel() == Integer.MAX_VALUE) { 6826 // Handle null links insertion first. Either an 6827 // insertAt or an insertInsideAt must have been used. 6828 // NOTE: we need to check if the number of links 6829 // actually changed as a null link beyond the index of 6830 // the first real link has no effect 6831 if (insertAt != -1) { 6832 if (port.numLinks() != origNumOutsideLinks) { 6833 // Enclose in a group to prevent deferral on undo. 6834 _undoContext.appendUndoMoML("<group><unlink port=\"" 6835 + portName + "\" index=\"" + insertAtSpec 6836 + "\" /></group>\n"); 6837 } 6838 } else { 6839 if (port.numInsideLinks() != origNumInsideLinks) { 6840 // Enclose in a group to prevent deferral on undo. 6841 _undoContext.appendUndoMoML("<group><unlink port=\"" 6842 + portName + "\" insideIndex=\"" 6843 + insertInsideAtSpec + "\" /></group>\n"); 6844 } 6845 } 6846 } 6847 } else { 6848 // Generate a link in the undo only if the port or relation is 6849 // not derived. If they are both derived, then the link belongs to 6850 // the class definition and should not be undone in undo. 6851 if (port.getDerivedLevel() == Integer.MAX_VALUE 6852 || relation.getDerivedLevel() == Integer.MAX_VALUE) { 6853 // The relation name was given, see if the link was 6854 // added inside or outside 6855 6856 if (port.numInsideLinks() != origNumInsideLinks) { 6857 if (insertInsideAt == -1) { 6858 insertInsideAt = port.numInsideLinks() - 1; 6859 } 6860 // Enclose in a group to prevent deferral on undo. 6861 // Handle deleting links in reverse order so that if 6862 // we copy and paste the undo/redo works out 6863 _undoContext.appendClosingUndoMoML( 6864 "<group><unlink port=\"" + portName 6865 + "\" insideIndex=\"" + insertInsideAt 6866 + "\" /></group>" + "\n"); 6867 } else if (port.numLinks() != origNumOutsideLinks) { 6868 if (insertAt == -1) { 6869 insertAt = port.numLinks() - 1; 6870 } 6871 // Enclose in a group to prevent deferral on undo. 6872 // Handle deleting links in reverse order so that if 6873 // we copy and paste the undo/redo works out 6874 _undoContext 6875 .appendClosingUndoMoML("<group><unlink port=\"" 6876 + portName + "\" index=\"" + insertAt 6877 + "\" /></group>" + "\n"); 6878 } else { 6879 // No change so do not need to generate any undo MoML 6880 } 6881 } 6882 } 6883 } 6884 } 6885 6886 /** Process pending link and delete requests, if any. 6887 * @exception Exception If something goes wrong. 6888 */ 6889 private void _processPendingRequests() throws Exception { 6890 if (_linkRequests != null) { 6891 Iterator requests = _linkRequests.iterator(); 6892 6893 while (requests.hasNext()) { 6894 LinkRequest request = (LinkRequest) requests.next(); 6895 6896 // Be sure to use the handler if these fail so that 6897 // we continue to the next link requests. 6898 try { 6899 request.execute(); 6900 } catch (Exception ex) { 6901 if (_handler != null) { 6902 int reply = _handler.handleError(request.toString(), 6903 _current, ex); 6904 6905 if (reply == ErrorHandler.CONTINUE) { 6906 continue; 6907 } else if (reply == ErrorHandler.CANCEL) { 6908 // NOTE: Since we have to throw an XmlException for 6909 // the exception to be properly handled, we communicate 6910 // that it is a user cancellation with the special 6911 // string pattern "*** Canceled." in the message. 6912 throw new XmlException("*** Canceled.", 6913 _currentExternalEntity(), _getLineNumber(), 6914 _getColumnNumber()); 6915 } 6916 } else { 6917 // No handler. Throw the original exception. 6918 throw ex; 6919 } 6920 } 6921 } 6922 } 6923 6924 // Process delete requests that have accumulated in 6925 // this element. 6926 if (_deleteRequests != null) { 6927 Iterator requests = _deleteRequests.iterator(); 6928 6929 while (requests.hasNext()) { 6930 DeleteRequest request = (DeleteRequest) requests.next(); 6931 6932 // Be sure to use the handler if these fail so that 6933 // we continue to the next link requests. 6934 try { 6935 request.execute(); 6936 } catch (Exception ex) { 6937 if (_handler != null) { 6938 int reply = _handler.handleError(request.toString(), 6939 _current, ex); 6940 6941 if (reply == ErrorHandler.CONTINUE) { 6942 continue; 6943 } else if (reply == ErrorHandler.CANCEL) { 6944 // NOTE: Since we have to throw an XmlException for 6945 // the exception to be properly handled, we communicate 6946 // that it is a user cancellation with the special 6947 // string pattern "*** Canceled." in the message. 6948 throw new XmlException("*** Canceled.", 6949 _currentExternalEntity(), _getLineNumber(), 6950 _getColumnNumber()); 6951 } 6952 } else { 6953 // No handler. Throw the original exception. 6954 throw ex; 6955 } 6956 } 6957 } 6958 } 6959 } 6960 6961 /** Process an unlink request between two relations. 6962 * @param relation1Name The first relation name. 6963 * @param relation2Name The second relation name. 6964 * @exception XmlException If something goes wrong. 6965 * @exception IllegalActionException If the link is part of a class definition. 6966 */ 6967 private void _processUnlink(String relation1Name, String relation2Name) 6968 throws XmlException, IllegalActionException { 6969 // Check that required arguments are given 6970 if (relation1Name == null || relation2Name == null) { 6971 throw new XmlException("Element unlink requires two relations.", 6972 _currentExternalEntity(), _getLineNumber(), 6973 _getColumnNumber()); 6974 } 6975 6976 CompositeEntity context = (CompositeEntity) _current; 6977 6978 // Get relations. 6979 ComponentRelation relation1 = context.getRelation(relation1Name); 6980 _checkForNull(relation1, "No relation named \"" + relation1Name 6981 + "\" in " + context.getFullName()); 6982 6983 // Get relations. 6984 ComponentRelation relation2 = context.getRelation(relation2Name); 6985 _checkForNull(relation2, "No relation named \"" + relation2Name 6986 + "\" in " + context.getFullName()); 6987 6988 // Ensure that derived objects aren't changed. 6989 // We have to prohit adding links between class 6990 // elements because this operation cannot be undone, and 6991 // it will not be persistent. 6992 if (_isLinkInClass(context, relation1, relation2)) { 6993 throw new IllegalActionException(relation1, relation2, 6994 "Cannot unlink relations when both" 6995 + " are part of the class definition."); 6996 } 6997 6998 relation1.unlink(relation2); 6999 7000 // Propagate. Get the derived list for relation1, 7001 // then use its container as the context in which to 7002 // find relation2. 7003 Iterator derivedObjects = relation1.getDerivedList().iterator(); 7004 7005 while (derivedObjects.hasNext()) { 7006 ComponentRelation derivedRelation1 = (ComponentRelation) derivedObjects 7007 .next(); 7008 CompositeEntity derivedContext = (CompositeEntity) derivedRelation1 7009 .getContainer(); 7010 ComponentRelation derivedRelation2 = derivedContext 7011 .getRelation(relation2Name); 7012 derivedRelation1.unlink(derivedRelation2); 7013 } 7014 7015 // Handle the undo aspect. 7016 if (_undoEnabled) { 7017 // Generate a link in the undo only if one or the other relation is 7018 // not derived. If they are both derived, then the link belongs to 7019 // the class definition and should not be recreated in undo. 7020 if (relation1.getDerivedLevel() == Integer.MAX_VALUE 7021 || relation2.getDerivedLevel() == Integer.MAX_VALUE) { 7022 _undoContext.appendUndoMoML("<link relation1=\"" + relation1Name 7023 + "\" relation2=\"" + relation2Name + "\" />\n"); 7024 } 7025 } 7026 } 7027 7028 /** Process an unlink request between a port and relation. 7029 * @param portName The port name. 7030 * @param relationName The relation name. 7031 * @param indexSpec The index of the channel. 7032 * @param insideIndexSpec The index of the inside channel. 7033 * @exception XmlException If something goes wrong. 7034 * @exception IllegalActionException If the link is part of a class definition. 7035 */ 7036 private void _processUnlink(String portName, String relationName, 7037 String indexSpec, String insideIndexSpec) 7038 throws XmlException, IllegalActionException { 7039 _checkClass(_current, CompositeEntity.class, 7040 "Element \"unlink\" found inside an element that " 7041 + "is not a CompositeEntity. It is: " + _current); 7042 7043 int countArgs = 0; 7044 7045 // Check that one of the required arguments is given 7046 if (indexSpec != null) { 7047 countArgs++; 7048 } 7049 7050 if (insideIndexSpec != null) { 7051 countArgs++; 7052 } 7053 7054 if (relationName != null) { 7055 countArgs++; 7056 } 7057 7058 if (countArgs != 1) { 7059 throw new XmlException( 7060 "Element unlink requires exactly one of " 7061 + "an index, an insideIndex, or a relation.", 7062 _currentExternalEntity(), _getLineNumber(), 7063 _getColumnNumber()); 7064 } 7065 7066 CompositeEntity context = (CompositeEntity) _current; 7067 7068 // Parse port 7069 ComponentPort port = _getPort(portName, context); 7070 7071 // Get relation if given 7072 if (relationName != null) { 7073 Relation tmpRelation = context.getRelation(relationName); 7074 _checkForNull(tmpRelation, "No relation named \"" + relationName 7075 + "\" in " + context.getFullName()); 7076 7077 ComponentRelation relation = (ComponentRelation) tmpRelation; 7078 7079 // Ensure that derived objects aren't changed. 7080 if (_isLinkInClass(context, port, relation)) { 7081 throw new IllegalActionException(port, 7082 "Cannot unlink a port from a relation when both" 7083 + " are part of the class definition."); 7084 } 7085 7086 // Handle the undoable aspect. 7087 // Generate a link in the undo only if one or the other relation is 7088 // not derived. If they are both derived, then the link belongs to 7089 // the class definition and should not be recreated in undo. 7090 if (_undoEnabled && (port.getDerivedLevel() == Integer.MAX_VALUE 7091 || relation.getDerivedLevel() == Integer.MAX_VALUE)) { 7092 // Get the relation at the given index 7093 List linkedRelations = port.linkedRelationList(); 7094 int index = linkedRelations.indexOf(tmpRelation); 7095 7096 if (index != -1) { 7097 // Linked on the outside... 7098 _undoContext.appendUndoMoML("<link port=\"" + portName 7099 + "\" insertAt=\"" + index + "\" relation=\"" 7100 + relationName + "\" />\n"); 7101 } else { 7102 List insideLinkedRelations = port.insideRelationList(); 7103 index = insideLinkedRelations.indexOf(tmpRelation); 7104 7105 // Linked on the inside. 7106 _undoContext.appendUndoMoML("<link port=\"" + portName 7107 + "\" insertInsideAt=\"" + index + "\" relation=\"" 7108 + relationName + "\" />\n"); 7109 } 7110 } 7111 7112 // Propagate. Get the derived list for the relation, 7113 // then use its container as the context in which to 7114 // find the port. 7115 Iterator derivedObjects = relation.getDerivedList().iterator(); 7116 7117 while (derivedObjects.hasNext()) { 7118 ComponentRelation derivedRelation = (ComponentRelation) derivedObjects 7119 .next(); 7120 CompositeEntity derivedContext = (CompositeEntity) derivedRelation 7121 .getContainer(); 7122 ComponentPort derivedPort = _getPort(portName, derivedContext); 7123 derivedPort.unlink(derivedRelation); 7124 } 7125 7126 port.unlink(relation); 7127 } else if (indexSpec != null) { 7128 // index is given. 7129 int index = Integer.parseInt(indexSpec); 7130 7131 // Ensure that derived objects aren't changed. 7132 // Unfortunately, getting the relation is fairly 7133 // expensive. 7134 List relationList = port.linkedRelationList(); 7135 if (relationList.size() <= index) { 7136 throw new IllegalActionException(port, "Cannot unlink index " 7137 + indexSpec + ", because there is no such link."); 7138 } 7139 Relation relation = (Relation) relationList.get(index); 7140 7141 if (_isLinkInClass(context, port, relation)) { 7142 throw new IllegalActionException(port, 7143 "Cannot unlink a port from a relation when both" 7144 + " are part of the class definition."); 7145 } 7146 7147 // Handle the undoable aspect before doing the unlinking. 7148 // Generate a link in the undo only if one or the other relation is 7149 // not derived. If they are both derived, then the link belongs to 7150 // the class definition and should not be recreated in undo. 7151 if (_undoEnabled) { 7152 // Get the relation at the given index. 7153 List linkedRelations = port.linkedRelationList(); 7154 Relation r = (Relation) linkedRelations.get(index); 7155 // Generate undo moml only if either the port is 7156 // not derived or there is a relation and it is not derived. 7157 if (port.getDerivedLevel() == Integer.MAX_VALUE || r != null 7158 && r.getDerivedLevel() == Integer.MAX_VALUE) { 7159 // FIXME: need to worry about vertex? 7160 _undoContext.appendUndoMoML("<link port=\"" + portName 7161 + "\" insertAt=\"" + indexSpec + "\" "); 7162 7163 // Only need to specify the relation if there was 7164 // a relation at that index. Otherwise a null 7165 // link is inserted 7166 if (r != null) { 7167 _undoContext.appendUndoMoML( 7168 "relation=\"" + r.getName(context) + "\" "); 7169 } 7170 _undoContext.appendUndoMoML(" />\n"); 7171 } 7172 } 7173 7174 // Propagate. 7175 Iterator derivedObjects = port.getDerivedList().iterator(); 7176 7177 while (derivedObjects.hasNext()) { 7178 ComponentPort derivedPort = (ComponentPort) derivedObjects 7179 .next(); 7180 derivedPort.unlink(index); 7181 } 7182 7183 port.unlink(index); 7184 } else { 7185 // insideIndex is given. 7186 int index = Integer.parseInt(insideIndexSpec); 7187 7188 // Ensure that derived objects aren't changed. 7189 // Unfortunately, getting the relation is fairly 7190 // expensive. 7191 List relationList = port.insideRelationList(); 7192 Relation relation = (Relation) relationList.get(index); 7193 7194 if (_isLinkInClass(context, port, relation)) { 7195 throw new IllegalActionException(port, 7196 "Cannot unlink a port from a relation when both" 7197 + " are part of the class definition."); 7198 } 7199 7200 // Handle the undoable aspect before doing the unlinking 7201 if (_undoEnabled) { 7202 // Get the relation at the given index 7203 List linkedRelations = port.insideRelationList(); 7204 Relation r = (Relation) linkedRelations.get(index); 7205 // Generate undo moml only if either the port is 7206 // not derived or there is a relation and it is not derived. 7207 if (port.getDerivedLevel() == Integer.MAX_VALUE || r != null 7208 && r.getDerivedLevel() == Integer.MAX_VALUE) { 7209 // FIXME: need to worry about vertex? 7210 _undoContext.appendUndoMoML("<link port=\"" + portName 7211 + "\" insertInsideAt=\"" + index + "\" "); 7212 7213 // Only need to specify the relation if there was 7214 // a relation at that index. Otherwise a null 7215 // link is inserted 7216 if (r != null) { 7217 _undoContext.appendUndoMoML( 7218 "relation=\"" + r.getName(context) + "\" "); 7219 } 7220 _undoContext.appendUndoMoML(" />\n"); 7221 } 7222 } 7223 7224 // Propagate. 7225 Iterator derivedObjects = port.getDerivedList().iterator(); 7226 7227 while (derivedObjects.hasNext()) { 7228 ComponentPort derivedPort = (ComponentPort) derivedObjects 7229 .next(); 7230 derivedPort.unlinkInside(index); 7231 } 7232 7233 port.unlinkInside(index); 7234 } 7235 } 7236 7237 /** Push the current context. 7238 */ 7239 private void _pushContext() { 7240 _containers.push(_current); 7241 _namespaces.push(_namespace); 7242 _namespace = _DEFAULT_NAMESPACE; 7243 _namespaceTranslations.push(_namespaceTranslationTable); 7244 _namespaceTranslationTable = new HashMap(); 7245 _namespacesPushed = true; 7246 } 7247 7248 /** Reset the undo information to give a fresh setup for the next 7249 * incremental change. NOTE: this resets all the undo information except 7250 * for the UndoStackAttribute which is associated with the model. 7251 */ 7252 private void _resetUndo() { 7253 _undoContext = null; 7254 _undoContexts = new Stack(); 7255 _undoEnabled = false; 7256 _undoForOverrides.clear(); 7257 } 7258 7259 /** Given a name that is either absolute (with a leading period) 7260 * or relative to _current, find an attribute with that name. 7261 * The attribute is required to 7262 * be contained (deeply) by the current environment, or an XmlException 7263 * will be thrown. 7264 * @param name The name of the attribute, relative or absolute. 7265 * @return The attribute. 7266 * @exception XmlException If the attribute is not found. 7267 */ 7268 private Attribute _searchForAttribute(String name) throws XmlException { 7269 Attribute result = null; 7270 7271 // If the name is absolute, strip the prefix. 7272 String currentName = "(no top level)"; 7273 7274 if (_current != null) { 7275 currentName = _current.getFullName(); 7276 } 7277 7278 if (_current != null && name.startsWith(currentName)) { 7279 int prefix = currentName.length(); 7280 7281 if (name.length() > prefix) { 7282 name = name.substring(prefix + 1); 7283 } 7284 } 7285 7286 if (_current != null) { 7287 // Now we are assured that name is relative. 7288 result = _current.getAttribute(name); 7289 } else { 7290 // Findbugs suggests checking for null 7291 throw new XmlException( 7292 "The current object in the hierarchy " 7293 + "is null? Could not find property: " + name 7294 + " in " + currentName, 7295 _currentExternalEntity(), _getLineNumber(), 7296 _getColumnNumber()); 7297 } 7298 7299 if (result == null) { 7300 throw new XmlException( 7301 "No such property: " + name + " in " + currentName, 7302 _currentExternalEntity(), _getLineNumber(), 7303 _getColumnNumber()); 7304 } 7305 7306 return result; 7307 } 7308 7309 /** Search for a class definition in the current context or 7310 * anywhere above it in the hierarchy. 7311 * If a instance of a MoML class with a matching name and 7312 * source is found, then return it. Otherwise, return null. 7313 * @param name The name of the class. 7314 * @param source The source for the class. 7315 * @return A class, if it exists. 7316 * @exception Exception If a source is specified and it cannot 7317 * be opened. 7318 */ 7319 private ComponentEntity _searchForClassInContext(String name, String source) 7320 throws Exception { 7321 ComponentEntity candidate = _searchForEntity(name, _current); 7322 7323 // Search upwards in the hierarchy if necessary. 7324 NamedObj context = _current; 7325 7326 // Make sure we get a real candidate, which is a 7327 // class definition. The second term in the if will 7328 // cause the search to continue up the hierarchy. 7329 // NOTE: There is still an oddness, in that 7330 // the class scoping results in a subtle (and 7331 // maybe incomprehensible) identification of 7332 // the base class, particularly when pasting 7333 // an instance or subclass into a new context. 7334 while ((candidate == null || !candidate.isClassDefinition()) 7335 && context != null) { 7336 context = context.getContainer(); 7337 7338 if (context instanceof CompositeEntity) { 7339 candidate = ((CompositeEntity) context).getEntity(name); 7340 } 7341 } 7342 7343 if (candidate != null) { 7344 // Check that its source matches. 7345 String candidateSource = candidate.getSource(); 7346 7347 if (source == null && candidateSource == null) { 7348 return candidate; 7349 } else if (source != null && candidateSource != null) { 7350 // Have to convert to a URL to check whether the 7351 // same file is being specified. 7352 URI sourceURI = fileNameToURL(source, _base).toURI(); 7353 URI candidateSourceURI = fileNameToURL(candidateSource, _base) 7354 .toURI(); 7355 7356 // FIXME: URL.equals() is very expensive? See: 7357 // http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html 7358 if (sourceURI.equals(candidateSourceURI)) { 7359 return candidate; 7360 } 7361 } 7362 } 7363 7364 return null; 7365 } 7366 7367 /** Given a name that is either absolute (with a leading period) 7368 * or relative to the specified context, find a component entity 7369 * with that name. Return null if it is not found. 7370 * @param name The name of the entity. 7371 * @param context The context in which to search. 7372 * @return An entity with the specified name, or null if none is found. 7373 * @exception XmlException If the name refers to an entity in an 7374 * inappropriate context or if the context is not an instance 7375 * of CompositeEntity. 7376 * @exception IllegalActionException If the name is ambiguous in that 7377 * more than one entity in workspace matches. This will only occur 7378 * if the name is absolute and is not inside the current model. 7379 */ 7380 private ComponentEntity _searchForEntity(String name, NamedObj context) 7381 throws XmlException, IllegalActionException { 7382 // If the name is absolute, we first have to find a 7383 // name from the imports that matches. 7384 if (name.startsWith(".")) { 7385 // Name is absolute. 7386 String topLevelName; 7387 int nextPeriod = name.indexOf(".", 1); 7388 7389 if (nextPeriod < 1) { 7390 topLevelName = name.substring(1); 7391 } else { 7392 topLevelName = name.substring(1, nextPeriod); 7393 } 7394 7395 // Search the current top level, if the name matches. 7396 if (_toplevel != null && _toplevel instanceof ComponentEntity 7397 && topLevelName.equals(_toplevel.getName())) { 7398 if (nextPeriod < 1) { 7399 /* NOTE: This is too restrictive. 7400 * With an absolute name, there is no 7401 * reason to disallow setting the context. 7402 * This is useful in particular when deleting 7403 * ports to make sure undo works. The undo 7404 * code has to execute in a context that may 7405 * be higher than that in which the port 7406 * was deleted in order for the connections 7407 * to be re-established. 7408 */ 7409 /* 7410 if (context != null 7411 && context != _toplevel) { 7412 throw new XmlException( 7413 "Reference to an existing entity: " 7414 + _toplevel.getFullName() 7415 + " in an inappropriate context: " 7416 + context.getFullName(), 7417 _currentExternalEntity(), 7418 _getLineNumber(), 7419 _getColumnNumber()); 7420 } 7421 */ 7422 return (ComponentEntity) _toplevel; 7423 } else { 7424 if (name.length() > nextPeriod + 1) { 7425 ComponentEntity result = ((CompositeEntity) _toplevel) 7426 .getEntity(name.substring(nextPeriod + 1)); 7427 7428 if (result != null) { 7429 /* NOTE: This is too restrictive. 7430 * With an absolute name, there is no 7431 * reason to disallow setting the context. 7432 * This is useful in particular when deleting 7433 * ports to make sure undo works. The undo 7434 * code has to execute in a context that may 7435 * be higher than that in which the port 7436 * was deleted in order for the connections 7437 * to be re-established. 7438 */ 7439 /* 7440 if (context != null 7441 && !context.deepContains(result)) { 7442 throw new XmlException( 7443 "Reference to an existing entity: " 7444 + result.getFullName() 7445 + " in an inappropriate context: " 7446 + context.getFullName(), 7447 _currentExternalEntity(), 7448 _getLineNumber(), 7449 _getColumnNumber()); 7450 } 7451 */ 7452 return result; 7453 } 7454 } 7455 } 7456 } else { 7457 // Name of the top level doesn't match. 7458 // NOTE: It is tempting to try to find the object within the workspace. 7459 // However, the names of objects in the workspace are not unique, 7460 // so if there is more than one with a matching name, which should be 7461 // returned? Hence, we don't permit MoML to reach into another toplevel 7462 // through the absolute naming mechanism. Just return null. 7463 } 7464 7465 return null; 7466 } else { 7467 // Name is relative. 7468 if (context instanceof CompositeEntity) { 7469 ComponentEntity result = ((CompositeEntity) context) 7470 .getEntity(name); 7471 return result; 7472 } 7473 7474 if (context == null) { 7475 // The name might be a top-level name, but without 7476 // the leading period. 7477 return _searchForEntity("." + name, /* context*/null); 7478 } 7479 7480 return null; 7481 } 7482 } 7483 7484 /** Given a name that is either absolute (with a leading period) 7485 * or relative to _current, find a port with that name. 7486 * The port is required to 7487 * be contained (deeply) by the current environment, or an XmlException 7488 * will be thrown. 7489 * @param name The name of the port. 7490 * @return The port. 7491 * @exception XmlException If the port is not found. 7492 */ 7493 private Port _searchForPort(String name) throws XmlException { 7494 Port result = null; 7495 7496 // If the name is absolute, strip the prefix. 7497 String topLevelName = "(no top level)"; 7498 7499 if (_toplevel != null) { 7500 topLevelName = _toplevel.getFullName(); 7501 } 7502 7503 if (_toplevel != null && name.startsWith(topLevelName)) { 7504 int prefix = topLevelName.length(); 7505 7506 if (name.length() > prefix) { 7507 name = name.substring(1, name.length()); 7508 } 7509 } 7510 7511 // Now we are assured that name is relative. 7512 if (_current instanceof Entity) { 7513 result = ((Entity) _current).getPort(name); 7514 } 7515 7516 if (result == null) { 7517 throw new XmlException( 7518 "No such port: " + name + " in " + topLevelName, 7519 _currentExternalEntity(), _getLineNumber(), 7520 _getColumnNumber()); 7521 } 7522 7523 return result; 7524 } 7525 7526 /** Given a name that is either absolute (with a leading period) 7527 * or relative to _current, find a relation with that name. 7528 * The relation is required to 7529 * be contained (deeply) by the current environment, or an XmlException 7530 * will be thrown. 7531 * @param name The name of the relation. 7532 * @return The relation. 7533 * @exception XmlException If the relation is not found. 7534 */ 7535 private ComponentRelation _searchForRelation(String name) 7536 throws XmlException { 7537 ComponentRelation result = null; 7538 7539 // If the name is absolute, strip the prefix. 7540 String topLevelName = "(no top level)"; 7541 7542 if (_toplevel != null) { 7543 topLevelName = _toplevel.getFullName(); 7544 } 7545 7546 if (_toplevel != null && name.startsWith(topLevelName)) { 7547 int prefix = topLevelName.length(); 7548 7549 if (name.length() > prefix) { 7550 name = name.substring(1, name.length()); 7551 } 7552 } 7553 7554 // Now we are assured that name is relative. 7555 if (_current instanceof CompositeEntity) { 7556 result = ((CompositeEntity) _current).getRelation(name); 7557 } 7558 7559 if (result == null) { 7560 throw new XmlException( 7561 "No such relation: " + name + " in " + topLevelName, 7562 _currentExternalEntity(), _getLineNumber(), 7563 _getColumnNumber()); 7564 } 7565 7566 return result; 7567 } 7568 7569 /** Store the value of the file being read. We do this for performance 7570 * reasons because URL.toString() is expensive. For large models, 7571 * caching results in a 2x speed-up in opening time under Mac OS X. 7572 */ 7573 private void _setXmlFile(URL xmlFile) { 7574 _xmlFile = xmlFile; 7575 _xmlFileName = _xmlFile != null ? _xmlFile.toString() : null; 7576 } 7577 7578 /** Add a class name to the list of missing classes. 7579 * @param className the class name to be added. 7580 */ 7581 private void _updateMissingClasses(String className) { 7582 if (_missingClasses == null) { 7583 _missingClasses = new HashSet<String>(); 7584 } 7585 _missingClasses.add(className); 7586 } 7587 7588 /////////////////////////////////////////////////////////////////// 7589 //// private members //// 7590 // Remote xmlFiles that the user approved of when the security concern 7591 // dialog popped up. 7592 private static Set _approvedRemoteXmlFiles = new HashSet(); 7593 7594 // Attributes associated with an entity. 7595 private Map _attributes = new HashMap(); 7596 7597 // The list of attribute names, in the order they were parsed. 7598 private List _attributeNameList = new ArrayList(0); 7599 7600 // The namespace for automatic naming. 7601 private static String _AUTO_NAMESPACE = "auto"; 7602 7603 // Base for relative URLs. 7604 private URL _base; 7605 7606 // The class loader that will be used to instantiate objects. 7607 private ClassLoader _classLoader = getClass().getClassLoader(); 7608 7609 // The default class loading strategy that can be adjusted for specific runtime requirements, 7610 // e.g. it can be modified for OSGi-based runtimes. 7611 private static ClassLoadingStrategy _defaultClassLoadingStrategy = new SimpleClassLoadingStrategy( 7612 MoMLParser.class.getClassLoader()); 7613 7614 // The optional default version specification to be used for loading java & actor-oriented classes, 7615 // in cases where this concept is supported by the class loading strategy. 7616 private VersionSpecification _defaultVersionSpecification; 7617 7618 // Count of configure tags so that they can nest. 7619 private int _configureNesting = 0; 7620 7621 // The source attribute specified by the configure element. 7622 private String _configureSource; 7623 7624 // The stack of objects that contain the current one. 7625 private Stack _containers = new Stack(); 7626 7627 // The current object in the hierarchy. 7628 private NamedObj _current; 7629 7630 // The current character data for the current element. 7631 private StringBuffer _currentCharData; 7632 7633 // The name of the currently active doc element. 7634 private String _currentDocName; 7635 7636 // The default namespace. 7637 private static String _DEFAULT_NAMESPACE = ""; 7638 7639 // Indentification of what is being deleted for the _delete() method. 7640 private static int _DELETE_ENTITY = 0; 7641 7642 private static int _DELETE_PORT = 1; 7643 7644 private static int _DELETE_PROPERTY = 2; 7645 7646 private static int _DELETE_RELATION = 3; 7647 7648 // List of delete requests. 7649 private List _deleteRequests; 7650 7651 // Stack of lists of delete requests. 7652 private Stack _deleteRequestStack = new Stack(); 7653 7654 // Count of doc tags so that they can nest. 7655 private int _docNesting = 0; 7656 7657 // The external entities being parsed. 7658 private Stack _externalEntities = new Stack(); 7659 7660 // ErrorHandler that handles parse errors. 7661 private static ErrorHandler _handler = null; 7662 7663 // List of MoMLFilters to apply if non-null. MoMLFilters translate MoML 7664 // elements. These filters will filter all MoML for all instances 7665 // of this class. 7666 private static List _filterList = null; 7667 7668 /** MoMLParser to use with the MoMLFilters. We share one 7669 * MoMLParser between the filters, _filterMoMLParser is passed as 7670 * an argument. Each filter should call setContext(container) on 7671 * the passed-in filter. Since setContext() calls reset(), we 7672 * don't want to pass in the main MoMLParser. We share one 7673 * MoMLParser so as to avoid the expense of constructing one each 7674 * time we read an attribute. 7675 */ 7676 private static MoMLParser _filterMoMLParser = null; 7677 7678 /** Count of nested group elements. */ 7679 private int _groupCount = 0; 7680 7681 /** IconLoader used to load icons. */ 7682 private static IconLoader _iconLoader; 7683 7684 // List of weak references to 7685 // top-level entities imported via import element, 7686 // of MoML classes loaded in order to instantiate them, 7687 // and of models that have been parsed. 7688 private static Map _imports; 7689 7690 // List of link or unlink requests. 7691 private List _linkRequests; 7692 7693 // Stack of lists of link or unlink requests. 7694 private Stack _linkRequestStack = new Stack(); 7695 7696 // Set to true if a MoMLFilter modified the model. 7697 private static boolean _modified = false; 7698 7699 // The current namespace. 7700 private String _namespace = _DEFAULT_NAMESPACE; 7701 7702 // The stack of name spaces. 7703 private Stack _namespaces = new Stack(); 7704 7705 // Indicator that namespaces have been pushed on the stack. 7706 private boolean _namespacesPushed = false; 7707 7708 // The current translation table for names. 7709 private Map _namespaceTranslationTable = null; 7710 7711 // The stack of maps for name translations. 7712 private Stack _namespaceTranslations = new Stack(); 7713 7714 // The original context set by setContext(). 7715 private NamedObj _originalContext = null; 7716 7717 /** Positive if we are within a group named "doNotOverwriteOverrides". 7718 * The value indicates the group count at which this was set. 7719 */ 7720 private int _overwriteOverrides = 0; 7721 7722 // A set of settable parameters specified in property tags. 7723 private Set<Settable> _paramsToParse = new HashSet<Settable>(); 7724 7725 /** A list of scope extenders encountered while parsing. */ 7726 private List<ScopeExtender> _scopeExtenders; 7727 7728 /** The XmlParser. */ 7729 private XmlParser _xmlParser; 7730 7731 // Status of the deferral of the top-level. 7732 private boolean _previousDeferStatus = false; 7733 7734 // The stack of integers that represent the state of <if> elements. The top 7735 // integer always represent the state of the last <if> element, or the 7736 // initial state if no <if> element has been reached. If it is > 1, then the 7737 // elements within the <if> block should be ignored. If it is 1, then the 7738 // next input must be </if>, which closes the <if> block. If it is 0, then 7739 // all the elements are processed normally. 7740 private Stack _ifElementStack; 7741 7742 // List of missing actors. 7743 private Set<String> _missingClasses; 7744 7745 // True if we have printed the message about backtrack.xml being skipped. 7746 private static boolean _printedMacOSSkippingMessage = false; 7747 7748 // If greater than zero, skipping an element. 7749 private int _skipElement = 0; 7750 7751 // If attribute() found an element to skip, then 7752 // the first time we startElement(), we do not 7753 // want to increment _skipElement again in 7754 // startElement() because we already did it in 7755 // attribute(). 7756 private boolean _skipElementIsNew = false; 7757 7758 // If skipping an element, then this is the name of the element. 7759 private String _skipElementName; 7760 7761 // True if we are skipping a rendition body. Rendition bodies 7762 // are skipped if the rendition class was not found. 7763 private boolean _skipRendition = false; 7764 7765 // Top-level entity. 7766 private NamedObj _toplevel = null; 7767 7768 // List of top-level objects created by a parse operation. 7769 // If this is list is non-null when parse() is called, it will 7770 // be populated with a list of instance of NamedObj that are 7771 // created at the top level of the parse. Note that these 7772 // may not be top-level objects. 7773 private List _topObjectsCreated = null; 7774 7775 // Holds information needed to generate undo MoML at this level 7776 private Stack _undoContexts = new Stack(); 7777 7778 // The current undo context. This contains information about the 7779 // the current undo environment 7780 private UndoContext _undoContext = null; 7781 7782 // Set this to true to get debugging information for undo. 7783 private static boolean _undoDebug = false; 7784 7785 // Flag indicating if the MoML currently being parsed should be 7786 // undoable. Primarily for incremental parsing. 7787 private boolean _undoEnabled = false; 7788 7789 // Holds additional undo commands that have to execute in a 7790 // different context. 7791 private List<UndoAction> _undoForOverrides = new LinkedList<UndoAction>(); 7792 7793 // List of unrecognized elements. 7794 private List _unrecognized; 7795 7796 // The workspace for this model. 7797 private Workspace _workspace; 7798 7799 /** The XML file being read, if any. Do not set _xmlFile directly, 7800 * instead call _setXmlFile(). 7801 */ 7802 private URL _xmlFile = null; 7803 7804 /** The name of the XMLFile, which we cache for performance reasons. */ 7805 private String _xmlFileName = null; 7806 7807 /////////////////////////////////////////////////////////////////// 7808 //// inner classes //// 7809 // Class to record a deletion request. 7810 // The first argument to the constructor should be one of 7811 // _DELETE_ENTITY, _DELETE_PORT, _DELETE_PROPERTY, or _DELETE_RELATION. 7812 // The second should be the name of the object to delete. 7813 // The third (optional) argument should be the name of the context, 7814 // or null to use the current context (only valid for _DELETE_PORT). 7815 private class DeleteRequest { 7816 public DeleteRequest(int type, String name, String context) { 7817 _type = type; 7818 _name = name; 7819 _context = context; 7820 } 7821 7822 public NamedObj execute() throws Exception { 7823 if (_type == _DELETE_ENTITY) { 7824 return _deleteEntity(_name); 7825 } else if (_type == _DELETE_PORT) { 7826 return _deletePort(_name, _context); 7827 } else if (_type == _DELETE_PROPERTY) { 7828 return _deleteProperty(_name); 7829 } else { 7830 return _deleteRelation(_name); 7831 } 7832 } 7833 7834 private int _type; 7835 7836 private String _name; 7837 7838 private String _context; 7839 } 7840 7841 // Class that records a link request. 7842 private class LinkRequest { 7843 // This constructor is used to link two relations into 7844 // the same relation group. 7845 public LinkRequest(String relation1Name, String relation2Name) { 7846 _relationName = relation1Name; 7847 _relation2Name = relation2Name; 7848 } 7849 7850 // This constructor is used to link a port and a relation. 7851 public LinkRequest(String portName, String relationName, 7852 String insertAtSpec, String insertInsideAtSpec) { 7853 _portName = portName; 7854 _relationName = relationName; 7855 _indexSpec = insertAtSpec; 7856 _insideIndexSpec = insertInsideAtSpec; 7857 } 7858 7859 public void execute() throws IllegalActionException, XmlException { 7860 if (_portName != null) { 7861 _processLink(_portName, _relationName, _indexSpec, 7862 _insideIndexSpec); 7863 } else { 7864 _processLink(_relationName, _relation2Name); 7865 } 7866 } 7867 7868 @Override 7869 public String toString() { 7870 if (_portName != null) { 7871 return "link " + _portName + " to " + _relationName; 7872 } else { 7873 return "link " + _relationName + " to " + _relation2Name; 7874 } 7875 } 7876 7877 protected String _portName; 7878 7879 protected String _relationName; 7880 7881 protected String _relation2Name; 7882 7883 protected String _indexSpec; 7884 7885 protected String _insideIndexSpec; 7886 } 7887 7888 // Class that records a link request. 7889 private class UnlinkRequest extends LinkRequest { 7890 public UnlinkRequest(String portName, String relationName, 7891 String indexSpec, String insideIndexSpec) { 7892 super(portName, relationName, indexSpec, insideIndexSpec); 7893 } 7894 7895 // This constructor is used to link two relations into 7896 // the same relation group. 7897 public UnlinkRequest(String relation1Name, String relation2Name) { 7898 super(relation1Name, relation2Name); 7899 } 7900 7901 @Override 7902 public void execute() throws IllegalActionException, XmlException { 7903 if (_portName != null) { 7904 _processUnlink(_portName, _relationName, _indexSpec, 7905 _insideIndexSpec); 7906 } else { 7907 _processUnlink(_relationName, _relation2Name); 7908 } 7909 } 7910 7911 @Override 7912 public String toString() { 7913 return "unlink " + _portName + " from " + _relationName; 7914 } 7915 } 7916 7917 // Under Mac OS X, skip backtrack.xml. See 7918 // https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/Mac2008 and 7919 // follow the 'Problems with Eclipse and Ptolemy on the Mac' link. 7920 static { 7921 if (System.getProperty("os.name").equals("Mac OS X")) { 7922 inputFileNamesToSkip = new LinkedList<String>(); 7923 inputFileNamesToSkip.add("backtrack.xml"); 7924 } 7925 } 7926}