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}