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 "&lt;?xml ... &gt;" 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 &lt;group&gt;
155 ... sequence of MoML elements ...
156 &lt;/group&gt;
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     *  &AElig;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     *  &AElig;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     *  &AElig;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 &AElig;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 &amp; 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}