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>&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 "&quot;bar&quot;" 115 </pre> 116 <p>The <code>&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>&</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 >property name="_applicationInitializer" 159 class="ptolemy.data.expr.StringParameter" 160 value="org.kepler.gui.KeplerInitializer"/< 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}