001/* A Ptolemy application that instantiates class names given on the command
002 line.
003
004 Copyright (c) 2004-2014 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.actor.gui;
030
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034
035import ptolemy.actor.CompositeActor;
036import ptolemy.actor.Director;
037import ptolemy.actor.Manager;
038import ptolemy.actor.injection.PortablePlaceable;
039import ptolemy.data.expr.Variable;
040import ptolemy.kernel.attributes.VersionAttribute;
041import ptolemy.kernel.util.Attribute;
042import ptolemy.kernel.util.IllegalActionException;
043import ptolemy.kernel.util.KernelException;
044import ptolemy.moml.MoMLParser;
045import ptolemy.util.StringUtilities;
046
047///////////////////////////////////////////////////////////////////
048//// CompositeActorSimpleApplication
049
050/**
051 This application creates one or more Ptolemy II models given a
052 classname on the command line, and then executes those models, each in
053 its own thread.  Each specified class should be derived from
054 CompositeActor, and should have a constructor that takes a single
055 argument, an instance of Workspace.  If the model does not contain
056 a manager, then one will be created for it.
057 <p>
058 The model is not displayed,  models that have actors that extend
059 Placeable should instead use
060 {@link ptolemy.actor.gui.CompositeActorApplication}.
061 </p>
062 <p>
063 The command-line arguments can also set parameter values for any
064 parameter in the models, with the name given relative to the top-level
065 entity.  For example, to specify the iteration count in an SDF model,
066 you can invoke this on the command line as follows:
067 </p>
068 <pre>
069 java -classpath $PTII ptolemy.actor.gui.CompositeActorSimpleApplication \
070 -director.iterations 1000 \
071 -class ptolemy.actor.gui.test.TestModel
072 </pre>
073 <p>
074 This assumes that the model given by the specified class name has a director
075 named "director" with a parameter named "iterations".  If more than
076 one model is given on the command line, then the parameter values will
077 be set for all models that have such a parameter.
078 </p>
079
080 @see ptolemy.actor.gui.CompositeActorApplication
081 @author Christopher Brooks
082 @version $Id$
083 @since Ptolemy II 4.1
084 @Pt.ProposedRating Red (cxh)
085 @Pt.AcceptedRating Red (vogel)
086 */
087public class CompositeActorSimpleApplication {
088    ///////////////////////////////////////////////////////////////////
089    ////                         public methods                    ////
090
091    /** Create a new application with the specified command-line arguments.
092     *  @param args The command-line arguments.
093     */
094    public static void main(String[] args) {
095        CompositeActorSimpleApplication application = new CompositeActorSimpleApplication();
096        _run(application, args);
097    }
098
099    /** Return the list of models.
100     *  @return The list of models passed in as arguments.
101     */
102    public List<CompositeActor> models() {
103        // Used primarily for testing.
104        return _models;
105    }
106
107    /** Parse the command-line arguments, creating models as specified.
108     *  @param args The command-line arguments.
109     *  @exception Exception If something goes wrong.
110     */
111    public void processArgs(String[] args) throws Exception {
112        if (args != null) {
113            _parseArgs(args);
114
115            // start the models.
116            Iterator models = _models.iterator();
117
118            while (models.hasNext()) {
119                startRun((CompositeActor) models.next());
120            }
121        }
122    }
123
124    /** Report an exception.  This prints a message to the standard error
125     *  stream, followed by the stack trace.
126     *  @param ex The exception to report.
127     */
128    public void report(Exception ex) {
129        report("", ex);
130    }
131
132    /** Report a message to the user.
133     *  This prints a message to the standard output stream.
134     *  @param message The message to report.
135     */
136    public void report(String message) {
137        System.out.println(message);
138    }
139
140    /** Report an exception with an additional message.
141     *  This prints a message to standard error, followed by the
142     *  stack trace.
143     *  @param message The message.
144     *  @param ex The exception to report.
145     */
146    public void report(String message, Exception ex) {
147        System.err.println("Exception thrown:\n" + message + "\n"
148                + KernelException.stackTraceToString(ex));
149    }
150
151    /** If the specified model has a manager and is not already running,
152     *  then execute the model in a new thread.  Otherwise, do nothing.
153     *  If the model contains an atomic entity that implements Placeable,
154     *  we create create an instance of ModelFrame, if nothing implements
155     *  Placeable, then we do not create an instance of ModelFrame.  This
156     *  allows us to run non-graphical models on systems that do not have
157     *  a display.
158     *  <p>
159     *  We then start the model running.
160     *
161     *  @param model The model to execute.
162     *  @exception IllegalActionException If the model contains Placeables.
163     *  or does not have a manager.
164     *  @return Always returns null.
165     *  @see ptolemy.actor.Manager#startRun()
166     */
167    public synchronized Object startRun(CompositeActor model)
168            throws IllegalActionException {
169        // This method is synchronized so that it can atomically modify
170        // the count of executing processes.
171        // NOTE: If you modify this method, please be sure that it
172        // will work for non-graphical models in the nightly test suite.
173        // Iterate through the model, looking for something that is Placeable.
174        Iterator atomicEntities = model.allAtomicEntityList().iterator();
175
176        while (atomicEntities.hasNext()) {
177            Object object = atomicEntities.next();
178
179            if (object instanceof Placeable
180                    || object instanceof PortablePlaceable) {
181                throw new IllegalActionException(
182                        "CompositeActorSimpleApplication does not support "
183                                + "actors that are instances of placeable, "
184                                + "object was: " + object);
185            }
186        }
187
188        Manager manager = model.getManager();
189
190        if (manager != null) {
191            try {
192                manager.startRun();
193            } catch (IllegalActionException ex) {
194                // Model is already running.  Ignore.
195            }
196        } else {
197            report("Model " + model.getFullName() + " cannot be executed "
198                    + "because it does not have a manager.");
199        }
200        return null;
201    }
202
203    /** If the specified model has a manager and is executing, then
204     *  stop execution by calling the stop() method of the manager.
205     *  If there is no manager, do nothing.
206     *  @param model The model to stop.
207     */
208    public void stopRun(CompositeActor model) {
209        Manager manager = model.getManager();
210
211        if (manager != null) {
212            manager.stop();
213        }
214    }
215
216    /** Wait for all windows to close.
217     */
218    public synchronized void waitForFinish() {
219        while (_openCount > 0) {
220            try {
221                wait();
222            } catch (InterruptedException ex) {
223                break;
224            }
225        }
226    }
227
228    ///////////////////////////////////////////////////////////////////
229    ////                         protected methods                 ////
230
231    /** Parse a command-line argument.  The recognized arguments, which
232     *  result in this method returning true, are summarized below:
233     *  <ul>
234     *  <li>If the argument is "-class", then attempt to interpret
235     *  the next argument as the fully qualified classname of a class
236     *  to instantiate as a ptolemy model.  The model will be created,
237     *  added to the directory of models, and then executed.
238     *  <li>If the argument is "-help", then print a help message.
239     *  <li>If the argument is "-test", then set a flag that will
240     *  abort execution of any created models after two seconds.
241     *  <li>If the argument is "-version", then print a short version message.
242     *  <li>If the argument is "", then ignore it.
243     *  </ul>
244     *  Otherwise, the argument is ignored and false is returned.
245     *
246     *  @param arg The argument to be parse.
247     *  @return True if the argument is understood, false otherwise.
248     *  @exception Exception If something goes wrong.
249     */
250    protected boolean _parseArg(String arg) throws Exception {
251        if (arg.equals("-class")) {
252            _expectingClass = true;
253        } else if (arg.equals("-help")) {
254            System.out.println(_usage());
255
256            // Don't call System.exit(0) here, it will break the test suites
257        } else if (arg.equals("-test")) {
258            _test = true;
259        } else if (arg.equals("-version")) {
260            System.out.println("Version " + VersionAttribute.CURRENT_VERSION.getExpression());
261
262            // quit the program if the user asked for the version
263            // Don't call System.exit(0) here, it will break the test suites
264        } else if (arg.equals("")) {
265            // Ignore blank argument.
266        } else {
267            if (_expectingClass) {
268                _expectingClass = false;
269
270                MoMLParser parser = new MoMLParser();
271                String string = "<entity name=\"toplevel\" class=\"" + arg
272                        + "\"/>";
273                CompositeActor model = (CompositeActor) parser.parse(string);
274
275                // Temporary hack because cloning doesn't properly clone
276                // type constraints.
277                CompositeActor modelClass = (CompositeActor) parser
278                        .searchForClass(arg, model.getSource());
279
280                if (modelClass != null) {
281                    model = modelClass;
282                }
283
284                _models.add(model);
285
286                // Create a manager.
287                Manager manager = model.getManager();
288
289                if (manager == null) {
290                    model.setManager(new Manager(model.workspace(), "manager"));
291                    //manager = model.getManager();
292                }
293            } else {
294                // Argument not recognized.
295                return false;
296            }
297        }
298
299        return true;
300    }
301
302    /** Parse the command-line arguments.
303     *  @param args The arguments to be parsed.
304     *  @exception Exception If an argument is not understood or triggers
305     *   an error.
306     */
307    protected void _parseArgs(String[] args) throws Exception {
308        for (int i = 0; i < args.length; i++) {
309            String arg = args[i];
310
311            if (_parseArg(arg) == false) {
312                if (arg.startsWith("-") && i < args.length - 1) {
313                    // Save in case this is a parameter name and value.
314                    _parameterNames.add(arg.substring(1));
315                    _parameterValues.add(args[i + 1]);
316                    i++;
317                } else {
318                    // Unrecognized option.
319                    throw new IllegalActionException(
320                            "Unrecognized option: " + arg);
321                }
322            }
323        }
324
325        if (_expectingClass) {
326            throw new IllegalActionException("Missing classname.");
327        }
328
329        // Check saved options to see whether any is a parameter.
330        Iterator names = _parameterNames.iterator();
331        Iterator values = _parameterValues.iterator();
332
333        while (names.hasNext() && values.hasNext()) {
334            String name = (String) names.next();
335            String value = (String) values.next();
336
337            boolean match = false;
338            Iterator models = _models.iterator();
339
340            while (models.hasNext()) {
341                CompositeActor model = (CompositeActor) models.next();
342                Attribute attribute = model.getAttribute(name);
343
344                if (attribute instanceof Variable) {
345                    match = true;
346                    ((Variable) attribute).setExpression(value);
347
348                    // Force evaluation so that listeners are notified.
349                    ((Variable) attribute).getToken();
350                }
351
352                Director director = model.getDirector();
353
354                if (director != null) {
355                    attribute = director.getAttribute(name);
356
357                    if (attribute instanceof Variable) {
358                        match = true;
359                        ((Variable) attribute).setExpression(value);
360
361                        // Force evaluation so that listeners are notified.
362                        ((Variable) attribute).getToken();
363                    }
364                }
365            }
366
367            if (!match) {
368                // Unrecognized option.
369                throw new IllegalActionException(
370                        "Unrecognized option: " + "-" + name);
371            }
372        }
373    }
374
375    /** Run the application.
376     *  @param application The application.
377     *  @param args The arguments to be passed to the application.
378     */
379    protected static void _run(CompositeActorSimpleApplication application,
380            String[] args) {
381        try {
382            application.processArgs(args);
383            application.waitForFinish();
384        } catch (Exception ex) {
385            System.err.println(KernelException.stackTraceToString(ex));
386            StringUtilities.exit(0);
387        }
388
389        // If the -test arg was set, then exit after 2 seconds.
390        if (_test) {
391            try {
392                Thread.sleep(2000);
393            } catch (InterruptedException e) {
394            }
395
396            StringUtilities.exit(0);
397        }
398    }
399
400    /** Return a string summarizing the command-line arguments.
401     *  @return A usage string.
402     */
403    protected String _usage() {
404        StringBuffer result = new StringBuffer("Usage: " + _commandTemplate
405                + "\n\n" + "Options that take values:\n");
406
407        int i;
408
409        for (i = 0; i < _commandOptions.length; i++) {
410            result.append(" " + _commandOptions[i][0] + " "
411                    + _commandOptions[i][1] + "\n");
412        }
413
414        result.append("\nBoolean flags:\n");
415
416        for (i = 0; i < _commandFlags.length; i++) {
417            result.append(" " + _commandFlags[i]);
418        }
419
420        return result.toString();
421    }
422
423    ///////////////////////////////////////////////////////////////////
424    ////                         protected variables               ////
425
426    /** The command-line options that are either present or not. */
427    protected String[] _commandFlags = { "-help", "-test", "-version", };
428
429    /** The command-line options that take arguments. */
430    protected String[][] _commandOptions = { { "-class", "<classname>" },
431            { "-<parameter name>", "<parameter value>" }, };
432
433    /** The form of the command line. */
434    protected String _commandTemplate = "ptolemy [ options ]";
435
436    /** The list of all the models. */
437    protected List<CompositeActor> _models = new LinkedList<CompositeActor>();
438
439    /** The count of currently open windows. */
440    protected int _openCount = 0;
441
442    /** If true, then auto exit after a few seconds. */
443    protected static boolean _test = false;
444
445    ///////////////////////////////////////////////////////////////////
446    ////                         private variables                 ////
447    // Flag indicating that the previous argument was -class.
448    private boolean _expectingClass = false;
449
450    // List of parameter names seen on the command line.
451    private List _parameterNames = new LinkedList();
452
453    // List of parameter values seen on the command line.
454    private List _parameterValues = new LinkedList();
455}