001/* Base class for Ptolemy configurations.
002
003 Copyright (c) 2000-2018 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 */
027package ptolemy.actor.gui;
028
029import java.io.File;
030import java.lang.reflect.Constructor;
031import java.lang.reflect.Field;
032import java.lang.reflect.Modifier;
033import java.net.URI;
034import java.net.URL;
035import java.util.HashSet;
036import java.util.Iterator;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.Set;
040
041import ptolemy.actor.ApplicationConfigurer;
042import ptolemy.actor.InstanceOpener;
043import ptolemy.actor.PubSubPort;
044import ptolemy.actor.TypedAtomicActor;
045import ptolemy.data.ArrayToken;
046import ptolemy.data.BooleanToken;
047import ptolemy.data.StringToken;
048import ptolemy.data.Token;
049import ptolemy.data.expr.Parameter;
050import ptolemy.data.expr.StringParameter;
051import ptolemy.data.type.BaseType;
052import ptolemy.graph.Inequality;
053import ptolemy.graph.InequalityTerm;
054import ptolemy.kernel.ComponentEntity;
055import ptolemy.kernel.CompositeEntity;
056import ptolemy.kernel.Entity;
057import ptolemy.kernel.InstantiableNamedObj;
058import ptolemy.kernel.Port;
059import ptolemy.kernel.attributes.URIAttribute;
060import ptolemy.kernel.util.Attribute;
061import ptolemy.kernel.util.IllegalActionException;
062import ptolemy.kernel.util.InternalErrorException;
063import ptolemy.kernel.util.NameDuplicationException;
064import ptolemy.kernel.util.NamedObj;
065import ptolemy.kernel.util.Settable;
066import ptolemy.kernel.util.Workspace;
067import ptolemy.moml.MoMLFilter;
068import ptolemy.moml.MoMLParser;
069import ptolemy.moml.filter.RemoveClasses;
070import ptolemy.moml.filter.RemoveGraphicalClasses;
071import ptolemy.util.ClassUtilities;
072import ptolemy.util.MessageHandler;
073import ptolemy.util.StringUtilities;
074
075///////////////////////////////////////////////////////////////////
076//// Configuration
077
078/**
079 The configuration of an application that uses Ptolemy II classes.
080 An instance of this class is in charge of the user interface,
081 and coordinates multiple views of multiple models. One of its
082 functions, for example, is to manage the opening of new models,
083 ensuring that an appropriate view is used. It also makes sure that
084 if a model is opened that is already open, then existing views are
085 shown rather than creating new views.
086 <p>
087 The applications <i>vergil</i> and <i>moml</i> (at least) use
088 configurations defined in MoML files, typically located in
089 ptII/ptolemy/configs. The <i>moml</i> application takes as
090 command line arguments a list of MoML files, the first of which
091 is expected to define an instance of Configuration and its contents.
092 That configuration is then used to open subsequent MoML files on the
093 command line, and to manage the user interface.
094 <p>
095 Rather than performing all these functions itself, this class
096 is a container for a model directory, effigy factories, and tableau
097 factories that actually realize these functions. An application
098 is configured by populating an instance of this class with
099 a suitable set of these other classes. A minimal configuration
100 defined in MoML is shown below:
101 <pre>
102 &lt;?xml version="1.0" standalone="no"?&gt;
103 &lt;!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN"
104 "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"&gt;
105 &lt;entity name="configuration" class="ptolemy.actor.gui.Configuration"&gt;
106 &lt;doc&gt;Configuration to run but not edit Ptolemy II models&lt;/doc&gt;
107 &lt;entity name="directory" class="ptolemy.actor.gui.ModelDirectory"/&gt;
108 &lt;entity name="effigyFactory" class="ptolemy.actor.gui.PtolemyEffigy$Factory"/&gt;
109 &lt;property name="tableauFactory" class="ptolemy.actor.gui.RunTableau$Factory"/&gt;
110 &lt;/entity&gt;
111 </pre>
112 <p>
113 It must contain, at a minimum, an instance of ModelDirectory, named
114 "directory", and an instance of EffigyFactory, named "effigyFactory".
115 The openModel() method delegates to the effigy factory the opening of a model.
116 It may also contain an instance of TextEditorTableauFactory, named "tableauFactory".
117 A tableau is a visual representation of the model in a top-level window.
118 The above minimal configuration can be used to run Ptolemy II models
119 by opening a run panel only.
120 <p>
121 When the directory becomes empty (all models have been closed),
122 it removes itself from the configuration. When this happens, the
123 configuration calls System.exit() to exit the application.
124
125 <p>To access the configuration from a random place, if you have a
126 NamedObj <code>foo</code>, then you can call:
127 <pre>
128 Effigy effigy = Configuration.findEffigy(foo.toplevel());
129 Configuration configuration = effigy.toplevel();
130 </pre>
131
132
133 @author Steve Neuendorffer and Edward A. Lee
134 @version $Id$
135 @since Ptolemy II 1.0
136 @Pt.ProposedRating Green (eal)
137 @Pt.AcceptedRating Yellow (celaine)
138 @see EffigyFactory
139 @see ModelDirectory
140 @see Tableau
141 @see TextEditorTableau
142 */
143public class Configuration extends CompositeEntity
144        implements ApplicationConfigurer, InstanceOpener {
145    /** Construct an instance in the specified workspace with an empty
146     *  string as a name. You can then change the name with setName().
147     *  If the workspace argument is null, then use the default workspace.
148     *  Add the instance to the workspace directory.
149     *  Increment the version number of the workspace.
150     *  Note that there is no constructor that takes a container
151     *  as an argument; a Configuration is always
152     *  a top-level entity (this is enforced by the setContainer()
153     *  method).
154     *  @param workspace The workspace that will list the entity.
155     *  @exception IllegalActionException If the container is incompatible
156     *   with this entity.
157     *  @exception NameDuplicationException If the name coincides with
158     *   an entity already in the container.
159     */
160    public Configuration(Workspace workspace)
161            throws IllegalActionException, NameDuplicationException {
162        super(workspace);
163        _configurations.add(this);
164        classesToRemove = new Parameter(this, "_classesToRemove",
165                new ArrayToken(BaseType.STRING));
166        //classesToRemove.setTypeEquals(new ArrayType(BaseType.STRING));
167        removeGraphicalClasses = new Parameter(this, "_removeGraphicalClasses",
168                BooleanToken.FALSE);
169        removeGraphicalClasses.setTypeEquals(BaseType.BOOLEAN);
170    }
171
172    ///////////////////////////////////////////////////////////////////
173    ////                         public variables                  ////
174
175    /** A Parameter that is an array of Strings where each element
176     *  names a class to be removed.  The initial default value is
177     *  an array with an empty element.
178     *  <p> Kepler uses this parameter to remove certain classes:
179     *  <pre>
180     *  &lt;property name="_classesToRemove" class="ptolemy.data.expr.Parameter"
181     *  value="{&quot;ptolemy.codegen.kernel.StaticSchedulingCodeGenerator&quot;,&quot;ptolemy.codegen.c.kernel.CCodeGenerator&quot;}"&gt;
182     *      &lt;doc&gt;An array of Strings, where each element names a class
183     *  to removed by the MoMLFilter.&lt;/doc&gt;
184     *   &gt;/property&gt;
185     *  </pre>
186     */
187    public Parameter classesToRemove;
188
189    /** A Parameter that if set to true adds {@link
190     * ptolemy.moml.filter.RemoveGraphicalClasses} to the list of
191     * MoMLFilters.  Use this to run non-graphical classes.  Note that
192     * setting this parameter and using MoMLApplication is not likely
193     * to work as MoMLApplication sets the look and feel which invokes
194     * the graphical system.  The initial value is a boolean with the
195     * value false, indicating that RemoveGraphicalClasses should not
196     * be added to the filter list.
197     */
198    public Parameter removeGraphicalClasses;
199
200    ///////////////////////////////////////////////////////////////////
201    ////                         public methods                    ////
202
203    /** React to a change in an attribute.
204     *  @param attribute The attribute that changed.
205     *  @exception IllegalActionException If the change is not acceptable
206     *   to this container (not thrown in this base class).
207     */
208    @Override
209    public void attributeChanged(Attribute attribute)
210            throws IllegalActionException {
211        if (attribute == classesToRemove) {
212            // FIXME: There is code duplication here, but
213            // combining the code caused problems because getting
214            // calling getToken() on the other attribute resulted in
215            // attributeChanged() being called for the other token
216            // which caused rentry problems.  Basically, we ended
217            // up with two RemoveGraphicalClasses filters in the list.
218
219            // Find the RemoveClasses element, if any.
220            RemoveClasses removeClassesFilter = null;
221            List momlFilters = MoMLParser.getMoMLFilters();
222            if (momlFilters == null) {
223                momlFilters = new LinkedList();
224            } else {
225                Iterator filters = momlFilters.iterator();
226                while (filters.hasNext()) {
227                    MoMLFilter filter = (MoMLFilter) filters.next();
228                    if (filter instanceof RemoveClasses) {
229                        removeClassesFilter = (RemoveClasses) filter;
230                        break;
231                    }
232                }
233            }
234
235            // Get the token
236            ArrayToken classesToRemoveToken = (ArrayToken) classesToRemove
237                    .getToken();
238            if (removeClassesFilter == null) {
239                // We did not find a RemoveGraphicalClasses, so create one.
240                removeClassesFilter = new RemoveClasses();
241                momlFilters.add(removeClassesFilter);
242            }
243
244            // We always clear
245            RemoveClasses.clear();
246
247            // _classesToRemove is an array of Strings where each element
248            // names a class to be added to the MoMLFilter for removal.
249            for (int i = 0; i < classesToRemoveToken.length(); i++) {
250                String classNameToRemove = ((StringToken) classesToRemoveToken
251                        .getElement(i)).stringValue();
252                removeClassesFilter.put(classNameToRemove, null);
253            }
254
255            MoMLParser.setMoMLFilters(momlFilters);
256        } else if (attribute == removeGraphicalClasses) {
257            // Find the RemoveGraphicalClasses element, if any
258            RemoveGraphicalClasses removeGraphicalClassesFilter = null;
259            List momlFilters = MoMLParser.getMoMLFilters();
260            if (momlFilters == null) {
261                momlFilters = new LinkedList();
262            } else {
263                Iterator filters = momlFilters.iterator();
264                while (filters.hasNext()) {
265                    MoMLFilter filter = (MoMLFilter) filters.next();
266                    if (filter instanceof RemoveGraphicalClasses) {
267                        removeGraphicalClassesFilter = (RemoveGraphicalClasses) filter;
268                        break;
269                    }
270                }
271            }
272            // Get the token
273            BooleanToken removeGraphicalClassesToken = (BooleanToken) removeGraphicalClasses
274                    .getToken();
275            if (removeGraphicalClassesToken.booleanValue()) {
276                if (removeGraphicalClassesFilter == null) {
277                    // We did not find a RemoveGraphicalClasses, so create one.
278                    removeGraphicalClassesFilter = new RemoveGraphicalClasses();
279                    momlFilters.add(removeGraphicalClassesFilter);
280                }
281            } else {
282                if (removeGraphicalClassesFilter != null) {
283                    momlFilters.remove(removeGraphicalClassesFilter);
284                }
285            }
286            MoMLParser.setMoMLFilters(momlFilters);
287        }
288        super.attributeChanged(attribute);
289    }
290
291    /** Check the configuration for common style problems.
292     *  @return HTML describing the problems
293     *  @exception Exception If there is a problem cloning the configuration.
294     */
295    public String check() throws Exception {
296        StringBuffer results = new StringBuffer();
297        Configuration cloneConfiguration = (Configuration) clone(
298                new Workspace("clonedCheckWorkspace"));
299
300        // Check TypedAtomicActors and Attributes
301        Iterator containedObjects = deepNamedObjList().iterator();
302        while (containedObjects.hasNext()) {
303            NamedObj containedObject = (NamedObj) containedObjects.next();
304            // System.out.println("Configuration.check: containedObject: " + containedObject);
305            // Check the clone fields on AtomicActors and Attributes.
306            // Note that Director extends Attribute, so we get the
307            // Directors as well
308            if (containedObject instanceof TypedAtomicActor
309                    || containedObject instanceof Attribute) {
310                try {
311                    results.append(checkCloneFields(containedObject));
312                } catch (Throwable throwable) {
313                    throw new InternalErrorException(containedObject, null,
314                            throwable,
315                            "The check for " + "clone methods properly setting "
316                                    + "the fields failed.");
317                }
318            }
319        }
320
321        for (CompositeEntity composite : deepCompositeEntityList()) {
322            CompositeEntity clonedComposite = (CompositeEntity) composite
323                    .clone(new Workspace("clonedCompositeWorkspace"));
324            Iterator attributes = composite.attributeList().iterator();
325            while (attributes.hasNext()) {
326                Attribute attribute = (Attribute) attributes.next();
327                // System.out.println("Configuration.check: attribute: " + attribute);
328                if (!attribute.getClass().isMemberClass()) {
329                    // If an attribute is an inner class, it makes
330                    // no sense to clone to a different workspace because
331                    // the outer class will remain in the old workspace.
332                    // For example, IterateOverArray has an inner class
333                    // (IterateDirector) that is an attribute.
334                    try {
335                        results.append(checkCloneFields(attribute));
336                    } catch (Throwable throwable) {
337                        throw new InternalErrorException(attribute, null,
338                                throwable,
339                                "The check for "
340                                        + "clone methods properly setting "
341                                        + "the fields failed.");
342                    }
343                } else {
344                    // Clone the composite and check that any inner
345                    // classes are properly cloned.
346                    String name = attribute.getName();
347                    Attribute clonedAttribute = clonedComposite
348                            .getAttribute(name);
349                    if (clonedAttribute == null) {
350                        results.append("Could not find \"" + name + "\" in "
351                                + composite.getFullName() + "\n"
352                                + clonedComposite.description());
353                    } else {
354                        if (attribute.workspace()
355                                .equals(clonedAttribute.workspace())) {
356                            results.append("\nIn attribute "
357                                    + attribute.getFullName()
358                                    + ", the workspace is the same in the master and "
359                                    + " the clone?");
360                        }
361                        // Check that the Workspace of the outer class is different.
362                        Class attributeClass = attribute.getClass();
363
364                        if (!Modifier.isStatic(attributeClass.getModifiers())) {
365                            // Get the this$0 field, which refers to the outer class.
366                            Field thisZeroField = null;
367                            try {
368                                thisZeroField = attributeClass
369                                        .getDeclaredField("this$0");
370                            } catch (Throwable throwable) {
371                                results.append("Class "
372                                        + attributeClass.getName()
373                                        + " does not have a this$0 field?\n");
374                            }
375                            if (thisZeroField != null) {
376                                thisZeroField.setAccessible(true);
377
378                                Object outer = thisZeroField.get(attribute);
379                                if (Class
380                                        .forName("ptolemy.kernel.util.NamedObj")
381                                        .isAssignableFrom(outer.getClass())) {
382                                    NamedObj outerNamedObj = (NamedObj) outer;
383                                    NamedObj clonedOuterNamedObj = (NamedObj) thisZeroField
384                                            .get(clonedAttribute);
385                                    if (outerNamedObj.workspace().equals(
386                                            clonedOuterNamedObj.workspace())) {
387                                        results.append(
388                                                "\nAn inner class instance has the same workspace in the outer "
389                                                        + "class in both the original and the cloned attribute."
390                                                        + "\n Attribute:        "
391                                                        + attribute
392                                                                .getFullName()
393                                                        + "\n Cloned attribute: "
394                                                        + clonedAttribute
395                                                                .getFullName()
396                                                        + "\n Outer Workspace:       "
397                                                        + outerNamedObj
398                                                                .workspace()
399                                                        + "\n ClonedOuter Workspace: "
400                                                        + clonedOuterNamedObj
401                                                                .workspace()
402                                                        + "\n Outer Object:        "
403                                                        + outerNamedObj
404                                                                .getFullName()
405                                                        + "\n Cloned Outer Object: "
406                                                        + clonedOuterNamedObj
407                                                                .getFullName());
408                                    }
409                                }
410                            }
411                        }
412                    }
413                }
414            }
415            clonedComposite.setContainer(null);
416        }
417
418        // Check atomic actors for clone problems related to types
419        List entityList = allAtomicEntityList();
420        Iterator entities = entityList.iterator();
421        long startTime = (new java.util.Date()).getTime();
422        while (entities.hasNext()) {
423            Object entity = entities.next();
424            System.out.println("Configuration.check: entity: " + entity + " " + ptolemy.actor.Manager.timeAndMemory(startTime));
425            System.out.print("#");
426            if (entity instanceof TypedAtomicActor) {
427                // Check atomic actors for clone problems
428                try {
429                    results.append(checkCloneFields((TypedAtomicActor) entity));
430                } catch (Throwable throwable) {
431                    throw new InternalErrorException((TypedAtomicActor) entity,
432                            null, throwable,
433                            "The check for " + "clone methods properly setting "
434                                    + "the fields failed.");
435                }
436                TypedAtomicActor actor = (TypedAtomicActor) entity;
437                String fullName = actor.getName(this);
438                TypedAtomicActor clone = (TypedAtomicActor) cloneConfiguration
439                        .getEntity(fullName);
440                if (clone == null) {
441                    results.append("Actor " + fullName + " was not cloned!");
442                } else {
443
444                    // First, check type constraints
445                    Set<Inequality> constraints = actor.typeConstraints();
446                    Set<Inequality> cloneConstraints = clone.typeConstraints();
447
448                    // Make sure the clone has the same number of constraints.
449                    if (constraints.size() != cloneConstraints.size()) {
450                        results.append(actor.getFullName() + " has "
451                                + constraints.size() + " constraints that "
452                                + "differ from " + cloneConstraints.size()
453                                + " constraints its clone has.\n"
454                                + " Constraints: \nMaster Constraints:\n");
455                        Iterator constraintIterator = constraints.iterator();
456                        while (constraintIterator.hasNext()) {
457                            Inequality constraint = (Inequality) constraintIterator
458                                    .next();
459                            results.append(constraint.toString() + "\n");
460                        }
461                        results.append("Clone constraints:\n");
462                        Iterator cloneConstraintIterator = cloneConstraints
463                                .iterator();
464                        while (cloneConstraintIterator.hasNext()) {
465                            Inequality constraint = (Inequality) cloneConstraintIterator
466                                    .next();
467                            results.append(constraint.toString() + "\n");
468                        }
469
470                    }
471
472                    // Make sure the constraints are the same.
473                    // First, iterate through the constraints of the master
474                    // and create a HashSet of string descriptions.
475                    // This is likely to work since the problem is usually
476                    // the the clone is missing constraints.
477                    HashSet<String> constraintsDescription = new HashSet<String>();
478                    try {
479                        Iterator constraintIterator = constraints.iterator();
480                        while (constraintIterator.hasNext()) {
481                            Inequality constraint = (Inequality) constraintIterator
482                                    .next();
483                            constraintsDescription.add(constraint.toString());
484                        }
485                    } catch (Throwable throwable) {
486                        throw new IllegalActionException(actor, throwable,
487                                "Failed to iterate through constraints.");
488                    }
489                    // Make sure that each constraint in the clone is present
490                    // in the master
491                    Iterator cloneConstraintIterator = cloneConstraints
492                            .iterator();
493                    while (cloneConstraintIterator.hasNext()) {
494                        Inequality constraint = (Inequality) cloneConstraintIterator
495                                .next();
496                        if (!constraintsDescription
497                                .contains(constraint.toString())) {
498                            results.append(
499                                    "Master object of " + actor.getFullName()
500                                            + " is missing constraint:\n"
501                                            + constraint.toString() + ".\n");
502                        }
503                    }
504
505                    // Now do the same for the clone.
506                    HashSet<String> cloneConstraintsDescription = new HashSet<String>();
507                    try {
508                        Iterator constraintIterator = cloneConstraints
509                                .iterator();
510                        while (constraintIterator.hasNext()) {
511                            Inequality constraint = (Inequality) constraintIterator
512                                    .next();
513                            cloneConstraintsDescription
514                                    .add(constraint.toString());
515                        }
516                    } catch (Throwable throwable) {
517                        throw new IllegalActionException(actor, throwable,
518                                "Failed to iterate through constraints.");
519                    }
520                    // Make sure that each constraint in the clone is present
521                    // in the master
522                    Iterator constraintIterator = constraints.iterator();
523                    while (constraintIterator.hasNext()) {
524                        Inequality constraint = (Inequality) constraintIterator
525                                .next();
526                        if (!cloneConstraintsDescription
527                                .contains(constraint.toString())) {
528                            results.append("Clone of " + actor.getFullName()
529                                    + " is missing constraint:\n"
530                                    + constraint.toString() + ".\n");
531                        }
532                    }
533
534                    // Check that the type constraint is between ports
535                    // and Parameters of the same object.
536
537                    cloneConstraintIterator = cloneConstraints.iterator();
538                    while (cloneConstraintIterator.hasNext()) {
539                        Inequality constraint = (Inequality) cloneConstraintIterator
540                                .next();
541                        InequalityTerm greaterTerm = constraint
542                                .getGreaterTerm();
543                        InequalityTerm lesserTerm = constraint.getLesserTerm();
544
545                        Object greaterAssociatedObject = greaterTerm
546                                .getAssociatedObject();
547                        Object lesserAssociatedObject = lesserTerm
548                                .getAssociatedObject();
549                        if (greaterAssociatedObject instanceof NamedObj
550                                && lesserAssociatedObject instanceof NamedObj) {
551                            // FIXME: what about non-NamedObjs?
552                            NamedObj greaterNamedObj = (NamedObj) greaterAssociatedObject;
553                            NamedObj lesserNamedObj = (NamedObj) lesserAssociatedObject;
554                            greaterNamedObj.getContainer().getClass().getName();
555                            if (lesserNamedObj != null && greaterNamedObj
556                                    .getContainer() != lesserNamedObj
557                                            .getContainer()
558                            // actor.lib.qm.CompositeQM was causing false
559                            // positives because the contstraints had different
560                            // containers, but were contained within the Composite.
561                                    && greaterNamedObj.getContainer()
562                                            .getContainer() != lesserNamedObj
563                                                    .getContainer()
564                                    // PubSubPort that contains an
565                                    // initialTokens that is used to
566                                    // set the type.
567                                    && !(lesserNamedObj
568                                            .getContainer() instanceof PubSubPort)) {
569                                results.append(clone.getFullName()
570                                        + " has type constraints with "
571                                        + "associated objects that don't have "
572                                        + "the same container:\n"
573                                        + greaterNamedObj.getFullName()
574                                        + " has a container:\n"
575                                        + greaterNamedObj.getContainer() + "\n"
576                                        + lesserNamedObj.getFullName()
577                                        + " has a container:\n"
578                                        + lesserNamedObj.getContainer()
579                                        + "\nThe constraint was: " + constraint
580                                        + "\n"
581                                        + "This can occur if the clone(Workspace) "
582                                        + "method is not present or does not set "
583                                        + "the constraints like the constructor "
584                                        + "does or if a Parameter or Port is not "
585                                        + "declared public.\n");
586                            }
587                        }
588                    }
589                }
590            }
591        }
592        // Free up space.
593        cloneConfiguration.setContainer(null);
594        return results.toString();
595    }
596
597    /** Check that clone(Workspace) method properly sets the fields.
598     *  In a cloned Director, Attribute or Actor, all
599     *  private fields should either point to null or to
600     *  distinct objects.
601     *  @param namedObj The NamedObj, usually a Director, Attribute
602     *  or actor to be checked.
603     *  @return A string containing an error message if there is a problem,
604     *  otherwise return the empty string.
605     *  @exception CloneNotSupportedException If namedObj does not support
606     *  clone(Workspace).
607     *  @exception IllegalAccessException If there is a problem getting
608     *  a field.
609     *  @exception ClassNotFoundException If a class cannot be found.
610     */
611    public static String checkCloneFields(NamedObj namedObj)
612            throws CloneNotSupportedException, IllegalAccessException,
613            IllegalActionException, NameDuplicationException,
614            ClassNotFoundException {
615        NamedObj namedObjClone = (NamedObj) namedObj.clone(new Workspace());
616        StringBuffer results = new StringBuffer();
617        Class namedObjClass = namedObj.getClass();
618
619        // We check only the public, protected and private fields
620        // declared in this class, but not inherited fields.
621        Field[] namedObjFields = namedObjClass.getDeclaredFields();
622        for (Field field : namedObjFields) {
623            results.append(_checkCloneField(namedObj, namedObjClone, field));
624        }
625
626        // Get the fields of the parent classes
627        Class clazz = namedObjClass;
628        while (clazz != NamedObj.class && clazz != null) {
629            clazz = clazz.getSuperclass();
630            namedObjFields = clazz.getDeclaredFields();
631            for (Field field : namedObjFields) {
632                field.setAccessible(true);
633                results.append(
634                        _checkCloneField(namedObj, namedObjClone, field));
635            }
636        }
637
638        if (namedObjClone instanceof Attribute) {
639            ((Attribute) namedObjClone).setContainer(null);
640        } else if (namedObjClone instanceof ComponentEntity) {
641            ((ComponentEntity) namedObjClone).setContainer(null);
642        } else if (namedObjClone instanceof Port) {
643            ((Port) namedObjClone).setContainer(null);
644        }
645
646        return results.toString();
647    }
648
649    /** Close all the tableaux.
650     *  @exception IllegalActionException If thrown while accessing the Configuration
651     *  or while closing the tableaux.
652     */
653    public static void closeAllTableaux() throws IllegalActionException {
654        // Based on code by Chad Berkley.
655        Configuration configuration = Configuration.configurations().iterator()
656                .next();
657        // Get the directory from the configuration.
658        ModelDirectory directory = configuration.getDirectory();
659        if (directory == null) {
660            return;
661        }
662
663        Iterator effigies = directory.entityList(Effigy.class).iterator();
664        // Go through the directory and close each effigy.
665        while (effigies.hasNext()) {
666            Effigy effigy = (Effigy) effigies.next();
667            if (!effigy.closeTableaux()) {
668                return;
669            }
670        }
671    }
672
673    /** Return a list of all the configurations that have been created.
674     *  Note  that if this method is called before a configuration
675     *  is created, then it will return an empty linked list.
676     *  @return A list of configurations, where each element of the list
677     *  is of type Configuration.
678     */
679    public static List<Configuration> configurations() {
680        return _configurations;
681    }
682
683    /** Create the first tableau for the given effigy, using the
684     *  tableau factory.  This is called after an effigy is first opened,
685     *  or when a new effigy is created.  If the method fails
686     *  to create a tableau, then it removes the effigy from the directory.
687     *  This prevents us from having lingering effigies that have no
688     *  user interface.
689     *  @return A tableau for the specified effigy, or null if none
690     *   can be opened.
691     *  @param effigy The effigy for which to create a tableau.
692     */
693    public Tableau createPrimaryTableau(final Effigy effigy) {
694        // NOTE: It used to be that the body of this method was
695        // actually executed later, in the event thread, so that it can
696        // safely interact with the user interface.
697        // However, this does not appear to be necessary, and it
698        // makes it impossible to return the tableau.
699        // So we no longer do this.
700        // If the object referenced by the effigy contains
701        // an attribute that is an instance of TextEditorTableauFactory,
702        // then use that factory to create the tableau.
703        // Otherwise, use the first factory encountered in the
704        // configuration that agrees to represent this effigy.
705        TableauFactory factory = null;
706        if (effigy instanceof PtolemyEffigy) {
707            NamedObj model = ((PtolemyEffigy) effigy).getModel();
708
709            if (model != null) {
710                Iterator factories = model.attributeList(TableauFactory.class)
711                        .iterator();
712
713                // If there are more than one of these, use the first
714                // one that agrees to open the model.
715                while (factories.hasNext() && factory == null) {
716                    factory = (TableauFactory) factories.next();
717
718                    try {
719                        Tableau tableau = factory.createTableau(effigy);
720
721                        if (tableau != null) {
722                            // The first tableau is a master if the container
723                            // of the containing effigy is the model directory.
724                            // Used to do this:
725                            // if (effigy.getContainer() instanceof ModelDirectory) {
726                            if (effigy.masterEffigy() == effigy) {
727                                tableau.setMaster(true);
728                            }
729
730                            tableau.setEditable(effigy.isModifiable());
731                            tableau.show();
732                            return tableau;
733                        }
734                    } catch (Exception ex) {
735                        // Ignore so we keep trying.
736                        // NOTE: Uncomment this line to detect bugs when
737                        // you try to open a model and you get a text editor.
738                        // ex.printStackTrace();
739                        factory = null;
740                    }
741                }
742            }
743        }
744
745        // Defer to the configuration.
746        // Create a tableau if there is a tableau factory.
747        factory = (TableauFactory) getAttribute("tableauFactory");
748
749        if (factory != null) {
750            // If this fails, we do not want the effigy to linger
751            try {
752                Tableau tableau = factory.createTableau(effigy);
753
754                if (tableau == null) {
755                    throw new Exception("Tableau factory returns null.");
756                }
757
758                // The first tableau is a master if the container
759                // of the containing effigy is the model directory.
760                if (effigy.getContainer() instanceof ModelDirectory) {
761                    tableau.setMaster(true);
762                }
763
764                tableau.setEditable(effigy.isModifiable());
765                tableau.show();
766                return tableau;
767            } catch (Exception ex) {
768                // NOTE: Uncomment this line to detect bugs when
769                // you try to open a model and you get a text editor.
770                // ex.printStackTrace();
771                // Remove the effigy.  We were unable to open a tableau for it.
772
773                // If we have a link to a missing .htm file, we want to
774                // avoid popping up two MessageHandlers.
775                boolean calledMessageHandler = false;
776                try {
777                    if (effigy == null) {
778                        // Coverity Scan 1352685 warned that effigy can be null.
779                        throw new InternalErrorException(
780                                "createPrimaryTableau() "
781                                        + "called with a null Effigy?");
782                    } else if (effigy
783                            .getContainer() instanceof ModelDirectory) {
784                        // This is the master.
785                        // Calling setContainer() = null will exit, so
786                        // we display the error message here.
787                        //
788                        // We will get to here if
789                        // diva.graph.AbstractGraphController.rerender()
790                        // throws an NullPointerException when starting
791                        // vergil.
792                        if (effigy instanceof PtolemyEffigy) {
793                            if (((PtolemyEffigy) effigy).getModel() != null) {
794                                MessageHandler.error("Failed to open "
795                                        + ((PtolemyEffigy) effigy).getModel()
796                                                .getFullName(),
797                                        ex);
798                            } else {
799                                MessageHandler.error("Failed to open " + effigy,
800                                        ex);
801                            }
802                            calledMessageHandler = true;
803                        } else {
804                            // Opening a link to a non-existent .htm file
805                            // might get us to here because the effigy is
806                            // not a PtolemyEffigy.
807                            //
808                            // Note that because we call MessageHandler here,
809                            // this means that putting a try/catch around
810                            // configuration.openModel() does not do much good
811                            // if a .htm file is not found because the
812                            // MessageHandler pops up before we return
813                            // from the exception.
814                            // In addition, we cannot catch HeadlessExceptions.
815
816                            MessageHandler.error(
817                                    "Failed to open "
818                                            + effigy.identifier.getExpression(),
819                                    ex);
820                            calledMessageHandler = true;
821                        }
822                    }
823
824                    // This eventually calls Configuration._removeEntity()
825                    // which calls System.exit();
826                    effigy.setContainer(null);
827                } catch (Throwable throwable) {
828                    calledMessageHandler = false;
829                    throw new InternalErrorException(this, throwable, null);
830                }
831
832                // As a last resort, attempt to open source code
833                // associated with the object.
834                if (effigy instanceof PtolemyEffigy) {
835                    NamedObj object = ((PtolemyEffigy) effigy).getModel();
836
837                    // Source code is found by name.
838                    String filename = StringUtilities
839                            .objectToSourceFileName(object);
840
841                    try {
842                        URL toRead = getClass().getClassLoader()
843                                .getResource(filename);
844
845                        // If filename was not found in the classpath, then search
846                        // each element in the classpath for a directory named
847                        // src and then search for filename.  This is needed
848                        // by Eclipse for Kepler.  See also TextEffigy.newTextEffigy()
849                        if (toRead == null) {
850                            toRead = ClassUtilities.sourceResource(filename);
851                            //  System.out.println("Configuration: sourceResource "
852                            //        + filename + " " + toRead);
853                        }
854
855                        if (toRead != null) {
856                            return openModel(null, toRead,
857                                    toRead.toExternalForm());
858                        } else {
859                            MessageHandler.error(
860                                    "Cannot find a tableau or the source code for "
861                                            + object.getFullName());
862                        }
863                    } catch (Exception exception) {
864                        MessageHandler
865                                .error("Failed to open the source code for "
866                                        + object.getFullName(), exception);
867                    }
868                }
869
870                // Note that we can't rethrow the exception here
871                // because removing the effigy may result in
872                // the application exiting.
873                if (!calledMessageHandler) {
874                    MessageHandler.error("Failed to open tableau for "
875                            + effigy.identifier.getExpression(), ex);
876                }
877            }
878        }
879        return null;
880    }
881
882    /** Find an effigy for the specified model by searching all the
883     *  configurations that have been created. Although typically there is
884     *  only one, in principle there may be more than one.  This can be used
885     *  to find a configuration, which is typically the result of calling
886     *  toplevel() on the effigy.
887     *  @param model The model for which to find an effigy.
888     *  @return An effigy, or null if none can be found.
889     */
890    public static Effigy findEffigy(NamedObj model) {
891        for (Configuration configuration : _configurations) {
892            Effigy effigy = configuration.getEffigy(model);
893
894            if (effigy != null) {
895                return effigy;
896            }
897        }
898
899        return null;
900    }
901
902    /** Instantiate the class named by a StringParameter in the configuration.
903     *  @param parameterName The name of the StringParameter in the configuration.
904     *  @param constructorParameterTypes An array of parameter types, null if there
905     *  are no parameters
906     *  @param constructorParameterClass An array of objects to pass to the constructor.
907     *  @return an instance of the class named by parameterName, or
908     *  null if the configuration does not contain a parameter by that
909     *  name.
910     *  @exception ClassNotFoundException If the class named by
911     *  parameterName cannot be found.
912     *  @exception IllegalAccessException If the constructor is not accessible.
913     *  @exception IllegalActionException If thrown while reading the
914     *  parameter named by parameterName.
915     *  @exception InstantiationException If the object cannot be instantiated
916     *  @exception java.lang.reflect.InvocationTargetException If
917     *  there is problem invoking the constructor.
918     *  @exception NoSuchMethodException If there is no constructor with the type.
919     */
920    public Object getStringParameterAsClass(String parameterName,
921            Class[] constructorParameterTypes,
922            Object[] constructorParameterClass)
923            throws ClassNotFoundException, IllegalAccessException,
924            IllegalActionException, InstantiationException,
925            java.lang.reflect.InvocationTargetException, NoSuchMethodException {
926        // Deal with the PDF Action first.
927        StringParameter classNameParameter = (StringParameter) getAttribute(
928                parameterName, StringParameter.class);
929
930        if (classNameParameter != null) {
931            String className = classNameParameter.stringValue();
932            Class clazz = Class.forName(className);
933            Constructor constructor = clazz
934                    .getDeclaredConstructor(constructorParameterTypes);
935            return constructor.newInstance(constructorParameterClass);
936        }
937        return null;
938    }
939
940    /** Get the model directory.
941     *  @return The model directory, or null if there isn't one.
942     */
943    public ModelDirectory getDirectory() {
944        Entity directory = getEntity(_DIRECTORY_NAME);
945
946        if (directory instanceof ModelDirectory) {
947            return (ModelDirectory) directory;
948        }
949
950        return null;
951    }
952
953    /** Get the effigy for the specified Ptolemy model.
954     *  This searches all instances of PtolemyEffigy deeply contained by
955     *  the directory, and returns the first one it encounters
956     *  that is an effigy for the specified model.
957     *  @param model The Ptolemy model.
958     *  @return The effigy for the model, or null if none is found.
959     */
960    public PtolemyEffigy getEffigy(NamedObj model) {
961        Entity directory = getEntity(_DIRECTORY_NAME);
962
963        if (directory instanceof ModelDirectory) {
964            return _findEffigyForModel((ModelDirectory) directory, model);
965        } else {
966            return null;
967        }
968    }
969
970    /** Open the specified instance.
971     *  A derived class looks for the instance, and if the instance already has
972     *  open tableaux, then put those in the foreground
973     *  Otherwise, create a new tableau and if
974     *  necessary, a new effigy.  Unless there is a more natural container
975     *  for the effigy (e.g. it is a hierarchical model), then if a new
976     *  effigy is created, it is put into the directory of the configuration.
977     *  Any new tableau created will be contained by that effigy.
978     *
979     *  @param entity The entity to open.
980     *  @exception IllegalActionException If constructing an effigy or tableau
981     *   fails.
982     *  @exception NameDuplicationException If a name conflict occurs (this
983     *   should not be thrown).
984     */
985    @Override
986    public void openAnInstance(NamedObj entity)
987            throws IllegalActionException, NameDuplicationException {
988        // This could be called openInstance(), but we don't want to
989        // have a dependency to Tableau in ModalController and ModalRefinement.
990
991        // Note that we ignore the return value here.
992        openInstance(entity);
993    }
994
995    /** Open the specified instance. If the instance already has
996     *  open tableaux, then put those in the foreground and
997     *  return the first one.  Otherwise, create a new tableau and if
998     *  necessary, a new effigy.  Unless there is a more natural container
999     *  for the effigy (e.g. it is a hierarchical model), then if a new
1000     *  effigy is created, it is put into the directory of the configuration.
1001     *  Any new tableau created will be contained by that effigy.
1002     *  @param entity The entity to open.
1003     *  @return The tableau that is created, or the first one found,
1004     *   or null if none is created or found.
1005     *  @exception IllegalActionException If constructing an effigy or tableau
1006     *   fails.
1007     *  @exception NameDuplicationException If a name conflict occurs (this
1008     *   should not be thrown).
1009     */
1010    public Tableau openInstance(NamedObj entity)
1011            throws IllegalActionException, NameDuplicationException {
1012        return openInstance(entity, null);
1013    }
1014
1015    /** Open the specified instance. If the instance already has
1016     *  open tableaux, then put those in the foreground and
1017     *  return the first one.  Otherwise, create a new tableau and,
1018     *  if necessary, a new effigy. Unless there is a more natural
1019     *  place for the effigy (e.g. it is a hierarchical model), then if a new
1020     *  effigy is created, it is put into the <i>container</i> argument,
1021     *  or if that is null, into the directory of the configuration.
1022     *  Any new tableau created will be contained by that effigy.
1023     *  @param entity The model.
1024     *  @param container The container for any new effigy.
1025     *  @return The tableau that is created, or the first one found,
1026     *   or null if none is created or found.
1027     *  @exception IllegalActionException If constructing an effigy or tableau
1028     *   fails.
1029     *  @exception NameDuplicationException If a name conflict occurs (this
1030     *   should not be thrown).
1031     */
1032    public Tableau openInstance(NamedObj entity, CompositeEntity container)
1033            throws IllegalActionException, NameDuplicationException {
1034        return _openModel(entity, container);
1035    }
1036
1037    /** Open the specified URL.
1038     *  If a model with the specified identifier is present in the directory,
1039     *  then find all the tableaux of that model and make them
1040     *  visible; otherwise, read a model from the specified URL <i>in</i>
1041     *  and create a default tableau for the model and add the tableau
1042     *  to this directory.
1043     *  @param base The base for relative file references, or null if
1044     *   there are no relative file references.
1045     *  @param in The input URL.
1046     *  @param identifier The identifier that uniquely identifies the model.
1047     *  @return The tableau that is created, or null if none.
1048     *  @exception Exception If the URL cannot be read.
1049     */
1050    public Tableau openModel(URL base, URL in, String identifier)
1051            throws Exception {
1052        return openModel(base, in, identifier, null);
1053    }
1054
1055    /** Open the specified URL using the specified effigy factory.
1056     *  If a model with the specified identifier is present in the directory,
1057     *  then find all the tableaux of that model and make them
1058     *  visible; otherwise, read a model from the specified URL <i>in</i>
1059     *  and create a default tableau for the model and add the tableau
1060     *  to this directory.
1061     *  @param base The base for relative file references, or null if
1062     *   there are no relative file references.
1063     *  @param in The input URL.
1064     *  @param identifier The identifier that uniquely identifies the model.
1065     *  @param factory The effigy factory to use.
1066     *  @return The tableau that is created, or null if none.
1067     *  @exception Exception If the URL cannot be read.
1068     */
1069    public Tableau openModel(URL base, URL in, String identifier,
1070            EffigyFactory factory) throws Exception {
1071        ModelDirectory directory = (ModelDirectory) getEntity(_DIRECTORY_NAME);
1072
1073        if (directory == null) {
1074            throw new InternalErrorException("No model directory!");
1075        }
1076
1077        // Check to see whether the model is already open.
1078        Effigy effigy = directory.getEffigy(identifier);
1079
1080        if (effigy == null) {
1081            // No previous effigy exists that is identified by this URL.
1082            // Find an effigy factory to read it.
1083            if (factory == null) {
1084                // Check to see whether the URL includes the special
1085                // target "#in_browser", and if so, use a BrowserEffigy factory.
1086                // NOTE: This used to be handled only by HTMLViewer, but
1087                // this limited where the #in_browser notation could be used.
1088                // The following is adapted from HTMLViewer.
1089
1090                // NOTE: It would be nice to use target="_browser" or some
1091                // such, but this doesn't work. Targets aren't
1092                // seen unless the link is inside a frame,
1093                // regrettably.  An alternative might be to
1094                // use the "userInfo" part of the URL,
1095                // defined at http://www.ncsa.uiuc.edu/demoweb/url-primer.html
1096                boolean useBrowser = false;
1097                String ref = in.getRef();
1098                if (ref != null) {
1099                    useBrowser = ref.equals("in_browser");
1100                }
1101                String protocol = in.getProtocol();
1102                if (protocol != null) {
1103                    // Suggested mailto: extension from Paul Lieverse.
1104                    // Unfortunately, it doesn't work for me on a Mac.
1105                    // Leaving it here in case it works for someone else.
1106                    useBrowser |= protocol.equals("mailto");
1107                }
1108                if (useBrowser) {
1109                    if (BrowserEffigy.staticFactory == null) {
1110                        // The following will set BrowserEffigy.staticFactory.
1111                        new BrowserEffigy.Factory(this, "browserEffigyFactory");
1112                    }
1113                    factory = BrowserEffigy.staticFactory;
1114                } else {
1115                    // Not a browser or mailto URL.
1116                    // Get an effigy factory from this configuration.
1117                    factory = (EffigyFactory) getEntity("effigyFactory");
1118                }
1119            }
1120
1121            if (factory == null) {
1122                throw new InternalErrorException(
1123                        "No effigy factories in the configuration!");
1124            }
1125
1126            effigy = factory.createEffigy(directory, base, in);
1127
1128            if (effigy == null) {
1129                MessageHandler.error(
1130                        "Unsupported file type or connection not available: "
1131                                + in.toExternalForm());
1132                return null;
1133            }
1134
1135            if (effigy.identifier.getExpression().compareTo("Unnamed") == 0) {
1136                // If the value identifier field of the effigy we just
1137                // created is "Unnamed", then set it to the value of
1138                // the identifier parameter.
1139                //
1140                // HSIFEffigyFactory sets effiigy.identifier because it
1141                // converts the file we specified from HSIF to MoML and then
1142                // opens up a file other than the one we specified.
1143                effigy.identifier.setExpression(identifier);
1144            }
1145
1146            // Check the URL to see whether it is a file,
1147            // and if so, whether it is writable.
1148            if (in != null && in.getProtocol().equals("file")) {
1149                String filename = in.getFile();
1150                File file = new File(filename);
1151
1152                try {
1153                    if (!file.canWrite()) {
1154                        // FIXME: we need a better way to check if
1155                        // a URL is writable.
1156
1157                        // Sigh.  If the filename has spaces in it,
1158                        // then the URL will have %20s.  However,
1159                        // the file does not have %20s.
1160                        // See
1161                        // https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=153
1162                        filename = StringUtilities.substitute(filename, "%20",
1163                                " ");
1164                        file = new File(filename);
1165                        if (!file.canWrite()) {
1166                            effigy.setModifiable(false);
1167                        }
1168                    }
1169                } catch (java.security.AccessControlException accessControl) {
1170                    // If we are running in a sandbox, then canWrite()
1171                    // may throw an AccessControlException.
1172                    effigy.setModifiable(false);
1173                }
1174            } else {
1175                effigy.setModifiable(false);
1176            }
1177
1178            return createPrimaryTableau(effigy);
1179        } else {
1180            // Model already exists.
1181            return effigy.showTableaux();
1182        }
1183    }
1184
1185    /** Open the specified Ptolemy II model. If a model already has
1186     *  open tableaux, then put those in the foreground and
1187     *  return the first one.  Otherwise, create a new tableau and if
1188     *  necessary, a new effigy.  Unless there is a more natural container
1189     *  for the effigy (e.g. it is a hierarchical model), then if a new
1190     *  effigy is created, it is put into the directory of the configuration.
1191     *  Any new tableau created will be contained by that effigy.
1192     *  @param entity The model.
1193     *  @return The tableau that is created, or the first one found,
1194     *   or null if none is created or found.
1195     *  @exception IllegalActionException If constructing an effigy or tableau
1196     *   fails.
1197     *  @exception NameDuplicationException If a name conflict occurs (this
1198     *   should not be thrown).
1199     */
1200    public Tableau openModel(NamedObj entity)
1201            throws IllegalActionException, NameDuplicationException {
1202        NamedObj container = entity.getContainer();
1203        Effigy containerEffigy = getEffigy(container);
1204        return openModel(entity, containerEffigy);
1205    }
1206
1207    /** Open the specified Ptolemy II model. If a model already has
1208     *  open tableaux, then put those in the foreground and
1209     *  return the first one.  Otherwise, create a new tableau and,
1210     *  if necessary, a new effigy. Unless there is a more natural
1211     *  place for the effigy (e.g. it is a hierarchical model), then if a new
1212     *  effigy is created, it is put into the <i>container</i> argument,
1213     *  or if that is null, into the directory of the configuration.
1214     *  Any new tableau created will be contained by that effigy.
1215     *  @param entity The model.
1216     *  @param container The container for any new effigy.
1217     *  @return The tableau that is created, or the first one found,
1218     *   or null if none is created or found.
1219     *  @exception IllegalActionException If constructing an effigy or tableau
1220     *   fails.
1221     *  @exception NameDuplicationException If a name conflict occurs (this
1222     *   should not be thrown).
1223     */
1224    public Tableau openModel(NamedObj entity, CompositeEntity container)
1225            throws IllegalActionException, NameDuplicationException {
1226        // If the entity defers its MoML definition to another,
1227        // then open that other, unless this is a class extending another,
1228        // and also unless this is an object that contains a TableauFactory.
1229        // I.e., by default, when you open an instance of a class, what
1230        // is opened is the class definition, not the instance, unless
1231        // the instance contains a TableauFactory, in which case, we defer
1232        // to that TableauFactory.
1233        InstantiableNamedObj deferredTo = null;
1234        boolean isClass = false;
1235
1236        if (entity instanceof InstantiableNamedObj) {
1237            deferredTo = (InstantiableNamedObj) ((InstantiableNamedObj) entity)
1238                    .getParent();
1239            isClass = ((InstantiableNamedObj) entity).isClassDefinition();
1240        }
1241
1242        if (deferredTo != null && !isClass) {
1243            entity = deferredTo;
1244        }
1245
1246        return _openModel(entity, container);
1247    }
1248
1249    /** If the argument is not null, then throw an exception.
1250     *  This ensures that the object is always at the top level of
1251     *  a hierarchy.
1252     *  @param container The proposed container.  If the proposed
1253     *  container is null, then super.setContainer(null) is invoked,
1254     *  which may free up memory.
1255     *  @exception IllegalActionException If the argument is not null.
1256     *  @exception NameDuplicationException If thrown by a parent class.
1257     */
1258    @Override
1259    public void setContainer(CompositeEntity container)
1260            throws IllegalActionException, NameDuplicationException {
1261        if (container != null) {
1262            throw new IllegalActionException(this,
1263                    "Configuration can only be at the top level "
1264                            + "of a hierarchy.");
1265        }
1266        super.setContainer(null);
1267    }
1268
1269    /** Find all instances of Tableau deeply contained in the directory
1270     *  and call show() on them.  If there is no directory, then do nothing.
1271     */
1272    public void showAll() {
1273        final ModelDirectory directory = (ModelDirectory) getEntity(
1274                _DIRECTORY_NAME);
1275
1276        if (directory == null) {
1277            return;
1278        }
1279
1280        _showTableaux(directory);
1281    }
1282
1283    ///////////////////////////////////////////////////////////////////
1284    ////                         public variables                  ////
1285
1286    /** The name of the model directory. */
1287    static public final String _DIRECTORY_NAME = "directory";
1288
1289    ///////////////////////////////////////////////////////////////////
1290    ////                         package protected methods         ////
1291
1292    /** Remove the configuration from the list of configurations.
1293     *  @param configuration The configuration to be removed.
1294     */
1295    void removeConfiguration(Configuration configuration) {
1296        _configurations.remove(configuration);
1297    }
1298
1299    ///////////////////////////////////////////////////////////////////
1300    ////                         protected methods                 ////
1301
1302    /** Remove the specified entity; if that entity is the model directory,
1303     *  then exit the application.  This method should not be called
1304     *  directly.  Call the setContainer() method of the entity instead with
1305     *  a null argument.
1306     *  The entity is assumed to be contained by this composite (otherwise,
1307     *  nothing happens). This does not alter the entity in any way.
1308     *  This method is <i>not</i> synchronized on the workspace, so the
1309     *  caller should be.
1310     *  @param entity The entity to remove.
1311     */
1312    @Override
1313    protected void _removeEntity(ComponentEntity entity) {
1314        super._removeEntity(entity);
1315        if (entity.getName().equals(_DIRECTORY_NAME)) {
1316            // If the ptolemy.ptII.doNotExit property or the
1317            // ptolemy.ptII.exitAfterWrapup property is set
1318            // then we don't actually call System.exit().
1319            //
1320            StringUtilities.exit(0);
1321        }
1322    }
1323
1324    ///////////////////////////////////////////////////////////////////
1325    ////                         private methods                   ////
1326
1327    /** Check that clone(Workspace) method properly sets the fields.
1328     *  In a cloned Director, Attribute or Actor, all
1329     *  private fields should either point to null or to
1330     *  distinct objects.
1331     *  @param namedObj The NamedObj, usually a Director, Attribute
1332     *  or actor to be checked.
1333     *  @param namedObjClone the clone of the namedObj, created with
1334     *  clone(new Workspace())
1335     *  @param field The field to be checked.
1336     *  @return A string containing an error message if there is a problem,
1337     *  otherwise return the empty string.
1338     *  @exception CloneNotSupportedException If namedObj does not support
1339     *  clone(Workspace).
1340     *  @exception IllegalAccessException If there is a problem getting
1341     *  a field.
1342     *  @exception ClassNotFoundException If a class cannot be found.
1343     */
1344    private static String _checkCloneField(NamedObj namedObj,
1345            NamedObj namedObjClone, Field field)
1346            throws CloneNotSupportedException, IllegalAccessException,
1347            ClassNotFoundException {
1348        Class namedObjClass = namedObj.getClass();
1349        StringBuffer results = new StringBuffer();
1350        // Tell the security manager we want to read private fields.
1351        // This will fail in an applet.
1352        field.setAccessible(true);
1353        Class fieldType = field.getType();
1354        if (!fieldType.isPrimitive() && field.get(namedObj) != null
1355                && !Modifier.isStatic(field.getModifiers())
1356                && !Modifier.isStatic(fieldType.getModifiers()) //matlab.Engine.ConversionParameters.
1357                /*&& !fieldType.isArray()*/
1358                // Skip fields introduced by javascope
1359                && !fieldType.toString().equals(
1360                        "COM.sun.suntest.javascope.database.CoverageUnit")
1361                && !field.getName().equals("js$p")
1362                // Skip fields introduced by backtracking
1363                && !(field.getName().indexOf("$RECORD$") != -1)
1364                && !(field.getName().indexOf("$RECORDS") != -1)
1365                && !(field.getName().indexOf("$CHECKPOINT") != -1)
1366                // Skip dependency injection fields
1367                && !(field.getName().indexOf("_implementation") != -1)
1368                // Skip immutables
1369                && !fieldType.equals(java.net.InetAddress.class)
1370                && !fieldType.equals(java.util.regex.Pattern.class)
1371                // SharedParameter has a _containerClass field
1372                && !fieldType.equals(Boolean.class)
1373                && !fieldType.equals(Class.class)
1374                && !fieldType.equals(String.class)
1375                && !fieldType.equals(Token.class)
1376                // Variable has various type fields
1377                && !fieldType.equals(ptolemy.data.type.Type.class)
1378                && !fieldType.equals(Settable.Visibility.class)) {
1379
1380            // If an object is equal and the default hashCode() from
1381            // Object is the same, then we have a problem.
1382            if (field.get(namedObj).equals(field.get(namedObjClone))
1383                    && System.identityHashCode(field.get(namedObj)) == System
1384                            .identityHashCode(field.get(namedObjClone))) {
1385
1386                String message = "";
1387                if (Class.forName("ptolemy.kernel.util.NamedObj")
1388                        .isAssignableFrom(fieldType)) {
1389                    NamedObj fieldNamedObj = (NamedObj) Class
1390                            .forName("ptolemy.kernel.util.NamedObj")
1391                            .cast(field.get(namedObj));
1392                    NamedObj cloneNamedObj = (NamedObj) Class
1393                            .forName("ptolemy.kernel.util.NamedObj")
1394                            .cast(field.get(namedObjClone));
1395                    message = "Field: " + fieldNamedObj.workspace().getName()
1396                            + " Clone: " + cloneNamedObj.workspace().getName();
1397                }
1398
1399                // Determine what code should go in clone(W)
1400                String assignment = field.getName();
1401                // FIXME: extend this to more types
1402                if (Class.forName("ptolemy.kernel.Port")
1403                        .isAssignableFrom(fieldType)) {
1404                    assignment = ".getPort(\"" + assignment + "\")";
1405                    //                       } else if (fieldType.isInstance( new Attribute())) {
1406                } else if (Class.forName("ptolemy.kernel.util.Attribute")
1407                        .isAssignableFrom(fieldType)) {
1408                    Attribute fieldAttribute = (Attribute) field
1409                            .get(namedObjClone);
1410
1411                    if (fieldAttribute.getContainer() != namedObjClone) {
1412                        // If the attribute is actually contained by a Port
1413                        // and not by the AtomicActor, then get its value.
1414                        // SDF actors that have ports that have
1415                        // tokenConsumptionRate and tokenProductionRate
1416                        // such as ConvolutionalCoder need this.
1417                        assignment = "."
1418                                + fieldAttribute.getContainer().getName()
1419                                + ".getAttribute(\"" + fieldAttribute.getName()
1420                                + "\")";
1421                    } else {
1422                        assignment = ".getAttribute(\"" + assignment + "\")";
1423                    }
1424                } else {
1425                    assignment = "\n\t/* Get the object method "
1426                            + "or null?  */ " + assignment;
1427                }
1428
1429                String shortClassName = field.getType().getName().substring(
1430                        field.getType().getName().lastIndexOf(".") + 1);
1431
1432                //new Exception("Configuration._checkCloneField()").printStackTrace();
1433
1434                results.append("The " + field.getName() + " "
1435                        + field.getType().getName() + " field"
1436                        + "\n\tin the clone of \"" + namedObjClass.getName()
1437                        + "\"\n\tdoes not point to an "
1438                        + "object distinct from the "
1439                        + "master.  \n\tThis may cause problems with "
1440                        + "actor oriented classes."
1441                        + "\n\tThe clone(Workspace) "
1442                        + "method should have a line " + "like:\n newObject."
1443                        + field.getName() + " = (" + shortClassName
1444                        + ")newObject" + assignment + ";\n" + message);
1445            }
1446
1447        }
1448        return results.toString();
1449
1450    }
1451
1452    /** Return an identifier for the specified effigy based on its
1453     *  container (if any) and its name.
1454     *  @return An identifier for the effigy.
1455     */
1456    private String _effigyIdentifier(Effigy effigy, NamedObj entity) {
1457        // Set the identifier of the effigy to be that
1458        // of the parent with the model name appended.
1459        NamedObj parent = effigy.getContainer();
1460
1461        if (!(parent instanceof Effigy)) {
1462            return effigy.getFullName();
1463        }
1464
1465        Effigy parentEffigy = (Effigy) parent;
1466
1467        // Note that we add a # the first time, and
1468        // then add . after that.  So
1469        // file:/c:/foo.xml#bar.bif is ok, but
1470        // file:/c:/foo.xml#bar#bif is not
1471        // If the title does not contain a legitimate
1472        // way to reference the submodel, then the user
1473        // is likely to look at the title and use the wrong
1474        // value if they xml edit files by hand. (cxh-4/02)
1475        String entityName = parentEffigy.identifier.getExpression();
1476        String separator = "#";
1477
1478        if (entityName.indexOf("#") >= 0) {
1479            separator = ".";
1480        }
1481
1482        return entityName + separator + entity.getName();
1483    }
1484
1485    // Recursively search the specified composite for an instance of
1486    // PtolemyEffigy that matches the specified model.
1487    private PtolemyEffigy _findEffigyForModel(CompositeEntity composite,
1488            NamedObj model) {
1489        if (composite != null) {
1490            Iterator effigies = composite.entityList(PtolemyEffigy.class)
1491                    .iterator();
1492
1493            while (effigies.hasNext()) {
1494                PtolemyEffigy effigy = (PtolemyEffigy) effigies.next();
1495
1496                // First see whether this effigy matches.
1497                if (effigy.getModel() == model) {
1498                    return effigy;
1499                }
1500
1501                // Then see whether any effigy inside this one matches.
1502                PtolemyEffigy inside = _findEffigyForModel(effigy, model);
1503
1504                if (inside != null) {
1505                    return inside;
1506                }
1507            }
1508        }
1509
1510        return null;
1511    }
1512
1513    /** Open the specified model without deferring to its class definition.
1514     *  @param entity The model to open.
1515     *  @param container The container for any new effigy.
1516     *  @return The tableau that is created, or the first one found,
1517     *   or null if none is created or found.
1518     *  @exception IllegalActionException If constructing an effigy or tableau
1519     *   fails.
1520     *  @exception NameDuplicationException If a name conflict occurs (this
1521     *   should not be thrown).
1522     */
1523    private Tableau _openModel(NamedObj entity, CompositeEntity container)
1524            throws IllegalActionException, NameDuplicationException {
1525        if (entity == null) {
1526            throw new IllegalActionException("Nothing to open.");
1527        }
1528        // Search the model directory for an effigy that already
1529        // refers to this model.
1530        PtolemyEffigy effigy = getEffigy(entity);
1531
1532        if (effigy != null) {
1533            // Found one.  Display all open tableaux.
1534            return effigy.showTableaux();
1535        } else {
1536            // There is no pre-existing effigy.  Create one.
1537            effigy = new PtolemyEffigy(workspace());
1538            effigy.setModel(entity);
1539
1540            // Look to see whether the model has a URIAttribute.
1541            List attributes = entity.attributeList(URIAttribute.class);
1542
1543            if (attributes.size() > 0) {
1544                // The entity has a URI, which was probably
1545                // inserted by MoMLParser.
1546                URI uri = ((URIAttribute) attributes.get(0)).getURI();
1547
1548                // Set the URI and identifier of the effigy.
1549                effigy.uri.setURI(uri);
1550
1551                // NOTE: The uri might be null, which results in
1552                // a null pointer exception below. In particular,
1553                // the class Effigy always has a URI attribute, but
1554                // the value might not get set.
1555                if (uri == null) {
1556                    effigy.identifier
1557                            .setExpression(_effigyIdentifier(effigy, entity));
1558                } else {
1559                    effigy.identifier.setExpression(uri.toString());
1560                }
1561
1562                if (container == null) {
1563                    // Put the effigy into the directory
1564                    ModelDirectory directory = getDirectory();
1565                    if (directory == null) {
1566                        throw new NullPointerException("While trying to open "
1567                                + entity + " in " + container
1568                                + ", getDirectory() returned null?  "
1569                                + "Perhaps an entity of name \""
1570                                + _DIRECTORY_NAME + "\" was not created?");
1571                    }
1572                    effigy.setName(directory.uniqueName(entity.getName()));
1573                    effigy.setContainer(directory);
1574                } else {
1575                    effigy.setName(container.uniqueName(entity.getName()));
1576                    effigy.setContainer(container);
1577                }
1578
1579                // Create a default tableau.
1580                return createPrimaryTableau(effigy);
1581            } else {
1582                // If we get here, then we are looking inside a model
1583                // that is defined within the same file as the parent,
1584                // probably.  Create a new PtolemyEffigy
1585                // and open a tableau for it.
1586                // Put the effigy inside the effigy of the parent,
1587                // rather than directly into the directory.
1588                NamedObj parent = entity.getContainer();
1589                PtolemyEffigy parentEffigy = null;
1590
1591                // Find the first container above in the hierarchy that
1592                // has an effigy.
1593                while (parent != null && parentEffigy == null) {
1594                    parentEffigy = getEffigy(parent);
1595                    parent = parent.getContainer();
1596                }
1597
1598                boolean isContainerSet = false;
1599
1600                if (parentEffigy != null) {
1601                    // OK, we can put it into this other effigy.
1602                    effigy.setName(parentEffigy.uniqueName(entity.getName()));
1603                    effigy.setContainer(parentEffigy);
1604
1605                    // Set the uri of the effigy to that of
1606                    // the parent.
1607                    effigy.uri.setURI(parentEffigy.uri.getURI());
1608
1609                    // Indicate success.
1610                    isContainerSet = true;
1611                }
1612
1613                // If the above code did not find an effigy to put
1614                // the new effigy within, then put it into the
1615                // directory directly or the specified container.
1616                if (!isContainerSet) {
1617                    if (container == null) {
1618                        CompositeEntity directory = getDirectory();
1619                        effigy.setName(directory.uniqueName(entity.getName()));
1620                        effigy.setContainer(directory);
1621                    } else {
1622                        effigy.setName(container.uniqueName(entity.getName()));
1623                        effigy.setContainer(container);
1624                    }
1625                }
1626
1627                effigy.identifier
1628                        .setExpression(_effigyIdentifier(effigy, entity));
1629
1630                return createPrimaryTableau(effigy);
1631            }
1632        }
1633    }
1634
1635    // Call show() on all instances of Tableaux contained by the specified
1636    // container.
1637    private void _showTableaux(CompositeEntity container) {
1638        Iterator entities = container.entityList().iterator();
1639
1640        while (entities.hasNext()) {
1641            Object entity = entities.next();
1642
1643            if (entity instanceof Tableau) {
1644                ((Tableau) entity).show();
1645            } else if (entity instanceof CompositeEntity) {
1646                _showTableaux((CompositeEntity) entity);
1647            }
1648        }
1649    }
1650
1651    ///////////////////////////////////////////////////////////////////
1652    ////                         private variables                 ////
1653
1654    /** The list of configurations that have been created. */
1655    private static LinkedList<Configuration> _configurations = new LinkedList<Configuration>();
1656}