001/* An application that executes non-graphical
002 models specified on the command line.
003
004 Copyright (c) 2001-2017 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.moml;
030
031import java.io.File;
032import java.lang.reflect.Method;
033
034import ptolemy.actor.CompositeActor;
035import ptolemy.actor.ExecutionListener;
036import ptolemy.actor.Manager;
037import ptolemy.actor.injection.ActorModuleInitializer;
038import ptolemy.kernel.util.BasicModelErrorHandler;
039import ptolemy.kernel.util.ChangeListener;
040import ptolemy.kernel.util.ChangeRequest;
041import ptolemy.kernel.util.Workspace;
042import ptolemy.moml.filter.BackwardCompatibility;
043import ptolemy.moml.filter.RemoveGraphicalClasses;
044import ptolemy.util.MessageHandler;
045import ptolemy.util.SimpleMessageHandler;
046import ptolemy.util.StringUtilities;
047
048///////////////////////////////////////////////////////////////////
049//// MoMLSimpleApplication
050
051/** A simple application that reads in a .xml file as a command
052 line argument and runs it.
053
054 <p>MoMLApplication sets the look and feel, which starts up Swing,
055 so we can't use MoMLApplication for non-graphical simulations.
056
057 <p>We implement the ChangeListener interface so that this
058 class will get exceptions thrown by failed change requests.
059
060 For example to use this class, try:
061 <pre>
062 java -classpath $PTII ptolemy.actor.gui.MoMLSimpleApplication ../../../ptolemy/domains/sdf/demo/OrthogonalCom/OrthogonalCom.xml
063 </pre>
064
065 @author Christopher Hylands
066 @version $Id$
067 @since Ptolemy II 8.0
068 @Pt.ProposedRating Red (cxh)
069 @Pt.AcceptedRating Red (eal)
070 */
071public class MoMLSimpleApplication
072        implements ChangeListener, ExecutionListener {
073
074    /** Instantiate a MoMLSimpleApplication.  This constructor is
075     * probably not useful by itself, it is for use by subclasses.
076     *
077     * <p>The HandSimDroid work in $PTII/ptserver uses dependency
078     * injection to determine which implementation actors such as
079     * Const and Display to use.  This method reads the
080     * ptolemy/actor/ActorModule.properties file.</p>
081     *
082     *  @exception Exception Not thrown in this base class
083     */
084    public MoMLSimpleApplication() throws Exception {
085        ActorModuleInitializer.initializeInjector();
086    }
087
088    /** Parse the xml file and run it.
089     *  @param xmlFileName A string that refers to an MoML file that
090     *  contains a Ptolemy II model.  The string should be
091     *  a relative pathname.
092     *  @exception Throwable If there was a problem parsing
093     *  or running the model.
094     */
095    public MoMLSimpleApplication(String xmlFileName) throws Throwable {
096        this();
097        _workspace = new Workspace("MoMLSimpleApplicationWorkspace");
098        _parser = new MoMLParser();
099
100        // The default MessageHandler._error() merely prints the
101        // exception, we want to throw the exception and stop execution.
102        MessageHandler.setMessageHandler(new SimpleMessageHandler());
103
104        // The test suite calls MoMLSimpleApplication multiple times,
105        // and the list of filters is static, so we reset it each time
106        // so as to avoid adding filters every time we run an auto test.
107        // We set the list of MoMLFilters to handle Backward Compatibility.
108        MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters(),
109                _workspace);
110
111        // Filter out any graphical classes.
112        MoMLParser.addMoMLFilter(new RemoveGraphicalClasses());
113
114        // If there is a MoML error, then throw the exception as opposed
115        // to skipping the error.  If we call StreamErrorHandler instead,
116        // then the nightly build may fail to report MoML parse errors
117        // as failed tests
118        //parser.setErrorHandler(new StreamErrorHandler());
119        // We use parse(URL, URL) here instead of parseFile(String)
120        // because parseFile() works best on relative pathnames and
121        // has problems finding resources like files specified in
122        // parameters if the xml file was specified as an absolute path.
123        _toplevel = (CompositeActor) _parser.parse(null,
124                new File(xmlFileName).toURI().toURL());
125
126        // If the model is a top level, and a model error handler has not been set,
127        // then set a BasicModelErrorHandler.
128        // (PtolemyFrame has similar code.)
129        if (_toplevel.getContainer() == null) {
130            if (_toplevel.getModelErrorHandler() == null) {
131                _toplevel.setModelErrorHandler(new BasicModelErrorHandler());
132            }
133        }
134
135        _manager = new Manager(_toplevel.workspace(), "MoMLSimpleApplication");
136        _toplevel.setManager(_manager);
137        _toplevel.addChangeListener(this);
138
139        _manager.addExecutionListener(this);
140        // Coverity Scan stated: "Volatile not atomically updated (VOLATILE_ATOMICITY)"
141        // ". stale_update: Updating _activeCount based on a stale value. Any intervening update in another thread is overwritten."
142        // So, we synchronize the update on this.
143        synchronized (this) {
144            _activeCount++;
145        }
146
147        _manager.startRun();
148
149        Thread waitThread = new UnloadThread();
150
151        // Note that we start the thread here, which could
152        // be risky when we subclass, since the thread will be
153        // started before the subclass constructor finishes (FindBugs)
154        waitThread.start();
155        waitThread.join();
156        if (_sawThrowable != null) {
157            throw _sawThrowable;
158        }
159
160    }
161
162    ///////////////////////////////////////////////////////////////////
163    ////                         Public methods                    ////
164
165    /** React to a change request has been successfully executed by
166     *  doing nothing. This method is called after a change request
167     *  has been executed successfully.  In this class, we
168     *  do nothing.
169     *  @param change The change that has been executed, or null if
170     *   the change was not done via a ChangeRequest.
171     */
172    @Override
173    public void changeExecuted(ChangeRequest change) {
174    }
175
176    /** React to a change request that has resulted in an exception.
177     *  This method is called after a change request was executed,
178     *  but during the execution in an exception was thrown.
179     *  This method throws a runtime exception with a description
180     *  of the original exception.
181     *  @param change The change that was attempted or null if
182     *   the change was not done via a ChangeRequest.
183     *  @param exception The exception that resulted.
184     */
185    @Override
186    public void changeFailed(ChangeRequest change, Exception exception) {
187        // If we do not implement ChangeListener, then ChangeRequest
188        // will print any errors to stdout and continue.
189        // This causes no end of trouble with the test suite
190        // We can't throw an Exception here because this method in
191        // the base class does not throw Exception.
192        String description = "";
193
194        if (change != null) {
195            description = change.getDescription();
196        }
197
198        throw new RuntimeException("MoMLSimplApplication.changeFailed(): "
199                + description + " failed: ", exception);
200    }
201
202    /** Clean up by freeing memory.  After calling cleanup(), do not call
203     *  rerun().
204     */
205    public void cleanup() {
206        MoMLSimpleApplication.closeVertx();
207        // The next line removes the static backward compatibility
208        // filters, which is probably not what we want if we
209        // want to parse another file.
210        //BackwardCompatibility.clear();
211
212        // The next line will remove the static MoMLParser (_filterMoMLParser)
213        // used by the filters.  If we add filters, then the _filterMoMLParser
214        // is recreated, so this is probably safe.  We need to get rid
215        // of _filterMoMLParser so that the _manager is collected.
216        MoMLParser.setMoMLFilters(null);
217
218        if (_parser != null) {
219            _parser.resetAll();
220            // _parser is a protected variable so setting it to
221            // null will (hopefully) cause the garbage
222            // collector to collect it.
223            _parser = null;
224        }
225
226        // _manager is a protected variable so setting it to
227        // null will (hopefully) cause the garbage
228        // collector to collect it.
229        _manager = null;
230
231        // _toplevel and _workspace are protected variables so
232        // setting it to null will (hopefully) cause the
233        // garbage collector to collect them
234
235        // Set toplevel to null so that the Manager is collected.
236        _toplevel = null;
237
238        // Set workspace to null so that the objects contained
239        // by the workspace may be collected.
240        _workspace = null;
241    }
242
243    /** If the VertxHelperBase class is present, then invoke the
244     *  closeVertx() method so that this process does not wait around
245     *  for the Vert.x threads.
246     */
247    public static void closeVertx() {
248        try {
249            Class clazz = Class
250                    .forName("ptolemy.actor.lib.jjs.VertxHelperBase");
251            if (clazz != null) {
252                Method method = clazz.getMethod("closeVertx");
253                method.invoke(null);
254            }
255        } catch (NoClassDefFoundError ex) {
256            // Ignore this, it means that MoMLSimpleApplication was invoked without the Vert.x jar files.
257        } catch (Throwable throwable) {
258            System.err.println(
259                    "MoMLSimpleApplication: Failed to invoke VertxHelperBase.closeVertx() during exit.  This can be ignored. Error was: "
260                            + throwable);
261        }
262    }
263
264    /** Report an execution failure.   This method will be called
265     *  when an exception or error is caught by a manager.
266     *  Exceptions are reported this way when the run() or startRun()
267     *  methods of the manager are used to perform the execution.
268     *  If instead the execute() method is used, then exceptions are
269     *  not caught, and are instead just passed up to the caller of
270     *  the execute() method.  Those exceptions are not reported
271     *  here (unless, of course, the caller of the execute() method does
272     *  so).
273     *  In this class, we set a flag indicating that execution has finished.
274     *
275     *  @param manager The manager controlling the execution.
276     *  @param throwable The throwable to report.
277     */
278    @Override
279    public synchronized void executionError(Manager manager,
280            Throwable throwable) {
281        _sawThrowable = throwable;
282        _executionFinishedOrError = true;
283        _activeCount--;
284        if (_activeCount <= 0) {
285            notifyAll();
286        }
287    }
288
289    /** Report that the current execution has finished and
290     *  the wrapup sequence has completed normally. The number of successfully
291     *  completed iterations can be obtained by calling getIterationCount()
292     *  on the manager.
293     *  In this class, we set a flag indicating that execution has finished.
294     *  @param manager The manager controlling the execution.
295     */
296    @Override
297    public synchronized void executionFinished(Manager manager) {
298        _activeCount--;
299        _executionFinishedOrError = true;
300        if (_activeCount <= 0) {
301            notifyAll();
302        }
303    }
304
305    /** Report that the manager has changed state.
306     *  To access the new state, use the getState() method of Manager.
307     *  In this class, do nothing.
308     *  @param manager The manager controlling the execution.
309     *  @see Manager#getState()
310     */
311    @Override
312    public void managerStateChanged(Manager manager) {
313    }
314
315    /** Create an instance of each model file named in the arguments
316     *  and run it.
317     *  @param args The command-line arguments naming the Ptolemy II
318     *  model files (typically .xml files) to be invoked.
319     */
320    public static void main(String[] args) {
321        try {
322            for (String arg : args) {
323                new MoMLSimpleApplication(arg);
324            }
325        } catch (Throwable ex) {
326            System.err.println("Command failed: " + ex);
327            ex.printStackTrace();
328            StringUtilities.exit(1);
329        }
330        // Closing vertx is required by:
331        // $PTII/bin/ptinvoke ptolemy.moml.MoMLSimpleApplication $PTII/ptolemy/actor/lib/jjs/modules/httpServer/test/auto/KeyValueStoreClient.xml
332        MoMLSimpleApplication.closeVertx();
333    }
334
335    /** Execute the same model again.
336     *  @exception Exception if there was a problem rerunning the model.
337     */
338    public void rerun() throws Exception {
339        _manager.execute();
340    }
341
342    /** Return the toplevel.
343     *  @return The toplevel
344     */
345    public CompositeActor toplevel() {
346        // Used in util/testsuite/auto.tcl.
347        return _toplevel;
348    }
349
350    /** Wait for all executing runs to finish, then return.
351     */
352    public synchronized void waitForFinish() {
353        while (_activeCount > 0) {
354            try {
355                wait();
356            } catch (InterruptedException ex) {
357                break;
358            }
359        }
360    }
361
362    ///////////////////////////////////////////////////////////////////
363    ////                         protected variables               ////
364
365    /** The count of currently executing runs. */
366    protected volatile int _activeCount = 0;
367
368    /** A flag that indicates if the execution has finished or thrown
369     *  an error.  The code busy waits until executionFinished() or
370     *  executionError() is called.  If this variable is true and
371     *  _sawThrowable is null then executionFinished() was called.  If
372     *  this variable is true and _sawThrowable is non-null then
373     *  executionError() was called.
374     */
375    protected volatile boolean _executionFinishedOrError = false;
376
377    /** The manager of this model. */
378    protected Manager _manager = null;
379
380    /** The MoMLParser used to parse the model. */
381    protected volatile MoMLParser _parser;
382
383    /** The exception seen by executionError(). */
384    protected volatile Throwable _sawThrowable = null;
385
386    /** The toplevel model that is created and then destroyed. */
387    protected volatile CompositeActor _toplevel;
388
389    /** The workspace in which the model and Manager are created. */
390    protected volatile Workspace _workspace;
391
392    ///////////////////////////////////////////////////////////////////
393    ////                         private inner classes             ////
394
395    /** Wait for the run to finish and the unload the model.
396     */
397    private class UnloadThread extends Thread {
398        @Override
399        public void run() {
400            waitForFinish();
401            if (_sawThrowable != null) {
402                throw new RuntimeException("Execution failed", _sawThrowable);
403            }
404        }
405    }
406}