001/* An application that executes models 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.net.URL;
031import java.util.LinkedList;
032import java.util.List;
033
034import ptolemy.actor.Manager;
035import ptolemy.kernel.util.IllegalActionException;
036import ptolemy.kernel.util.KernelException;
037import ptolemy.util.MessageHandler;
038import ptolemy.util.StringUtilities;
039
040///////////////////////////////////////////////////////////////////
041//// PtExecuteApplication
042
043/**
044 This application executes Ptolemy II models specified on the
045 command line.
046 <p>
047 The exact facilities that are available are determined by an optional
048 command line argument that names a directory in ptolemy/configs that
049 contains a configuration.xml file.  For example, if we call vergil
050 -ptiny, then we will use ptolemy/configs/ptiny/configuration.xml and
051 ptolemy/configs/ptiny/intro.htm.  The default configuration is
052 ptolemy/configs/runConfiguration.xml, which is loaded before any
053 other command-line arguments are processed.
054
055 <p>This application also takes an optional command line argument pair
056 <code>-conf <i>configurationFile.xml</i></code> that names a configuration
057 to be read.  For example,
058 <pre>
059 $PTII/bin/ptexecute -conf ptolemy/configs/full/configuration.xml ../../domains/sdf/demo/Butterfly/Butterfly.xml
060 </pre>
061 and
062 <pre>
063 $PTII/bin/ptexecute -full ../../domains/sdf/demo/Butterfly/Butterfly.xml
064 </pre>
065 are equivalent
066 <p>
067 If no configuration is specified on the command line, then
068 the MoML file ptolemy/configs/runConfiguration.xml is loaded before
069 other command line arguments are processed.
070
071 <p> If one of the command-line arguments is -exit, then System.exit()
072 is called when all the models are finished running.  System.exit()
073 returns 0 if all the models finished without throwing an exception,
074 otherwise it returns an integer that represents the number of models
075 that threw an exception.  The main() method calls System.exit()
076 as well and returns an integer that represents the number of models
077 that threw an exception.
078
079 <p>If there are no command-line arguments at all, then this class
080 does nothing.
081
082 <p> This class will bring up the GUI and usually requires access
083 to a display. The {@link ptolemy.actor.gui.MoMLSimpleApplication}
084 class will run models in a non-graphical context.
085
086 @author Edward A. Lee, Steve Neuendorffer Christopher Hylands
087 @version $Id$
088 @since Ptolemy II 1.0
089 @Pt.ProposedRating Yellow (eal)
090 @Pt.AcceptedRating Red (eal)
091 @see ModelFrame
092 @see RunTableau
093 */
094public class PtExecuteApplication extends MoMLApplication {
095    /** Parse the specified command-line arguments, creating models
096     *  and running them.
097     *  @param args The command-line arguments.
098     *  @exception Exception If command line arguments have problems.
099     */
100    public PtExecuteApplication(String[] args) throws Exception {
101        // FIXME: Under JDK1.3.1_06, the MoMLApplication constructor
102        // calls setLookAndFeel() which invokes getDefaultToolkit()
103        // which may cause PtExecuteApplication to not exit.  See
104        // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4030718
105        // However, since we now run with JDK1.4.1, this should not
106        // be a problem.
107        super(args);
108    }
109
110    ///////////////////////////////////////////////////////////////////
111    ////                         public methods                    ////
112
113    /**  Display a stack trace because one of the models has an error.
114     *  A dialog box with the stack trace is created, the stack trace
115     *  is printed to stderr and the eventual exiting of the process
116     *  is delayed so the stack trace can be read.
117     *  @param manager The manager calling this method.
118     *  @param throwable The Throwable to be displayed.
119     */
120    @Override
121    public synchronized void executionError(Manager manager,
122            Throwable throwable) {
123
124        // If you modify this code, make sure that the following command
125        // prints a meaningful message to stdout:
126        //  $PTII/bin/ptexecute $PTII/ptolemy/domains/sdf/kernel/test/auto/knownFailedTests/tunneling2.xml
127
128        // One of the models has an error, so we set the return value of
129        // the java process to something other than 1.
130        _exitValue++;
131        MessageHandler.error("Command failed", throwable);
132
133        // Delay exiting for two seconds so the stack trace is visible.
134        // FIXME: this is not the best way to do this.  The user will
135        // not have time to really read the message.  A better design
136        // would be to some how prevent the process from exiting until
137        // after the stack trace dialog is closed.
138        _test = true;
139
140        // Print the stack trace on standard error.
141        System.err.print(KernelException.stackTraceToString(throwable));
142
143        super.executionError(manager, throwable);
144    }
145
146    /** Create a new instance of this application, passing it the
147     *  command-line arguments.
148     *  @param args The command-line arguments.
149     */
150    public static void main(String[] args) {
151        try {
152            PtExecuteApplication application = new PtExecuteApplication(args);
153            application.runModels();
154            application.waitForFinish();
155        } catch (Throwable throwable) {
156            MessageHandler.error("Command failed", throwable);
157            // Be sure to print the stack trace so that
158            // "$PTII/bin/ptexecute -foo" prints something.
159            System.err.print(KernelException.stackTraceToString(throwable));
160
161            _exitValue++;
162
163            // Keep the process running so the dialog can be displayed.
164            _test = true;
165        }
166
167        // If the -test arg was set, then exit after 2 seconds.
168        if (_test) {
169            try {
170                Thread.sleep(2000);
171            } catch (InterruptedException e) {
172            }
173        }
174        StringUtilities.exit(_exitValue);
175    }
176
177    ///////////////////////////////////////////////////////////////////
178    ////                         protected methods                 ////
179
180    /** Return a default Configuration, which in this case is given by
181     *  the MoML file ptolemy/configs/runConfiguration.xml.
182     *  The default configuration supports executing, but not editing,
183     *  Ptolemy models.
184     *  If there is an _applicationInitializer parameter, then
185     *  construct it.  The _applicationInitializer parameter contains
186     *  a string that names a class to be initialized.
187
188     *  @return A default configuration.
189     *  @exception Exception If the configuration cannot be opened.
190     */
191    @Override
192    protected Configuration _createDefaultConfiguration() throws Exception {
193        if (_configurationURL == null) {
194            _configurationURL = specToURL(
195                    "ptolemy/configs/runConfiguration.xml");
196        }
197
198        _configuration = readConfiguration(_configurationURL);
199
200        // This has the side effect of merging properties from ptII.properties.
201        super._createDefaultConfiguration();
202
203        // Read the user preferences, if any.
204        PtolemyPreferences.setDefaultPreferences(_configuration);
205
206        return _configuration;
207    }
208
209    /** Throw an exception.
210     *  @return Does not return.
211     *  @exception Exception Always thrown.
212     */
213    @Override
214    protected Configuration _createEmptyConfiguration() throws Exception {
215        throw new Exception("No model specified.");
216    }
217
218    /** Parse the command-line arguments. This overrides the base class
219     *  only to set the usage information.
220     *  @exception Exception If an argument is not understood or triggers
221     *   an error.
222     */
223    @Override
224    protected synchronized void _parseArgs(String[] args) throws Exception {
225        _commandTemplate = "ptexecute [ options ] file ...";
226
227        // PtExecuteApplication.super._parseArgs(args)
228        // calls _createDefaultConfiguration() asap,
229        // so delay calling it until we process
230        // the arguments and possibly get a configuration.
231        List processedArgsList = new LinkedList();
232
233        for (int i = 0; i < args.length; i++) {
234            // Parse any configuration specific args first so that we can
235            // set up the configuration for later use by the parent class.
236            if (!_configurationParseArg(args[i])) {
237                // If we did not parse the arg, the
238                // add it to the list of args to be passed
239                // to the super class.
240                processedArgsList.add(args[i]);
241            }
242        }
243
244        if (_expectingConfiguration) {
245            throw new IllegalActionException("Missing configuration");
246        }
247
248        String[] processedArgs = (String[]) processedArgsList
249                .toArray(new String[processedArgsList.size()]);
250
251        super._parseArgs(processedArgs);
252    }
253
254    /** Return a string summarizing the command-line arguments.
255     *  @return A usage string.
256     */
257    @Override
258    protected String _usage() {
259        return _configurationUsage(_commandTemplate, _localCommandOptions,
260                _localCommandFlags);
261    }
262
263    ///////////////////////////////////////////////////////////////////
264    ////                         protected variables               ////
265    // _localCommandFlags and _commandOptions are static because
266    // _usage() may be called from the constructor of the parent class,
267    //  in which case non-static variables are null?
268
269    /** The command-line options that are either present or not. */
270    protected static String[] _localCommandFlags = { "-exit" };
271
272    /** The command-line options that take arguments. */
273    protected static String[][] _localCommandOptions = { { "-config",
274            "<configuration URL, defaults to ptolemy/configs/runConfiguration.xml>" }, };
275
276    ///////////////////////////////////////////////////////////////////
277    ////                         private methods                   ////
278
279    /** Parse a command-line argument.  Usually, we would name this
280     *  method _parseArg(), but we want to handle any arguments that
281     *  handle configuration changes before calling the parent class
282     *  _parseArg() because the parent class depends either having a
283     *  configuration to work with, or the parent class sets up a
284     *  configuration.
285     *  @return True if the argument is understood, false otherwise.
286     *  @exception Exception If something goes wrong.
287     */
288    private boolean _configurationParseArg(String arg) throws Exception {
289        if (arg.startsWith("-conf")) {
290            _expectingConfiguration = true;
291        } else if (arg.startsWith("-")) {
292            // If the argument names a directory in ptolemy/configs
293            // that contains a file named configuration.xml that can
294            // be found either as a URL or in the classpath, then
295            // assume that it is a configuration.  For example, -ptiny
296            // will look for ptolemy/configs/ptiny/configuration.xml
297            // If the argument does not name a configuration, then
298            // we return false so that the argument can be processed
299            // by the parent class.
300            try {
301                _configurationSubdirectory = arg.substring(1);
302
303                String potentialConfiguration = "ptolemy/configs/"
304                        + _configurationSubdirectory + "/configuration.xml";
305
306                // This will throw an Exception if we can't find the config.
307                _configurationURL = specToURL(potentialConfiguration);
308            } catch (Exception ex) {
309                // The argument did not name a configuration, let the parent
310                // class have a shot.
311                return false;
312            }
313        } else if (_expectingConfiguration) {
314            _expectingConfiguration = false;
315            _configurationURL = specToURL(arg);
316        } else {
317            return false;
318        }
319
320        return true;
321    }
322
323    ///////////////////////////////////////////////////////////////////
324    ////                         private variables                 ////
325    // The subdirectory (if any) of ptolemy/configs where the configuration
326    // may be found.  For example if vergil was called with -ptiny,
327    // then this variable will be set to "ptiny", and the configuration
328    // should be at ptolemy/configs/ptiny/configuration.xml
329    private String _configurationSubdirectory;
330
331    // URL of the configuration to read.
332    // The URL may absolute, or relative to the Ptolemy II tree root.
333    // We use the URL instead of the string so that if the configuration
334    // is set as a command line argument, we can use the processed value
335    // from the command line instead of calling specToURL() again, which
336    // might be expensive.
337    private URL _configurationURL;
338
339    // Return value of this process.  0 = everything ok, 2 = had an exception.
340    private static int _exitValue = 0;
341
342    // Flag indicating that the previous argument was -conf
343    private boolean _expectingConfiguration = false;
344}