001/* An application that reads one or more files specified on the command line.
002
003 Copyright (c) 1999-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
027 */
028package ptolemy.actor.gui;
029
030import java.io.File;
031import java.io.FilenameFilter;
032import java.io.IOException;
033import java.io.InputStream;
034import java.lang.reflect.Constructor;
035import java.net.URI;
036import java.net.URISyntaxException;
037import java.net.URL;
038import java.util.Iterator;
039import java.util.LinkedList;
040import java.util.List;
041
042import javax.swing.JFrame;
043
044import ptolemy.actor.CompositeActor;
045import ptolemy.actor.Director;
046import ptolemy.actor.ExecutionListener;
047import ptolemy.actor.Manager;
048import ptolemy.actor.TypedCompositeActor;
049import ptolemy.actor.injection.ActorModuleInitializer;
050import ptolemy.data.expr.Parameter;
051import ptolemy.data.expr.StringParameter;
052import ptolemy.kernel.ComponentEntity;
053import ptolemy.kernel.CompositeEntity;
054import ptolemy.kernel.attributes.VersionAttribute;
055import ptolemy.kernel.util.Attribute;
056import ptolemy.kernel.util.IllegalActionException;
057import ptolemy.kernel.util.InternalErrorException;
058import ptolemy.kernel.util.KernelException;
059import ptolemy.kernel.util.NameDuplicationException;
060import ptolemy.kernel.util.NamedObj;
061import ptolemy.kernel.util.Settable;
062import ptolemy.kernel.util.Workspace;
063import ptolemy.moml.Documentation;
064import ptolemy.moml.ErrorHandler;
065import ptolemy.moml.MoMLChangeRequest;
066import ptolemy.moml.MoMLParser;
067import ptolemy.moml.SimpleErrorHandler;
068import ptolemy.moml.filter.BackwardCompatibility;
069import ptolemy.util.ClassUtilities;
070import ptolemy.util.FileUtilities;
071import ptolemy.util.MessageHandler;
072import ptolemy.util.StringUtilities;
073
074///////////////////////////////////////////////////////////////////
075//// ConfigurationApplication
076
077/**
078 An application that reads one or more
079 files specified on the command line, or instantiates one or
080 more Java classes specified by the -class option.
081 If one of these files is an XML file that defines a Configuration, or one
082 of the classes is an instance of Configuration, then
083 all subsequent files will be read by delegating to the Configuration,
084 invoking its openModel() method.  A command-line file is assumed to be
085 a MoML file or a file that can be opened by the specified configuration.
086 <p>For example, this command uses the HyVisual configuration to
087 open a model:
088 <pre>
089 $PTII/bin/moml $PTII/ptolemy/configs/hyvisual/configuration.xml $PTII/ptolemy/domains/ct/demo/StickyMasses/StickyMasses.xml
090 </pre>
091 <p>
092 If a Ptolemy model is instantiated on the command line, either
093 by giving a MoML file or a -class argument, then parameters of that
094 model can be set on the command line.  The syntax is:
095 <pre>
096 $PTII/bin/ptolemy <i>modelFile.xml</i> -<i>parameterName</i> <i>value</i>
097 </pre>
098 where <i>parameterName</i> is the name of a parameter relative to
099 the top level of a model or the director of a model.  For instance,
100 if foo.xml defines a toplevel entity named <code>x</code> and
101 <code>x</code> contains an entity named <code>y</code> and a
102 parameter named <code>a</code>, and <code>y</code> contains a
103 parameter named <code>b</code>, then:
104 <pre>
105 $PTII/bin/ptolemy foo.xml -a 5 -y.b 10
106 </pre>
107 would set the values of the two parameters.
108
109 <p>Note that strings need to have double quotes converted to
110 <code>&amp;quot;</code> so to set a parameter named <code>c</code>
111 to the string <code>"bar"</code> it might be necessary to do
112 something like:</p>
113 <pre>
114 $PTII/bin/ptolemy foo.xml -a 5 -y.b 10 -c "&amp;quot;bar&amp;quot;"
115 </pre>
116 <p>The <code>&amp;quot;</code> is necessary to convert the double quote
117 to something safe to in an XML file.  The backslashes are necessary
118 to protect the <code>&amp;</code> and <code>;</code> from the shell
119 in the shell script.</p>
120
121 <p>Note that the ptolemy.actor.parameters.ParameterSet attribute is
122 a better way to set parameters at run time.  ParameterSet is
123 an attribute that reads multiple values from a file and sets
124 corresponding parameters in the container.</p>
125
126 <p>The -class option can be used to specify a Java class to be loaded.
127 The named class must have a constructor that takes a Workspace
128 as an argument.
129 In the example below, $PTII/ptolemy/domains/sdf/demo/Butterfly/Butterfly.java
130 is a class that has a constructor Butterfly(Workspace).
131 <pre>
132 $PTII/bin/ptolemy -class ptolemy.domains.sdf.demo.Butterfly.Butterfly
133 </pre>
134 Note that -class is very well tested now that we have use MoML
135 for almost all models.
136
137 <p>
138 Derived classes may provide default configurations. In particular, the
139 protected method _createDefaultConfiguration() is called before any
140 arguments are processed to provide a default configuration for those
141 command-line command-line arguments.  In this base class,
142 that method returns null, so no default configuration is provided.
143 <p>
144 If no arguments are given at all, then a default configuration is instead
145 obtained by calling the protected method _createEmptyConfiguration().
146 In this base class, that method also returns null,
147 so calling this with no arguments will not be very useful.
148 No configuration will be created and no models will be opened.
149 Derived classes can specify a configuration that opens some
150 welcome window, or a blank editor.
151
152 <p> This class read the following parameters from the configuration:
153 <dl>
154 <dt> <code>_applicationInitializer</code>
155 <dd> A StringParameter that names a class to be instantiated.
156 Kepler uses this parameter to instantiate KeplerInitializer:
157 <pre>
158 &gt;property name="_applicationInitializer"
159         class="ptolemy.data.expr.StringParameter"
160         value="org.kepler.gui.KeplerInitializer"/&lt;
161</pre>
162
163 </dl>
164
165 <p>To run this class from the command line without any of the
166 Ptolemy-specific scripts, try:</p>
167<pre>
168java -classpath $PTII ptolemy.actor.gui.ConfigurationApplication \
169   -run20x $PTII/ptolemy/configs/full/configuration.xml \
170   $PTII/ptolemy/actor/lib/test/auto/Ramp1.xml
171</pre>
172
173 @author Edward A. Lee and Steve Neuendorffer, Contributor: Christopher Hylands
174 @version $Id$
175 @since Ptolemy II 8.0
176 @Pt.ProposedRating Yellow (eal)
177 @Pt.AcceptedRating Red (eal)
178 @see Configuration
179 */
180public class ConfigurationApplication implements ExecutionListener {
181
182    /**
183     * Instantiate a ConfigurationApplication.  This constructor is probably
184     * not useful by itself, it is for use by subclasses.
185     *
186     * <p>The HandSimDroid work in $PTII/ptserver uses dependency
187     * injection to determine which implementation actors such as
188     * Const and Display to use.  This method reads the
189     * ptolemy/actor/ActorModule.properties file.</p>
190     */
191    public ConfigurationApplication() {
192        ActorModuleInitializer.initializeInjector();
193    }
194
195    /** Parse the specified command-line arguments, instantiating classes
196     *  and reading files that are specified.
197     *  @param args The command-line arguments.
198     *  @exception Exception If command line arguments have problems.
199     */
200    public ConfigurationApplication(String[] args) throws Exception {
201        this("ptolemy/configs", args);
202    }
203
204    /** Parse the specified command-line arguments, instantiating classes
205     *  and reading files that are specified.
206     *  @param basePath The basePath to look for configurations
207     *  in, usually "ptolemy/configs", but other tools might
208     *  have other configurations in other directories
209     *  @param args The command-line arguments.
210     *  @exception Exception If command line arguments have problems.
211     */
212    public ConfigurationApplication(String basePath, String[] args)
213            throws Exception {
214        this(basePath, args, MessageHandler.getMessageHandler(),
215                new SimpleErrorHandler());
216    }
217
218    /** Parse the specified command-line arguments, instantiating classes
219     *  and reading files that are specified.
220     *  @param basePath The basePath to look for configurations
221     *  in, usually "ptolemy/configs", but other tools might
222     *  have other configurations in other directories
223     *  @param args The command-line arguments.
224     *  @param messageHandler The message handler.
225     *  @param errorHandler the MoML error handler.
226     *  @exception Exception If command line arguments have problems.
227     */
228    public ConfigurationApplication(String basePath, String[] args,
229            MessageHandler messageHandler, ErrorHandler errorHandler)
230            throws Exception {
231        this();
232
233        _initializeApplication();
234
235        try {
236            StringUtilities.addPtolemyLibraryDirectoryToJavaLibraryPath();
237        } catch (Throwable throwable) {
238            // Java 12 no longer has ClassLoader.usr_paths.
239            System.err.println("ConfigurationApplication(): Could not add the ptolemy "
240                               + "library directory to the Java library path: "
241                               + throwable);
242        }
243        _basePath = basePath;
244
245        // Create a parser to use.
246        _parser = new MoMLParser();
247
248        MoMLParser.setErrorHandler(errorHandler);
249
250        // We set the list of MoMLFilters to handle Backward Compatibility.
251        MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters());
252
253        // 2/03: Moved the setMessageHandler() to before parseArgs() so
254        // that if we get an error in parseArgs() we will get a graphical
255        // stack trace.   Such an error could be caused by specifying a model
256        // as a command line argument and the model has an invalid parameter.
257        MessageHandler.setMessageHandler(messageHandler);
258
259        // Even if the user is set up for foreign locale, use the US locale.
260        // This is because certain parts of Ptolemy (like the expression
261        // language) are not localized.
262        // FIXME: This is a workaround for the locale problem, not a fix.
263        // FIXME: In March, 2001, Johan Ecker writes
264        // Ptolemy gave tons of exception when started on my laptop
265        // which has Swedish settings as default. The Swedish standard
266        // for floating points are "2,3", i.e. using a comma as
267        // delimiter. However, I think most Swedes are adaptable and
268        // do not mind using a dot instead since this more or less has
269        // become the world standard, at least in engineering. The
270        // problem is that I needed to change my global settings to
271        // start Ptolemy and this is quite annoying. I guess that the
272        // expression parser should just ignore the delimiter settings
273        // on the local computer and always use dot, otherwise Ptolemy
274        // will crash using its own init files.
275        try {
276            java.util.Locale.setDefault(java.util.Locale.US);
277        } catch (java.security.AccessControlException accessControl) {
278            // FIXME: If the application is run under Web Start, then this
279            // exception will be thrown.
280        }
281
282        try {
283            _parseArgs(args);
284
285            if (_statistics && !_run) {
286                Iterator models = models().iterator();
287
288                while (models.hasNext()) {
289                    NamedObj model = (NamedObj) models.next();
290                    if (model instanceof CompositeEntity) {
291                        System.out.println(
292                                "Statistics for " + model.getFullName());
293                        System.out.println(
294                                ((CompositeEntity) model).statistics(null));
295                    }
296                }
297                if (_exit) {
298                    StringUtilities.exit(0);
299                }
300            }
301
302            // Run if -run argument was specified.
303            if (_run) {
304                if (_printPDF) {
305                    // Need to set background
306                    PtolemyPreferences preferences = PtolemyPreferences
307                            .getPtolemyPreferencesWithinConfiguration(
308                                    _configuration);
309                    preferences.backgroundColor
310                            .setExpression("{1.0, 1.0, 1.0, 1.0}");
311
312                }
313                if (_run20x) {
314                    for (int i = 1; i <= 20; i++) {
315                        // Use Manager.execute()
316                        _runModels(false);
317                    }
318                } else {
319                    // Use Manager.startRun() and run the model in
320                    // a new thread.
321                    _runModels(true);
322                }
323
324                if (_exit) {
325                    // In vergil, this gets called in the
326                    // swing thread, which hangs everything
327                    // if we call waitForFinish() directly.
328                    // So instead, we create a new thread to
329                    // do it.
330                    Thread waitThread = new Thread() {
331                        @Override
332                        public void run() {
333                            waitForFinish();
334                            if (_printPDF) {
335                                try {
336                                    _printPDF();
337                                } catch (Exception ex) {
338                                    ex.printStackTrace();
339                                }
340                            }
341                            StringUtilities.exit(0);
342                        }
343                    };
344
345                    // Note that we start the thread here, which could
346                    // be risky when we subclass, since the thread will be
347                    // started before the subclass constructor finishes (FindBugs)
348                    waitThread.start();
349                }
350            } else {
351                if (_printPDF) {
352                    _printPDF();
353                }
354            }
355
356        } catch (Throwable ex) {
357            // Make sure that we do not eat the exception if there are
358            // problems parsing.  For example, "ptolemy -FOO bar bif.xml"
359            // will crash if bar is not a variable.  Instead, use
360            // "ptolemy -FOO \"bar\" bif.xml"
361            throwArgsException(ex, args);
362        }
363    }
364
365    ///////////////////////////////////////////////////////////////////
366    ////                         public methods                    ////
367
368    /** Close the model without saving or exiting.
369     *
370     *  <p>The caller of this method should be in the Swing Event Thread.
371     *  Typically, this is done with code like:</p>
372     *  <pre>
373     *   Runnable openModelAction = new Runnable() {
374     *       public void run() {
375     *           try {
376     *               ConfigurationApplication.closeModel(model[0])
377     *           } catch (Exception ex) {
378     *               throw new RuntimeException(ex);
379     *           }
380     *       }
381     *   };
382     *   SwingUtilities.invokeAndWait(openModelAction);
383     *  </pre>
384     *  <p>This method is primarily used for testing.</p>
385     *  @param model The TypedCompositeActor to be closed.
386     *  Typically, this comes from {@link #openModel(String)}.
387     *  @exception IllegalActionException If the model cannot be closed.
388     *  @see #openModel(String)
389     *  @exception NameDuplicationException If the model cannot be closed.
390     *  @see #openModel(String)
391     */
392    public static void closeModelWithoutSavingOrExiting(CompositeEntity model)
393            throws IllegalActionException, NameDuplicationException {
394        Effigy effigy = Configuration.findEffigy(model.toplevel());
395        // Avoid being prompted for save.
396        effigy.setModified(false);
397
398        // Avoid calling System.exit().
399        String previousPropertyValue = StringUtilities
400                .getProperty("ptolemy.ptII.doNotExit");
401        System.setProperty("ptolemy.ptII.doNotExit", "true");
402        try {
403            // FIXME: are all these necessary?
404            effigy.closeTableaux();
405
406            // Avoid a leaking Configuration.
407            // This might not be safe, if we had a WeakLinkedList, we
408            // could use make Configuration._configuration a WeakLinkedList.
409            // Or, perhaps effigy.closeTableaux() should do this.
410            Configuration configuration = (Configuration) Configuration
411                    .findEffigy(model.toplevel()).toplevel();
412            configuration.removeConfiguration(configuration);
413
414            model.setContainer(null);
415            MoMLParser.purgeAllModelRecords();
416        } finally {
417            System.setProperty("ptolemy.ptII.doNotExit", previousPropertyValue);
418        }
419    }
420
421    /** Return the array of possible configuration directories.
422     *  in basePath.
423     *  @param basePath The base path, typically "ptolemy/configs".
424     *  @return the possible configuration directories.
425     *  @exception IOException If the basePath cannot be found.
426     *  @exception URISyntaxException If the URI of the basePath is incorrect.
427     */
428    public static File[] configurationDirectories(String basePath)
429            throws IOException, URISyntaxException {
430        URI configurationURI = new URI(
431                specToURL(basePath).toExternalForm().replaceAll(" ", "%20"));
432        File configurationDirectory = new File(configurationURI);
433        ConfigurationFilenameFilter filter = new ConfigurationFilenameFilter();
434        File[] configurationDirectories = configurationDirectory
435                .listFiles(filter);
436        return configurationDirectories;
437    }
438
439    /** Return the full configuration directory or, if the full configuration
440     *  directory is not found, then the first configuration directory.
441     *  @return the possible configuration directories.
442     *  @exception IOException If the basePath cannot be found.
443     *  @exception URISyntaxException If the URI of the basePath is incorrect.
444     */
445    public static File configurationDirectoryFullOrFirst()
446            throws IOException, URISyntaxException {
447        File[] configurationDirectories = configurationDirectories(
448                "ptolemy/configs");
449        if (configurationDirectories.length < 1) {
450            throw new IOException(
451                    "Could not find any configurations in ptolemy/configs");
452        }
453        File configurationDirectory = configurationDirectories[0];
454        int i;
455        for (i = 0; i < configurationDirectories.length; i++) {
456            if (configurationDirectories[i].toString()
457                    .endsWith("configs/full")) {
458                configurationDirectory = configurationDirectories[i];
459            }
460        }
461        return configurationDirectory;
462    }
463
464    /** Reduce the count of executing models by one.  If the number of
465     *  executing models drops to zero, then notify threads that might
466     *  be waiting for this event.
467     *  @param manager The manager calling this method.
468     *  @param throwable The throwable being reported.
469     */
470    @Override
471    public synchronized void executionError(Manager manager,
472            Throwable throwable) {
473        _activeCount--;
474
475        if (_activeCount == 0) {
476            notifyAll();
477        }
478    }
479
480    /**  Reduce the count of executing models by one.  If the number of
481     *  executing models drops ot zero, then notify threads that might
482     *  be waiting for this event.
483     *  @param manager The manager calling this method.
484     */
485    @Override
486    public synchronized void executionFinished(Manager manager) {
487        _activeCount--;
488
489        if (_activeCount == 0) {
490            notifyAll();
491        }
492    }
493
494    /** Create a new instance of this application, passing it the
495     *  command-line arguments.
496     *
497     *  <p>For example to use the full configuration to open a model:</p>
498     *  <pre>
499     *  java -classpath $PTII ptolemy.actor.gui.ConfigurationApplication \
500     *     $PTII ptolemy/configs/full/configuration.xml \
501     *     $PTII/ptolemy/moml/demo/modulation.xml
502     *  </pre>
503     *
504     *  <p>However, it better to use {@link ptolemy.actor.gui.MoMLApplication},
505     *  which sets the native Java look and feel and uses a better error
506     *  handler, or to use {@link ptolemy.moml.MoMLSimpleApplication},
507     *  with non-graphical models. </p>
508     *
509     *  @param args The command-line arguments.
510     */
511    public static void main(String[] args) {
512        try {
513            new ConfigurationApplication(args);
514        } catch (Throwable throwable) {
515            MessageHandler.error("Command failed", throwable);
516            // Be sure to print the stack trace so that
517            // "$PTII/bin/moml -foo" prints something.
518            System.err.print(KernelException.stackTraceToString(throwable));
519            StringUtilities.exit(1);
520        }
521
522        // If the -test arg was set, then exit after 2 seconds.
523        if (_test) {
524            try {
525                Thread.sleep(2000);
526            } catch (InterruptedException e) {
527            }
528
529            StringUtilities.exit(0);
530        }
531    }
532
533    /** Do nothing.
534     *  @param manager The manager calling this method.
535     */
536    @Override
537    public void managerStateChanged(Manager manager) {
538    }
539
540    /** Return a list of the Ptolemy II models that were created by processing
541     *  the command-line arguments.
542     *  @return A list of instances of NamedObj.
543     */
544    public List<NamedObj> models() {
545        LinkedList<NamedObj> result = new LinkedList<NamedObj>();
546
547        if (_configuration == null) {
548            return result;
549        }
550
551        ModelDirectory directory = (ModelDirectory) _configuration
552                .getEntity(Configuration._DIRECTORY_NAME);
553        if (directory == null) {
554            throw new InternalErrorException("Failed to get the "
555                    + "model directory? This can happen "
556                    + "in a headless environment when the model attempts to "
557                    + "interact with the graphical display.  "
558                    + "It can also happen when the model fails to parse "
559                    + "because of a missing class.");
560        }
561        Iterator effigies = directory.entityList().iterator();
562
563        while (effigies.hasNext()) {
564            Effigy effigy = (Effigy) effigies.next();
565
566            if (effigy instanceof PtolemyEffigy) {
567                NamedObj model = ((PtolemyEffigy) effigy).getModel();
568                result.add(model);
569            }
570        }
571
572        return result;
573    }
574
575    /** Open a model and display it.
576     *
577     *  <p>The caller of this method should be in the Swing Event Thread.
578     *  Typically, this is done with code like:</p>
579     *  <pre>
580     *   Runnable openModelAction = new Runnable() {
581     *       public void run() {
582     *           try {
583     *               model[0] = ConfigurationApplication.openModel(modelFileName);
584     *           } catch (Exception ex) {
585     *               throw new RuntimeException(ex);
586     *           }
587     *       }
588     *   };
589     *   SwingUtilities.invokeAndWait(openModelAction);
590     *  </pre>
591     *  <p>This method is primarily used for testing.  To get the
592     *  ptolemy.vergil.basic.BasicGraphFrame from a model returned
593     *  by this method, see
594     *  ptolemy.vergil.basic.BasicGraphFrame.getBasicGraphFrame().</p>
595     *
596     *  @param modelFileName The pathname to the model.  Usually the
597     *  pathname starts with "$CLASSPATH".  The name of the top level
598     *  of the model must match the base name of the modelFileName.
599     *  Thus $CLASSPATH/Foo.xml should have a toplevel named "Foo".
600     *  If the name of the model and the name of the top level do
601     *  not match, then the last CompositeActor is returned.
602     *  @see #closeModelWithoutSavingOrExiting(CompositeEntity)
603     *  @return The model that was opened.
604     *  @exception Throwable If the model cannot be opened.
605     *  @deprecated Use #openModelOrEntity(String) instead and handle
606     *  the case where the modelFileName refers to a HTML or text file
607     *  or an interface diagram.
608     */
609    @Deprecated
610    public static TypedCompositeActor openModel(String modelFileName)
611            throws Throwable {
612        CompositeEntity model = openModelOrEntity(modelFileName);
613        if (!(model instanceof TypedCompositeActor)) {
614            System.out.println("While trying to open \"" + modelFileName
615                    + "\", openModelOrEntity() returned a "
616                    + (model == null ? "null" : model.getClass().getName())
617                    + ".  This can happen when opening a HTML or text file. ");
618            return null;
619        } else {
620            return (TypedCompositeActor) model;
621        }
622    }
623
624    /** Open a model and display it.
625     *
626     *  <p>The caller of this method should be in the Swing Event Thread.
627     *  Typically, this is done with code like:</p>
628     *  <pre>
629     *   Runnable openModelAction = new Runnable() {
630     *       public void run() {
631     *           try {
632     *               model[0] = ConfigurationApplication.openModelOrEntity(modelFileName);
633     *           } catch (Exception ex) {
634     *               throw new RuntimeException(ex);
635     *           }
636     *       }
637     *   };
638     *   SwingUtilities.invokeAndWait(openModelAction);
639     *  </pre>
640     *  <p>This method is primarily used for testing.  To get the
641     *  ptolemy.vergil.basic.BasicGraphFrame from a model returned
642     *  by this method, see
643     *  ptolemy.vergil.basic.BasicGraphFrame.getBasicGraphFrame().</p>
644     *
645     *  @param modelFileName The pathname to the model.  Usually the
646     *  pathname starts with "$CLASSPATH".  The name of the top level
647     *  of the model must match the base name of the modelFileName.
648     *  Thus $CLASSPATH/Foo.xml should have a toplevel named "Foo".
649     *  If the name of the model and the name of the top level do
650     *  not match, then the last CompositeActor is returned.
651     *  @see #closeModelWithoutSavingOrExiting(CompositeEntity)
652     *  @return The model that was opened.
653     *  @exception Throwable If the model cannot be opened.
654     */
655    public static CompositeEntity openModelOrEntity(String modelFileName)
656            throws Throwable {
657        CompositeEntity result = null;
658        try {
659            // We set the list of MoMLFilters to handle Backward Compatibility.
660            MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters());
661
662            // Convert the file name to a canonical file name so that
663            // this test may be run from any directory or from within Eclipse.
664            File canonicalModelFile = FileUtilities.nameToFile(modelFileName,
665                    null);
666            if (canonicalModelFile == null) {
667                throw new IOException(
668                        "Could not find \"" + modelFileName + "\".");
669            }
670            String canonicalModelFileName = canonicalModelFile
671                    .getCanonicalPath();
672
673            // Search for the full configuration or the first configuration.
674
675            // This is needed for the exportHTML tests under CapeCode
676            // because full/configuration.xml does not exist, but
677            // capecode/configuration.xml does.
678
679            File configurationDirectory = configurationDirectoryFullOrFirst();
680
681            // FIXME: are we in the right thread?
682            ConfigurationApplication application = new ConfigurationApplication(
683                    new String[] {
684                            // Need to display a frame or Kieler fails.
685                            //"ptolemy/actor/gui/test/testConfiguration.xml",
686                            configurationDirectory.getCanonicalPath()
687                                    + "/configuration.xml",
688                            canonicalModelFileName });
689
690            // Find the first CompositeEntity whos name matches
691            // the basename of the file, skipping the
692            // Configuration etc.
693
694            // Not all modelFileNames have dots in them?
695            int indexOfDot = modelFileName.lastIndexOf('.');
696            if (indexOfDot == -1) {
697                indexOfDot = modelFileName.length() - 1;
698            }
699
700            // The basename of the model.
701            String baseName = modelFileName.substring(
702                    modelFileName.lastIndexOf(File.separator) + 1, indexOfDot);
703            NamedObj model = null;
704            Iterator models = application.models().iterator();
705            while (models.hasNext()) {
706                model = (NamedObj) models.next();
707                if (model instanceof CompositeEntity
708                        && !(model instanceof Configuration)) {
709                    result = (CompositeEntity) model;
710                    if (model.getName().equals(baseName)) {
711                        break;
712                    }
713                }
714            }
715        } catch (Throwable throwable) {
716            throwable.printStackTrace();
717            throw throwable;
718        }
719        return result;
720    }
721
722    /** Read a Configuration from the URL given by the specified string.
723     *  The URL may absolute, or relative to the Ptolemy II tree root,
724     *  or in the classpath.  To convert a String to a URL suitable for
725     *  use by this method, call specToURL(String).
726     *  <p>If there is an _applicationInitializer parameter, then
727     *  instantiate the class named by that parameter.  The
728     *  _applicationInitializer parameter contains a string that names
729     *  a class to be initialized.
730     *  <p>If the configuration has already been read in, then the old
731     *  configuration will be deleted.  Note that this may exit the application.
732     *  @param specificationURL A string describing a URL.
733     *  @return A configuration.
734     *  @exception Exception If the configuration cannot be opened, or
735     *   if the contents of the URL is not a configuration.
736     */
737    public static Configuration readConfiguration(URL specificationURL)
738            throws Exception {
739        if (_initialSpecificationURI == null) {
740            _initialSpecificationURI = specificationURL.toURI();
741        }
742        MoMLParser parser = new MoMLParser();
743        parser.reset();
744
745        Configuration configuration = null;
746        Exception cause = null;
747        try {
748            configuration = (Configuration) parser.parse(specificationURL,
749                    specificationURL);
750        } catch (Exception ex) {
751            cause = ex;
752            ex.printStackTrace();
753        }
754
755        if (configuration == null) {
756            NullPointerException exception = new NullPointerException(
757                    "Failed to find configuration in " + specificationURL);
758            if (cause != null) {
759                exception.initCause(cause);
760            }
761            throw exception;
762        }
763        // If the toplevel model is a configuration containing a directory,
764        // then create an effigy for the configuration itself, and put it
765        // in the directory.
766        ComponentEntity directory = configuration.getDirectory();
767
768        if (directory != null) {
769            PtolemyEffigy effigy = null;
770            try {
771                effigy = new PtolemyEffigy((ModelDirectory) directory,
772                        configuration.getName());
773                // Mark this to be a system effigy so it is not deleted
774                // when an effigy it contains is deleted.
775                effigy.setSystemEffigy(true);
776            } catch (NameDuplicationException ex) {
777                // Try deleting the old configuration
778                PtolemyEffigy oldEffigy = (PtolemyEffigy) ((ModelDirectory) directory)
779                        .getEntity(configuration.getName());
780                oldEffigy.setContainer(null);
781                effigy = new PtolemyEffigy((ModelDirectory) directory,
782                        configuration.getName());
783            }
784
785            effigy.setModel(configuration);
786            effigy.identifier.setExpression(specificationURL.toExternalForm());
787        }
788
789        // If there is an _applicationInitializer parameter, then
790        // construct it.  The _applicationInitializer parameter contains
791        // a string that names a class to be initialized.
792        StringParameter applicationInitializerParameter = (StringParameter) configuration
793                .getAttribute("_applicationInitializer", Parameter.class);
794
795        if (applicationInitializerParameter != null) {
796            String applicationInitializerClassName = applicationInitializerParameter
797                    .stringValue();
798            try {
799                Class applicationInitializer = Class
800                        .forName(applicationInitializerClassName);
801                applicationInitializer.newInstance();
802            } catch (Throwable throwable) {
803                throw new Exception(
804                        "Failed to call application initializer " + "class \""
805                                + applicationInitializerClassName
806                                + "\".  Perhaps the configuration file \""
807                                + specificationURL + "\" has a problem?",
808                        throwable);
809            }
810        }
811
812        return configuration;
813    }
814
815    /** Start the models running, each in a new thread, then return.
816     *  @exception KernelException If the manager throws it.
817     */
818    public void runModels() throws KernelException {
819        _runModels(true);
820    }
821
822    /** Given the name of a file or a URL, convert it to a URL.
823     *  This first attempts to do that directly by invoking a URL constructor.
824     *  If that fails, then it tries to interpret the spec as a file name
825     *  on the local file system.  If that fails, then it tries to interpret
826     *  the spec as a resource accessible to the class loader, which uses
827     *  the classpath to find the resource.  If that fails, then it throws
828     *  an exception.  The specification can give a file name relative to
829     *  current working directory, or the directory in which this application
830     *  is started up.
831     *  @param spec The specification.
832     *  @return the URL.
833     *  @exception IOException If it cannot convert the specification to
834     *   a URL.
835     */
836    public static URL specToURL(String spec) throws IOException {
837        // FIXME: There is a bit of a design flaw here because
838        // we open a stream to the url (which is probably expensive)
839        // and then close it.  The reason for opening the stream
840        // is that we want to be sure that the URL is valid,
841        // and if it is not, we check the local file system
842        // and the classpath.
843        // One solution would be to have a method that returned a
844        // URLConnection because we can open a stream with a
845        // URLConnection and still get the original URL if necessary
846        URL specURL = null;
847
848        try {
849            // First argument is null because we are only
850            // processing absolute URLs this way.  Relative
851            // URLs are opened as ordinary files.
852            specURL = new URL(null, spec);
853            // Make sure that the specURL actually exists
854            InputStream urlStream = specURL.openStream();
855            urlStream.close();
856            return specURL;
857        } catch (Exception ex) {
858            try {
859                // Try as a regular file
860                File file = new File(spec);
861
862                // Oddly, under Windows file.exists() might return even
863                // though the file does not exist if we changed user.dir.
864                // See
865                // http://forum.java.sun.com/thread.jsp?forum=31&thread=328939
866                // One hack is to convert to an absolute path first
867                File absoluteFile = file.getAbsoluteFile();
868
869                try {
870                    if (!absoluteFile.exists()) {
871                        throw new IOException(
872                                "File '" + absoluteFile + "' does not exist.");
873                    }
874                } catch (java.security.AccessControlException accessControl) {
875                    IOException exception = new IOException(
876                            "AccessControlException while " + "trying to read '"
877                                    + absoluteFile + "'");
878
879                    // IOException does not have a cause argument constructor.
880                    exception.initCause(accessControl);
881                    throw exception;
882                }
883
884                specURL = absoluteFile.getCanonicalFile().toURI().toURL();
885                //InputStream urlStream = specURL.openStream();
886                //urlStream.close();
887                return specURL;
888            } catch (Throwable throwable) {
889                try {
890                    // Try one last thing, using the classpath.
891                    // Need a class context, and this is a static method, so...
892                    // we can't use this.getClass().getClassLoader()
893                    // NOTE: There doesn't seem to be any way to convert
894                    // this a canonical name, so if a model is opened this
895                    // way, and then later opened as a file, the model
896                    // directory will think it has two different files.
897                    //Class refClass = Class.forName(
898                    //        "ptolemy.kernel.util.NamedObj");
899                    //specURL = refClass.getClassLoader().getResource(spec);
900                    // This works in Web Start, see
901                    // http://download.oracle.com/javase/1.5.0/docs/guide/javaws/developersguide/faq.html#211
902
903                    // Unfortunately, Thread.currentThread().getContextClassLoader() can
904                    // return null.  This happened when
905                    // $CLASSPATH/ptolemy/actor/lib/vertx/demo/TokenTransmissionTime/Sender.xml
906                    // failed and subsequent calls to openModelOrEntity() would
907                    // fail because the configuration could not be found.
908                    // So, we have our own getResource() that handles this.
909                    specURL = ClassUtilities.getResource(spec);
910
911                    if (specURL == null) {
912                        try {
913                            specURL = ClassUtilities.jarURLEntryResource(spec);
914                        } catch (IOException ex4) {
915                            // Ignore
916                        }
917                        if (specURL == null) {
918                            // If we have a jar URL, convert spaces to %20
919                            // so as to avoid multiple windows with the
920                            // same file.  Web Start needs this if the Web
921                            // Start cache is in a directory that has
922                            // spaces in the path, which is the default
923                            // under Windows.
924                            specURL = JNLPUtilities
925                                    .canonicalizeJarURL(new URL(spec));
926                            if (specURL == null) {
927                                throw new Exception(
928                                        "JNLPUtilities.canonicalizeJarURL(new URL(\""
929                                                + spec + "\")) returned null.");
930                            }
931                        }
932                    }
933
934                    // Verify that it can be opened
935                    InputStream urlStream = specURL.openStream();
936                    urlStream.close();
937                    return specURL;
938                } catch (Exception ex3) {
939                    // Use a very verbose message in case opening
940                    // the configuration fails under Web Start.
941                    // Without this error message, users will
942                    // have no hope of telling us why Web Start failed.
943                    IOException exception = new IOException("File not found: '"
944                            + spec + "'\n caused by:\n" + ex + "\n AND:\n"
945                            + throwable + "\n AND:\n" + ex3);
946
947                    // IOException does not have a cause argument
948                    exception.initCause(ex3);
949                    throw exception;
950                }
951            }
952        }
953    }
954
955    /** Throw an exception that includes the elements of the args parameter.
956     *  @param cause The throwable that caused the problem.
957     *  @param args An array of Strings.
958     *  @exception Exception Always thrown
959     */
960    public static void throwArgsException(Throwable cause, String[] args)
961            throws Exception {
962
963        // Accumulate the arguments into a StringBuffer
964        StringBuffer argsStringBuffer = new StringBuffer();
965
966        try {
967            for (String arg : args) {
968                if (argsStringBuffer.length() > 0) {
969                    argsStringBuffer.append(" ");
970                }
971
972                argsStringBuffer.append(arg);
973            }
974        } catch (Throwable throwable) {
975            //Ignored
976        }
977
978        // Make sure we throw an exception if one is caught.
979        // If we don't, then running vergil -foo will just exit.
980        throw new Exception(
981                "Failed to parse \"" + argsStringBuffer.toString() + "\"",
982                cause);
983    }
984
985    /** Wait for all executing runs to finish, then return.
986     */
987    public synchronized void waitForFinish() {
988        while (_activeCount > 0) {
989            try {
990                wait();
991            } catch (InterruptedException ex) {
992                break;
993            }
994        }
995    }
996
997    ///////////////////////////////////////////////////////////////////
998    ////                         protected methods                 ////
999
1000    /**Get the number of models that are active.
1001     * @return The number of active models.
1002     * @see #setActiveCount
1003     */
1004    public int getActiveCount() {
1005        return _activeCount;
1006    }
1007
1008    /**Set the number of active models.
1009     * @param _activeCount The number of active models.
1010     * @see #getActiveCount
1011     */
1012    public void setActiveCount(int _activeCount) {
1013        this._activeCount = _activeCount;
1014    }
1015
1016    /** Return a string summarizing the command-line arguments,
1017     *  including any configuration directories in a base path,
1018     *  typically "ptolemy/configs".
1019     *  Some subclasses of this class use configurations from ptolemy/configs.
1020     *  For example, if ptolemy/configs/full/configuration.xml exists
1021     *  then -full is a legitimate argument.
1022     *  @param commandTemplate The form of the command line
1023     *  @param commandOptions Command-line options that take arguments.
1024     *  @param commandFlags An array of Strings that list command-line
1025     *  options that are either present or not.
1026     *  @return A usage string.
1027     *  @see ptolemy.util.StringUtilities#usageString(String, String [][], String [])
1028     */
1029    protected String _configurationUsage(String commandTemplate,
1030            String[][] commandOptions, String[] commandFlags) {
1031        String[][] commandFlagsWithDescriptions = new String[commandFlags.length][2];
1032        for (int i = 0; i < commandFlags.length; i++) {
1033            commandFlagsWithDescriptions[i][0] = commandFlags[i];
1034            commandFlagsWithDescriptions[i][1] = "";
1035        }
1036        return _configurationUsage(commandTemplate, commandOptions,
1037                commandFlagsWithDescriptions);
1038    }
1039
1040    /** Return a string summarizing the command-line arguments,
1041     *  including any configuration directories in a base path,
1042     *  typically "ptolemy/configs".
1043     *  Some subclasses of this class use configurations from ptolemy/configs.
1044     *  For example, if ptolemy/configs/full/configuration.xml exists
1045     *  then -full is a legitimate argument.
1046     *  @param commandTemplate The form of the command line
1047     *  @param commandOptions Command-line options that take arguments.
1048     *  @param commandFlagsWithDescriptions A 2xM array of Strings that list
1049     *  command-line options that are either present or not and a description
1050     *  of what the command line option does.
1051     *  @return A usage string.
1052     *  @see ptolemy.util.StringUtilities#usageString(String, String [][], String [][])
1053     */
1054    protected String _configurationUsage(String commandTemplate,
1055            String[][] commandOptions,
1056            String[][] commandFlagsWithDescriptions) {
1057        StringBuffer result = new StringBuffer("Usage: " + commandTemplate
1058                + "\n\n" + "Options that take values:\n");
1059        int i;
1060
1061        // Print any command options from this class first
1062        for (i = 0; i < _commandOptions.length; i++) {
1063            result.append(" " + _commandOptions[i][0] + " "
1064                    + _commandOptions[i][1] + "\n");
1065        }
1066
1067        for (i = 0; i < commandOptions.length; i++) {
1068            result.append(" " + commandOptions[i][0] + " "
1069                    + commandOptions[i][1] + "\n");
1070        }
1071
1072        result.append("\nFlags (do not take values):\n");
1073
1074        // Print any command flags from this class first
1075        for (i = 0; i < _commandFlagsWithDescriptions.length; i++) {
1076            result.append(" " + _commandFlagsWithDescriptions[i][0] + "\t"
1077                    + _commandFlagsWithDescriptions[i][1] + "\n");
1078        }
1079        for (i = 0; i < commandFlagsWithDescriptions.length; i++) {
1080            result.append(" " + commandFlagsWithDescriptions[i][0] + "\t"
1081                    + commandFlagsWithDescriptions[i][1] + "\n");
1082        }
1083
1084        try {
1085            // Look for configuration directories in _basePath
1086            // This will likely fail if ptolemy/configs is in a jar file
1087            // We use a URI here so that we cause call File(URI).
1088            File[] configurationDirectories = configurationDirectories(
1089                    _basePath);
1090
1091            if (configurationDirectories != null) {
1092                result.append("\nThe following (mutually exclusive) flags "
1093                        + "specify alternative configurations:\n");
1094
1095                for (i = 0; i < configurationDirectories.length; i++) {
1096                    String configurationName = configurationDirectories[i]
1097                            .getName();
1098                    result.append(" -" + configurationName);
1099
1100                    // Pad out to a fixed number of spaces to get good alignment.
1101                    for (int j = configurationName.length(); j < 20; j++) {
1102                        result.append(" ");
1103                    }
1104
1105                    String configurationFileName = configurationDirectories[i]
1106                            + File.separator + "configuration.xml";
1107
1108                    boolean printDefaultConfigurationMessage = true;
1109
1110                    try {
1111                        // Read the configuration and get the top level docs
1112                        // FIXME: this will not work if the configs are
1113                        // in jar files.
1114                        // FIXME: Skip jxta, since it starts up the jxta config
1115                        // tools.
1116                        if (!configurationName.equals("jxta")) {
1117                            URL specificationURL = specToURL(
1118                                    configurationFileName);
1119                            Configuration configuration;
1120                            // URL.equals() is very expensive so we convert to a URI first  See:
1121                            //http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html
1122                            if (specificationURL.toURI()
1123                                    .equals(_initialSpecificationURI)) {
1124                                // Avoid rereading the configuration, which will result
1125                                // in the old configuration being removed, which exits the app.
1126                                configuration = _configuration;
1127                            } else {
1128                                ErrorHandler errorHandler = MoMLParser
1129                                        .getErrorHandler();
1130
1131                                // Read the configuration. If there is an
1132                                // error, ignore the error, but don't print
1133                                // usage for that configuration
1134                                try {
1135                                    MoMLParser.setErrorHandler(
1136                                            new IgnoreErrorHandler());
1137                                    configuration = readConfiguration(
1138                                            specificationURL);
1139                                } finally {
1140                                    MoMLParser.setErrorHandler(errorHandler);
1141                                }
1142
1143                                if (configuration != null
1144                                        && configuration
1145                                                .getAttribute("_doc") != null
1146                                        && configuration.getAttribute(
1147                                                "_doc") instanceof Documentation) {
1148                                    Documentation doc = (Documentation) configuration
1149                                            .getAttribute("_doc");
1150                                    result.append("\t\t"
1151                                            + doc.getValueAsString() + "\n");
1152                                    printDefaultConfigurationMessage = false;
1153                                }
1154                            }
1155                        }
1156                    } catch (Exception ex) {
1157                        //result.append("\tCould not read configuration"
1158                        //    + "\n" + ex);
1159                        //ex.printStackTrace();
1160                    }
1161
1162                    if (printDefaultConfigurationMessage) {
1163                        result.append(
1164                                "\t\tuses " + configurationFileName + "\n");
1165                    }
1166                }
1167            }
1168        } catch (Exception ex) {
1169            result.append("Warning: Failed to find configuration(s) in '"
1170                    + _basePath + "': " + ex);
1171        }
1172
1173        return result.toString();
1174    }
1175
1176    /** Perform any application specific initialization.
1177     *  In this base class, do nothing.  Derived classes
1178     *  can perform initialization.
1179     *  This method is called by early in the constructor,
1180     *  so the object may not be completely constructed, so
1181     *  derived classes should not access fields from a
1182     *  parent class.
1183     */
1184    protected void _initializeApplication() {
1185        // Do nothing.
1186    }
1187
1188    /** Return a default Configuration, or null to do without one.
1189     *  This configuration will be created before any command-line arguments
1190     *  are processed.  If there are no command-line arguments, then
1191     *  the default configuration is given by _createEmptyConfiguration()
1192     *  instead.  This method merges the compile-time configuration file
1193     *  values from {@link ptolemy.util.StringUtilities#mergePropertiesFile()}.
1194     *  Subclasses should call
1195     * {@link ptolemy.actor.gui.PtolemyPreferences#setDefaultPreferences(Configuration)}.
1196     *  @return null
1197     *  @exception Exception Thrown in derived classes if the default
1198     *   configuration cannot be opened.
1199     */
1200    protected Configuration _createDefaultConfiguration() throws Exception {
1201        // Load the properties file
1202        try {
1203            StringUtilities.mergePropertiesFile();
1204        } catch (Throwable throwable) {
1205            // FIXME: this should be logged, not ignored
1206            // Ignore the exception, it clutters the start up.
1207        }
1208
1209        return null;
1210    }
1211
1212    /** Return a default Configuration to use when there are no command-line
1213     *  arguments, or null to do without one.  This base class returns the
1214     *  configuration returned by _createDefaultConfiguration().
1215     *  @return null
1216     *  @exception Exception Thrown in derived classes if the empty
1217     *   configuration cannot be opened.
1218     */
1219    protected Configuration _createEmptyConfiguration() throws Exception {
1220        return _createDefaultConfiguration();
1221    }
1222
1223    /** Open the specified Ptolemy II model. If a model already has
1224     *  open tableaux, then put those in the foreground and
1225     *  return the first one.  Otherwise, create a new tableau and if
1226     *  necessary, a new effigy.  Unless there is a more natural container
1227     *  for the effigy (e.g. it is a hierarchical model), then if a new
1228     *  effigy is created, it is put into the directory of the configuration.
1229     *  Any new tableau created will be contained by that effigy.
1230     *  @param entity The model.
1231     *  @return The tableau that is created, or the first one found,
1232     *   or null if none is created or found.
1233     *  @exception IllegalActionException If constructing an effigy or tableau
1234     *   fails.
1235     *  @exception NameDuplicationException If a name conflict occurs (this
1236     *   should not be thrown).
1237     */
1238    protected Tableau _openModel(NamedObj entity)
1239            throws IllegalActionException, NameDuplicationException {
1240        return _configuration.openModel(entity);
1241    }
1242
1243    /** Open the specified URL.
1244     *  If a model with the specified identifier is present in the directory,
1245     *  then find all the tableaux of that model and make them
1246     *  visible; otherwise, read a model from the specified URL <i>in</i>
1247     *  and create a default tableau for the model and add the tableau
1248     *  to this directory.
1249     *  @param base The base for relative file references, or null if
1250     *   there are no relative file references.
1251     *  @param in The input URL.
1252     *  @param identifier The identifier that uniquely identifies the model.
1253     *  @return The tableau that is created, or null if none.
1254     *  @exception Exception If the URL cannot be read.
1255     */
1256    protected Tableau _openModel(URL base, URL in, String identifier)
1257            throws Exception {
1258        return _configuration.openModel(base, in, identifier);
1259    }
1260
1261    /** Parse a command-line argument.
1262     *  @param arg The command-line argument to be parsed.
1263     *  @return True if the argument is understood, false otherwise.
1264     *  @exception Exception If something goes wrong.
1265     */
1266    protected boolean _parseArg(String arg) throws Exception {
1267        if (arg.equals("-class")) {
1268            _expectingClass = true;
1269        } else if (arg.equals("-exit")) {
1270            _exit = true;
1271        } else if (arg.equals("-help")) {
1272            System.out.println(_usage());
1273
1274            // NOTE: This means the test suites cannot test -help
1275            StringUtilities.exit(0);
1276        } else if (arg.equals("-printPDF")) {
1277            _printPDF = true;
1278        } else if (arg.equals("-run")) {
1279            _run = true;
1280        } else if (arg.equals("-runThenExit")) {
1281            _run = true;
1282            _exit = true;
1283        } else if (arg.equals("-run20x")) {
1284            _run = true;
1285            _run20x = true;
1286            _exit = true;
1287            Manager.minimumStatisticsTime = 1;
1288        } else if (arg.equals("-statistics")) {
1289            _statistics = true;
1290        } else if (arg.equals("-test")) {
1291            _test = true;
1292        } else if (arg.equals("-version")) {
1293            System.out.println("Version "
1294                    + VersionAttribute.CURRENT_VERSION.getExpression()
1295                    + ", Build $Id$");
1296
1297            // NOTE: This means the test suites cannot test -version
1298            StringUtilities.exit(0);
1299        } else if (arg.equals("")) {
1300            // Ignore blank argument.
1301        } else {
1302            if (_expectingClass) {
1303                // $PTII/bin/ptolemy -class ptolemy.domains.sdf.demo.Butterfly.Butterfly
1304                // Previous argument was -class
1305                _expectingClass = false;
1306
1307                // Create the class.
1308                Class<?> newClass = Class.forName(arg);
1309
1310                // Instantiate the specified class in a new workspace.
1311                Workspace workspace = new Workspace();
1312
1313                //Workspace workspace = _configuration.workspace();
1314                // Get the constructor that takes a Workspace argument.
1315                Class<?>[] argTypes = new Class[1];
1316                argTypes[0] = workspace.getClass();
1317
1318                Constructor<?> constructor = newClass.getConstructor(argTypes);
1319
1320                Object[] args = new Object[1];
1321                args[0] = workspace;
1322
1323                NamedObj newModel = (NamedObj) constructor.newInstance(args);
1324
1325                // If there is a configuration, then create an effigy
1326                // for the class, and enter it in the directory.
1327                System.out.println("-class: _configuration: " + _configuration);
1328
1329                if (_configuration != null) {
1330                    _openModel(newModel);
1331
1332                    // FIXME: we can probably delete this code.
1333                    //                     // Create an effigy for the model.
1334                    //                     PtolemyEffigy effigy = new PtolemyEffigy(_configuration
1335                    //                             .workspace());
1336                    //                     effigy.setModel(newModel);
1337                    //                     System.out.println("-class: effigy: " + effigy);
1338                    //                     ModelDirectory directory = (ModelDirectory) _configuration
1339                    //                         .getDirectory();
1340                    //                     // Can't use names with dots, so we substitute.
1341                    //                     String safeName =
1342                    //                         StringUtilities.substitute(arg, ".","_" );
1343                    //                     effigy.setName(safeName);
1344                    //                     if (directory != null) {
1345                    //                         if (directory.getEntity(safeName) != null) {
1346                    //                             // Name is already taken.
1347                    //                             int count = 2;
1348                    //                             String newName = safeName + " " + count;
1349                    //                             while (directory.getEntity(newName) != null) {
1350                    //                                 count++;
1351                    //                             }
1352                    //                             effigy.setName(newName);
1353                    //                         }
1354                    //                     }
1355                    //                     effigy.setContainer(directory);
1356                } else {
1357                    System.err.println("No configuration found.");
1358                    throw new IllegalActionException(newModel,
1359                            "No configuration found.");
1360                }
1361            } else {
1362                if (!arg.startsWith("-")) {
1363                    // Assume the argument is a file name or URL.
1364                    // Attempt to read it.
1365                    URL inURL;
1366                    try {
1367                        inURL = specToURL(arg);
1368                    } catch (IOException ex) {
1369                        try {
1370                            // Create a File and get the URL so that commands like
1371                            // $PTII/bin/vergil $PTII/doc/index.htm#in_browser work.
1372                            File inFile = new File(arg);
1373                            inURL = inFile.toURI().toURL();
1374                        } catch (Throwable throwable) {
1375                            if (StringUtilities.inApplet()) {
1376                                inURL = new URL(arg);
1377                            } else {
1378                                // FIXME: This is a fall back for relative filenames,
1379                                // I'm not sure if it will ever be called.
1380                                inURL = new URL(new URL("file://./"), arg);
1381                            }
1382                        }
1383                    }
1384
1385                    // Strangely, the XmlParser does not want as base the
1386                    // directory containing the file, but rather the
1387                    // file itself.
1388                    URL base = inURL;
1389
1390                    // If a configuration has been found, then
1391                    // defer to it to read the model.  Otherwise,
1392                    // assume the file is an XML file.
1393                    if (_configuration != null) {
1394                        ModelDirectory directory = _configuration
1395                                .getDirectory();
1396                        if (directory == null) {
1397                            throw new InternalErrorException(
1398                                    "No model directory!");
1399                        }
1400
1401                        String key = inURL.toExternalForm();
1402
1403                        //long startTime = (new Date()).getTime();
1404                        // Now defer to the model reader.
1405                        /*Tableau tableau = */_openModel(base, inURL, key);
1406
1407                        // FIXME: If the -run option was given, then start a run.
1408                        // FIXME: If the -fullscreen option was given, open full screen.
1409                        //System.out.println("Model open done: " +
1410                        //                   Manager.timeAndMemory(startTime));
1411                    } else {
1412                        // No configuration has been encountered.
1413                        // Assume this is a MoML file, and open it.
1414                        _parser.reset();
1415
1416                        try {
1417                            /*NamedObj toplevel = _parser.parse(base, inURL);
1418
1419                            if (toplevel instanceof Configuration) {
1420                                _configuration = (Configuration) toplevel;
1421                            }*/
1422
1423                            /*
1424                            If configuration is not set by the readConfiguration()
1425                            method, the _applicationInitializer is never called
1426                            which, for Kepler, results in no subsystems getting
1427                            loaded.  The code here is almost identical to that
1428                            in readConfiguration except for the loading of
1429                            _applicationInitializer.
1430                            -chad
1431                             */
1432                            _configuration = readConfiguration(inURL);
1433
1434                        } catch (Exception ex) {
1435                            // Unfortunately, java.util.zip.ZipException
1436                            // does not include the file name.
1437                            // If inURL is a jarURL check for %20
1438                            String detailMessage = "";
1439
1440                            try {
1441                                if (inURL.toString().indexOf("!/") != -1
1442                                        && inURL.toString()
1443                                                .indexOf("%20") != -1) {
1444                                    detailMessage = " The URL contains "
1445                                            + "'!/', so it may be a jar "
1446                                            + "URL, and jar URLs cannot contain "
1447                                            + "%20. This might happen if the "
1448                                            + "pathname to the jnlp file had a "
1449                                            + "space in it";
1450                                }
1451                            } catch (Throwable throwable) {
1452                                // Ignored
1453                            }
1454
1455                            throw new Exception("Failed to parse '" + inURL
1456                                    + "'" + detailMessage, ex);
1457                        }
1458                    }
1459                } else {
1460                    // Argument not recognized.
1461                    return false;
1462                }
1463            }
1464        }
1465
1466        return true;
1467    }
1468
1469    /** Parse the command-line arguments.
1470     *  @param args The command-line arguments to be parsed.
1471     *  @exception Exception If an argument is not understood or triggers
1472     *   an error.
1473     */
1474    protected void _parseArgs(String[] args) throws Exception {
1475        if (args.length > 0) {
1476            _configuration = _createDefaultConfiguration();
1477        } else {
1478            _configuration = _createEmptyConfiguration();
1479        }
1480
1481        for (int i = 0; i < args.length; i++) {
1482            String arg = args[i];
1483
1484            if (_parseArg(arg) == false) {
1485                if (arg.trim().startsWith("-")) {
1486                    if (i >= args.length - 1) {
1487                        throw new IllegalActionException(
1488                                "Cannot set " + "parameter " + arg
1489                                        + " when no value is " + "given.");
1490                    }
1491
1492                    // Save in case this is a parameter name and value.
1493                    _parameterNames.add(arg.substring(1));
1494                    _parameterValues.add(args[i + 1]);
1495                    i++;
1496                } else {
1497                    // Unrecognized option.
1498                    throw new IllegalActionException(
1499                            "Unrecognized option: " + arg);
1500                }
1501            }
1502        }
1503
1504        if (_expectingClass) {
1505            throw new IllegalActionException("Missing classname.");
1506        }
1507
1508        // Check saved options to see whether any is setting an attribute.
1509        Iterator names = _parameterNames.iterator();
1510        Iterator values = _parameterValues.iterator();
1511
1512        while (names.hasNext() && values.hasNext()) {
1513            String name = (String) names.next();
1514            String value = (String) values.next();
1515
1516            boolean match = false;
1517            ModelDirectory directory = _configuration.getDirectory();
1518
1519            if (directory == null) {
1520                throw new InternalErrorException("No model directory!");
1521            }
1522
1523            Iterator effigies = directory.entityList(Effigy.class).iterator();
1524
1525            while (effigies.hasNext()) {
1526                Effigy effigy = (Effigy) effigies.next();
1527
1528                if (effigy instanceof PtolemyEffigy) {
1529                    NamedObj model = ((PtolemyEffigy) effigy).getModel();
1530
1531                    // System.out.println("model = " + model.getFullName());
1532                    Attribute attribute = model.getAttribute(name);
1533
1534                    if (attribute instanceof Settable) {
1535                        match = true;
1536
1537                        // Use a MoMLChangeRequest so that visual rendition (if
1538                        // any) is updated and listeners are notified.
1539                        String moml = "<property name=\"" + name + "\" value=\""
1540                                + value + "\"/>";
1541                        MoMLChangeRequest request = new MoMLChangeRequest(this,
1542                                model, moml);
1543                        model.requestChange(request);
1544
1545                        /* Formerly (before the change request):
1546                         ((Settable)attribute).setExpression(value);
1547                         if (attribute instanceof Variable) {
1548                         // Force evaluation so that listeners are notified.
1549                         ((Variable)attribute).getToken();
1550                         }
1551                         */
1552                    }
1553
1554                    if (model instanceof CompositeActor) {
1555                        Director director = ((CompositeActor) model)
1556                                .getDirector();
1557
1558                        if (director != null) {
1559                            attribute = director.getAttribute(name);
1560
1561                            if (attribute instanceof Settable) {
1562                                match = true;
1563
1564                                // Use a MoMLChangeRequest so that visual rendition (if
1565                                // any) is updated and listeners are notified.
1566                                String moml = "<property name=\"" + name
1567                                        + "\" value=\"" + value + "\"/>";
1568                                MoMLChangeRequest request = new MoMLChangeRequest(
1569                                        this, director, moml);
1570                                director.requestChange(request);
1571
1572                                /* Formerly (before change request):
1573                                 ((Settable)attribute).setExpression(value);
1574                                 if (attribute instanceof Variable) {
1575                                 // Force evaluation so that listeners
1576                                 // are notified.
1577                                 ((Variable)attribute).getToken();
1578                                 }
1579                                 */
1580                            }
1581                        }
1582                    }
1583                }
1584            }
1585
1586            if (!match) {
1587                // Unrecognized option.
1588                throw new IllegalActionException("Unrecognized option: "
1589                        + "No parameter exists with name " + name);
1590            }
1591        }
1592
1593        // If the default configuration contains any Tableaux,
1594        // then we show them now.  This is deferred until now because
1595        // how they are shown may depend on command-line arguments
1596        // and/or parameters in some MoML file that is read.
1597        if (_configuration == null) {
1598            throw new IllegalActionException("No configuration provided.");
1599        }
1600
1601        _configuration.showAll();
1602    }
1603
1604    /** Print each effigy to the first printer with the string "PDF"
1605     *  in the name.  For this to work, the frame associated with the
1606     *  tableau must implement Printable or Pageable.  As a side
1607     *  effect, for better printing, the background color is set to
1608     *  white.
1609     *  @exception Exception If a printer with the string "PDF"
1610     *  cannot be found or if the job cannot be set to the PDF print
1611     *  service or if there is another problem printing.
1612     */
1613    protected void _printPDF() throws Exception {
1614        if (_configuration == null) {
1615            System.out.println("_printPDF: no configuration?");
1616            return;
1617        }
1618        ModelDirectory directory = _configuration.getDirectory();
1619        Iterator effigies = directory.entityList().iterator();
1620
1621        while (effigies.hasNext()) {
1622            Effigy effigy = (Effigy) effigies.next();
1623            Iterator tableaux = effigy.entityList(Tableau.class).iterator();
1624            while (tableaux.hasNext()) {
1625                Tableau tableau = (Tableau) tableaux.next();
1626                JFrame frame = tableau.getFrame();
1627                if (frame instanceof TableauFrame) {
1628                    // FIXME: lamely, we skip by the configuration directory and UserLibrary by name?
1629                    if (!tableau.getFullName().equals(
1630                            ".configuration.directory.configuration.graphTableau")
1631                            && !tableau.getFullName().equals(
1632                                    ".configuration.directory.UserLibrary.graphTableau")) {
1633                        try {
1634                            // Set the background to white
1635
1636                            //frame.setBackground(java.awt.Color.WHITE);
1637                            //((ptolemy.vergil.basic.BasicGraphFrame)frame).getJGraph().getCanvasPane().getCanvas().setBackground(java.awt.Color.WHITE);
1638                            PtolemyPreferences preferences = PtolemyPreferences
1639                                    .getPtolemyPreferencesWithinConfiguration(
1640                                            _configuration);
1641                            // Coverity Scan suggests avoiding a NPE here.
1642                            if (preferences == null) {
1643                                throw new InternalErrorException(_configuration,
1644                                        null,
1645                                        "Could not get PtolemyPreferences?"
1646                                                + "  Perhaps \""
1647                                                + PtolemyPreferences.PREFERENCES_WITHIN_CONFIGURATION
1648                                                + "\" could not be read in the configuration?");
1649                            } else {
1650                                preferences.backgroundColor
1651                                        .setExpression("{1.0, 1.0, 1.0, 1.0}");
1652                                frame.repaint();
1653                            }
1654                        } catch (Exception ex) {
1655                            System.out.println(
1656                                    "Failed to set the background to white.");
1657                            ex.printStackTrace();
1658                        }
1659                        ((TableauFrame) frame).printPDF();
1660                    }
1661                }
1662            }
1663        }
1664    }
1665
1666    /** Read a Configuration from the URL given by the specified string.
1667     *  The URL may absolute, or relative to the Ptolemy II tree root,
1668     *  or in the classpath.  To convert a String to a URL suitable for
1669     *  use by this method, call specToURL(String).
1670     *  @param specificationURL A string describing a URL.
1671     *  @return A configuration.
1672     *  @exception Exception If the configuration cannot be opened, or
1673     *   if the contents of the URL is not a configuration.
1674     *  @deprecated Use readConfiguration() instead.
1675     */
1676    @Deprecated
1677    protected Configuration _readConfiguration(URL specificationURL)
1678            throws Exception {
1679        return readConfiguration(specificationURL);
1680    }
1681
1682    /** Return a string summarizing the command-line arguments.
1683     *  @return A usage string.
1684     */
1685    protected String _usage() {
1686        // Call the static method that generates the usage strings.
1687        return StringUtilities.usageString(_commandTemplate, _commandOptions,
1688                _commandFlagsWithDescriptions);
1689    }
1690
1691    ///////////////////////////////////////////////////////////////////
1692    ////                         protected variables               ////
1693
1694    /** The base path of the configuration directory, usually
1695     *  "ptolemy/configs" for Ptolemy II, but subclasses might
1696     *  have configurations in a different directory.
1697     */
1698    protected String _basePath = "ptolemy/configs";
1699
1700    /** The command-line options that are either present or not. */
1701    protected String[][] _commandFlagsWithDescriptions = {
1702            { "-exit", "Exit after generating statistics" },
1703            { "-help", "Print this help message" },
1704            { "-printPDF", "Print to a PDF printer" },
1705            { "-run", "Run the models" },
1706            { "-run20x", "Run the models 20 times, then exit" },
1707            { "-runThenExit",
1708                    "Run the models, then exit after the models finish." },
1709            { "-statistics", "Open the model, print statistics and exit." },
1710            { "-test", "Exit after two seconds." },
1711            { "-version", "Print version information." } };
1712
1713    /** The command-line options that take arguments. */
1714    protected static String[][] _commandOptions = { { "-class", "<classname>" },
1715            { "-<parameter name>", "<parameter value>" }, };
1716
1717    /** The form of the command line. */
1718    protected String _commandTemplate = "moml [ options ] [file ...]";
1719
1720    /** The configuration model of this application. */
1721    protected Configuration _configuration;
1722
1723    /** Indicator that -runThenExit was requested. */
1724    protected boolean _exit = false;
1725
1726    /** The parser used to construct the configuration. */
1727    protected MoMLParser _parser;
1728
1729    /** If true, then print to PDF. */
1730    protected static boolean _printPDF = false;
1731
1732    /** If true, then -run was specified on the command line. */
1733    protected boolean _run = false;
1734
1735    /** If true, then -run20x was specified on the command line. */
1736    protected boolean _run20x = false;
1737
1738    /** If true, then -statistics was specified on the command line. */
1739    protected boolean _statistics = false;
1740
1741    /** If true, then auto exit after a few seconds. */
1742    protected static boolean _test = false;
1743
1744    ///////////////////////////////////////////////////////////////////
1745    ////                         inner classes                     ////
1746
1747    /** Look for directories that contain files named configuration.xml
1748     *  and intro.htm.
1749     */
1750    static class ConfigurationFilenameFilter implements FilenameFilter {
1751        // FindBugs suggests making this class static so as to decrease
1752        // the size of instances and avoid dangling references.
1753
1754        /** Return true if the specified file names a directory
1755         *  that contains a file named configuration.xml
1756         *  and a file named intro.htm
1757         *  @param directory the directory in which the potential
1758         *  directory was found.
1759         *  @param name the name of the directory or file.
1760         *  @return true if the file is a directory that
1761         *  contains a file called configuration.xml
1762         */
1763        @Override
1764        public boolean accept(File directory, String name) {
1765            try {
1766                File configurationDirectory = new File(directory, name);
1767
1768                if (!configurationDirectory.isDirectory()) {
1769                    return false;
1770                }
1771
1772                File configurationFile = new File(configurationDirectory,
1773                        "configuration.xml");
1774                File introFile = new File(configurationDirectory, "intro.htm");
1775
1776                if (configurationFile.isFile() && introFile.isFile()) {
1777                    return true;
1778                }
1779            } catch (Throwable throwable) {
1780                return false;
1781            }
1782
1783            return false;
1784        }
1785    }
1786
1787    /** Error Handler that ignore errors.
1788     */
1789    public static class IgnoreErrorHandler implements ErrorHandler {
1790        // FindBugs suggests making this class static so as to decrease
1791        // the size of instances and avoid dangling references.
1792
1793        ///////////////////////////////////////////////////////////////////
1794        ////                         public methods                    ////
1795
1796        /** Enable or disable skipping of errors.  This method does nothing.
1797         *  @param enable True to enable skipping, false to disable.
1798         */
1799        @Override
1800        public void enableErrorSkipping(boolean enable) {
1801        }
1802
1803        /** Ignore the error.
1804         *  @param element The XML element that triggered the error.
1805         *  @param context The container object for the element.
1806         *  @param exception The exception that was thrown.
1807         *  @return CONTINUE to request skipping this element.
1808         */
1809        @Override
1810        public int handleError(String element, NamedObj context,
1811                Throwable exception) {
1812            return CONTINUE;
1813        }
1814
1815    }
1816
1817    ///////////////////////////////////////////////////////////////////
1818    ////                         private methods                   ////
1819
1820    /** Start the models running, each in a new thread, then return.
1821     *  @param useStartRun True if Manager.startRun() should be called,
1822     *  false if Manager.executeShould be called.
1823     *  @exception KernelException If the manager throws it.
1824     */
1825    private void _runModels(boolean useStartRun) throws KernelException {
1826        Iterator models = models().iterator();
1827
1828        while (models.hasNext()) {
1829            NamedObj model = (NamedObj) models.next();
1830
1831            if (model instanceof CompositeActor) {
1832                CompositeActor actor = (CompositeActor) model;
1833
1834                if (_statistics) {
1835                    System.out.println("Statistics for " + model.getFullName());
1836                    System.out.println(
1837                            ((CompositeEntity) model).statistics(null));
1838                }
1839
1840                // Create a manager if necessary.
1841                Manager manager = actor.getManager();
1842
1843                if (manager == null) {
1844                    manager = new Manager(actor.workspace(), "manager");
1845                    actor.setManager(manager);
1846                }
1847
1848                manager.addExecutionListener(this);
1849                _activeCount++;
1850
1851                if (useStartRun) {
1852                    // Run the model in a new thread.
1853                    manager.startRun();
1854                } else {
1855                    manager.execute();
1856                }
1857            }
1858        }
1859    }
1860
1861    ///////////////////////////////////////////////////////////////////
1862    ////                         private variables                 ////
1863
1864    // The count of currently executing runs.
1865    private volatile int _activeCount = 0;
1866
1867    // Flag indicating that the previous argument was -class.
1868    private boolean _expectingClass = false;
1869
1870    // List of parameter names seen on the command line.
1871    private List<String> _parameterNames = new LinkedList<String>();
1872
1873    // List of parameter values seen on the command line.
1874    private List<String> _parameterValues = new LinkedList<String>();
1875
1876    /** URI from which the configuration was read.  We use a URI to
1877     * avoid URL.equals(),which is very expensive?  See FindBugs and
1878     * http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html
1879     * This variable is volatile to avoid "unsynchronized lazy
1880     * initialization of a non-volatile static field".  See FindBugs
1881     * LI_LAZY_INIT_STATIC.
1882     */
1883    private static volatile URI _initialSpecificationURI;
1884}