001/* An application for editing ptolemy models visually. 002 003 Copyright (c) 1999-2014 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.vergil; 029 030import java.io.File; 031import java.io.IOException; 032import java.net.URL; 033import java.util.Iterator; 034import java.util.LinkedList; 035import java.util.List; 036 037import javax.swing.SwingUtilities; 038 039import ptolemy.actor.gui.ActorGraphicalMessageHandler; 040import ptolemy.actor.gui.Configuration; 041import ptolemy.actor.gui.Effigy; 042import ptolemy.actor.gui.EffigyFactory; 043import ptolemy.actor.gui.MoMLApplication; 044import ptolemy.actor.gui.ModelDirectory; 045import ptolemy.actor.gui.PtolemyEffigy; 046import ptolemy.actor.gui.PtolemyPreferences; 047import ptolemy.actor.gui.UserActorLibrary; 048import ptolemy.data.expr.Parameter; 049import ptolemy.gui.PtGUIUtilities; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.InternalErrorException; 052import ptolemy.moml.MoMLParser; 053import ptolemy.moml.filter.RemoveNonPtinyClasses; 054import ptolemy.util.MessageHandler; 055import ptolemy.util.StringUtilities; 056 057/////////////////////////////////////////////////////////////////// 058//// VergilApplication 059 060/** 061 An application for editing ptolemy models visually. 062 063 <p> 064 The exact facilities that are available are determined by an optional 065 command line argument that names a directory in ptolemy/configs that 066 contains a configuration.xml file. For example, if we call vergil 067 -ptiny, then we will use ptolemy/configs/ptiny/configuration.xml and 068 ptolemy/configs/ptiny/intro.htm. The default configuration is 069 ptolemy/configs/full/configuration.xml, which is loaded before any 070 other command-line arguments are processed.</p> 071 072 <p>This application also takes an optional command line argument pair 073 <code>-configuration <i>configurationFile.xml</i></code> that names a configuration 074 to be read. For example, 075 <pre> 076 $PTII/bin/vergil -configuration ptolemy/configs/ptiny/configuration.xml 077 </pre> 078 and 079 <pre> 080 $PTII/bin/vergil -ptiny 081 </pre> 082 are equivalent 083 <p> 084 If there are no command-line arguments at all, then the configuration 085 file is augmented by the MoML file ptolemy/configs/full/welcomeWindow.xml</p> 086 087 <p>Note that if the configuration starts with "ptiny", then 088 {@link ptolemy.moml.filter.RemoveNonPtinyClasses} is used to remove classes 089 such as code generators that might be present in the model but are not part 090 of the Ptiny configuration.</p> 091 092 @author Edward A. Lee, Steve Neuendorffer, Christopher Hylands, contributor: Chad Berkeley 093 @version $Id$ 094 @since Ptolemy II 1.0 095 @Pt.ProposedRating Yellow (eal) 096 @Pt.AcceptedRating Red (eal) 097 @see ptolemy.actor.gui.ModelFrame 098 @see ptolemy.actor.gui.RunTableau 099 @see ptolemy.actor.gui.PtExecuteApplication 100 */ 101public class VergilApplication extends MoMLApplication { 102 /** Parse the specified command-line arguments, creating models 103 * and frames to interact with them. 104 * Look for configurations in "ptolemy/configs" 105 * @param args The command-line arguments. 106 * @exception Exception If command line arguments have problems. 107 */ 108 public VergilApplication(String[] args) throws Exception { 109 // FindBugs: FIXME: Note that MoMLApplication(String, String[]) starts a 110 // thread, which used to mean that the error handler will not be registered before 111 // the thread starts. 112 this("ptolemy/configs", args); 113 } 114 115 /** Parse the specified command-line arguments, creating models 116 * and frames to interact with them. 117 * @param basePath The basePath to look for configurations 118 * in, usually "ptolemy/configs", but other tools might 119 * have other configurations in other directories 120 * @param args The command-line arguments. 121 * @exception Exception If command line arguments have problems. 122 */ 123 public VergilApplication(String basePath, String[] args) throws Exception { 124 // FindBugs: FIXME: Note that MoMLApplication(String, 125 // String[]) starts a thread, which used to mean that the 126 // message handler and error handler were not registered 127 // before the thread starts. 128 super("ptolemy/configs", args, new VergilGraphicalMessageHandler(), 129 new VergilErrorHandler()); 130 try { 131 // Avoid (cd $PTII/doc/test/junit; make) throwing an exception under Java 1.8 132 // under Mac OS X and Linux. Also, the colors are wrong under Mac OS X 10.9. 133 // Another way to get this: "occurred just building a simple SDF model... Open a few libraries and perform a search. " 134 // The exception is: 135 // java.awt.color.CMMException: LCMS error 13: Couldn't link the profiles 136 // at sun.java2d.cmm.lcms.LCMS.createNativeTransform(Native Method) 137 // at sun.java2d.cmm.lcms.LCMS.createTransform(LCMS.java:156) 138 // at sun.java2d.cmm.lcms.LCMSTransform.doTransform(LCMSTransform.java:155) 139 // at sun.java2d.cmm.lcms.LCMSTransform.colorConvert(LCMSTransform.java:629) 140 // at java.awt.color.ICC_ColorSpace.toRGB(ICC_ColorSpace.java:182) 141 // at com.sun.pdfview.colorspace.PDFColorSpace.getPaint(PDFColorSpace.java:222) 142 // at com.sun.pdfview.PDFParser.iterate(PDFParser.java:656) 143 // at com.sun.pdfview.BaseWatchable.run(BaseWatchable.java:101) 144 // at java.lang.Thread.run(Thread.java:745) 145 // See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/PDF-renderer 146 // and https://stackoverflow.com/questions/26535842/multithreaded-jpeg-image-processing-in-java 147 148 // This code was in ptolemy/vergil/pdfrenderer/PDFIcon.java, but the problem persisted 149 // so we are probably invoking ICC_ColorSpace via ImageIO somewhere. 150 Class.forName("javax.imageio.ImageIO"); 151 Class.forName("java.awt.color.ICC_ColorSpace"); 152 153 // This class is not present under Linux? 154 try { 155 Class.forName("sun.java2d.cmm.lcms.LCMS"); 156 } catch (Throwable throwable) { 157 // Ignore. 158 } 159 } catch (Throwable throwable) { 160 new IllegalActionException(null, throwable, 161 "Could not instantiate a Java2d class?" 162 + "See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/PDF-renderer") 163 .printStackTrace(); 164 } 165 } 166 167 /////////////////////////////////////////////////////////////////// 168 //// public methods //// 169 170 /** Print out an error message and stack trace on stderr and then 171 * display a dialog box. This method is used as a fail safe 172 * in case there are problems with the configuration 173 * We use a Throwable here instead of an Exception because 174 * we might get an Error or and Exception. For example, if we 175 * are using JNI, then we might get a java.lang.UnsatisfiedLinkError, 176 * which is an Error, not and Exception. 177 * @param message The message to be displayed 178 * @param args The arguments to be displayed 179 * @param throwable The Throwable that caused the problem. 180 */ 181 public static void errorAndExit(String message, String[] args, 182 Throwable throwable) { 183 // This is public so that other application frameworks may 184 // invoke it 185 StringBuffer argsBuffer = new StringBuffer("Command failed"); 186 187 if (args.length > 0) { 188 argsBuffer.append("\nArguments: " + args[0]); 189 190 for (int i = 1; i < args.length; i++) { 191 argsBuffer.append(" " + args[i]); 192 } 193 194 argsBuffer.append("\n"); 195 } 196 197 // First, print out the stack trace so that 198 // if the next step fails the user has 199 // a chance of seeing the message. 200 System.out.println(argsBuffer.toString()); 201 throwable.printStackTrace(); 202 203 // Display the error message in a stack trace 204 // If there are problems with the configuration, 205 // then there is a chance that we have not 206 // registered the GraphicalMessageHandler yet 207 // so we do so now so that we are sure 208 // the user can see the message. 209 // One way to test this is to run vergil -configuration foo 210 MessageHandler.setMessageHandler(new ActorGraphicalMessageHandler()); 211 212 MessageHandler.error(argsBuffer.toString(), throwable); 213 214 try { 215 StringUtilities.exit(0); 216 } catch (SecurityException ex) { 217 System.out.println("Warning: Failed to call System.exit()." 218 + "(-sandbox always causes this)"); 219 System.out.println("About to clean configurations"); 220 Iterator configurations = Configuration.configurations().iterator(); 221 while (configurations.hasNext()) { 222 Configuration configuration = (Configuration) configurations 223 .next(); 224 try { 225 System.out.println("Setting container of " 226 + configuration.getFullName() + " to null"); 227 configuration.setContainer(null); 228 } catch (Exception ex2) { 229 ex2.printStackTrace(); 230 } 231 } 232 233 } 234 } 235 236 /** Create a new instance of this application, passing it the 237 * command-line arguments. 238 * @param args The command-line arguments. 239 */ 240 public static void main(final String[] args) { 241 if (PtGUIUtilities.macOSLookAndFeel()) { 242 String aboutNameProperty = "com.apple.mrj.application.apple.menu.about.name"; 243 try { 244 // Mac OS X: Set the "About" menu name. These calls must be made outside 245 // the Swing Event Thread. 246 System.setProperty(aboutNameProperty, "Vergil"); 247 } catch (SecurityException ex) { 248 if (!_printedSecurityExceptionMessage) { 249 _printedSecurityExceptionMessage = true; 250 System.out.println("Warning: Mac OS X: Failed to set the \"" 251 + aboutNameProperty + "\" property. " 252 + "(applets and -sandbox always causes this)"); 253 } 254 } 255 // Uncomment the next line to use the screen menu bar instead of a per-window 256 // menu bar. 257 //System.setProperty("apple.laf.useScreenMenuBar", "true"); 258 } 259 260 // FIXME: Java superstition dictates that if you want something 261 // to work, you should invoke it in event thread. Otherwise, 262 // weird things happens at the user interface level. This 263 // seems to prevent occasional errors rending HTML under Web Start. 264 try { 265 // NOTE: This is unfortunate... It would be nice 266 // if this could be run inside a PtolemyThread, since 267 // getting read access the workspace is much more efficient 268 // in PtolemyThread. 269 SwingUtilities.invokeLater(new Runnable() { 270 @Override 271 public void run() { 272 try { 273 new VergilApplication(args); 274 } catch (Throwable throwable) { 275 // If we get an Error or and Exception while 276 // configuring, we will end up here. 277 errorAndExit("Command failed", args, throwable); 278 } 279 } 280 }); 281 } catch (Throwable throwable2) { 282 // We are not likely to get here, but just to be safe 283 // we try to print the error message and display it in a 284 // graphical widget. 285 errorAndExit("Command failed", args, throwable2); 286 } 287 288 // If the -test arg was set, then exit after 2 seconds. 289 if (_test) { 290 try { 291 Thread.sleep(2000); 292 } catch (InterruptedException e) { 293 } 294 295 StringUtilities.exit(0); 296 } 297 } 298 299 /** 300 * Open the MoML file at the given location as a new library in the 301 * actor library for this application. 302 * 303 * An alternate class can be used to build the library if reading the 304 * MoML is not desired. The class must extend ptolemy.moml.LibraryBuilder 305 * and the _alternateLibraryBuilder property must be set with the 'value' 306 * set to the class that extends LibraryBuilder. 307 * 308 * @param configuration The configuration where we look for the 309 * actor library. 310 * @param file The MoML file to open. 311 * @exception Exception If there is a problem opening the configuration, 312 * opening the MoML file, or opening the MoML file as a new library. 313 * @deprecated Use {@link ptolemy.actor.gui.UserActorLibrary#openLibrary(Configuration, File)} 314 */ 315 @Deprecated 316 public static void openLibrary(Configuration configuration, File file) 317 throws Exception { 318 MoMLParser.setErrorHandler(new VergilErrorHandler()); 319 UserActorLibrary.openLibrary(configuration, file); 320 } 321 322 /////////////////////////////////////////////////////////////////// 323 //// protected methods //// 324 325 /** Return a default Configuration. The initial default configuration 326 * is the MoML file full/configuration.xml under the _basePath 327 * directory, which is usually ptolemy/configs. 328 * using different command line arguments can change the value 329 * Usually, we also open the user library, which is located 330 * in the directory returned by 331 * {@link ptolemy.util.StringUtilities#preferencesDirectory()} 332 * If the configuration contains a top level Parameter named 333 * _hideUserLibrary, then we do not open the user library. 334 * 335 * @return A default configuration. 336 * @exception Exception If the configuration cannot be opened. 337 */ 338 @Override 339 protected Configuration _createDefaultConfiguration() throws Exception { 340 try { 341 if (_configurationURL == null) { 342 _configurationURL = specToURL( 343 _basePath + "/full/configuration.xml"); 344 } 345 } catch (IOException ex) { 346 try { 347 // If we ship HyVisual without a full installation, then 348 // we try the hyvisual configuration. 349 // vergil -help needs this. 350 // FIXME: we could do better than this and either 351 // search for configurations or go through a list 352 // of them. 353 _configurationURL = specToURL( 354 _basePath + "/hyvisual/configuration.xml"); 355 } catch (IOException ex2) { 356 // Throw the original exception. 357 throw ex; 358 } 359 } 360 361 // This has the side effects of merging properties from ptII.properties 362 Configuration configuration = super._createDefaultConfiguration(); 363 364 try { 365 configuration = readConfiguration(_configurationURL); 366 } catch (Exception ex) { 367 throw new Exception( 368 "Failed to read configuration '" + _configurationURL + "'", 369 ex); 370 } 371 372 // Read the user preferences, if any. 373 PtolemyPreferences.setDefaultPreferences(configuration); 374 375 // If _hideUserLibraryAttribute is not present, or is false, 376 // call openUserLibrary(). openUserLibrary() will open either the 377 // user library or the library named by the _alternateLibraryBuilder. 378 Parameter hideUserLibraryAttribute = (Parameter) configuration 379 .getAttribute("_hideUserLibrary", Parameter.class); 380 381 if (hideUserLibraryAttribute == null 382 || hideUserLibraryAttribute.getExpression().equals("false")) { 383 384 // Load the user library. 385 try { 386 MoMLParser.setErrorHandler(new VergilErrorHandler()); 387 UserActorLibrary.openUserLibrary(configuration); 388 } catch (Exception ex) { 389 MessageHandler.error("Failed to display user library.", ex); 390 } 391 } 392 393 return configuration; 394 } 395 396 /** Return a default Configuration to use when there are no command-line 397 * arguments. If the configuration contains a parameter 398 * _applicationBlankPtolemyEffigyAtStartup 399 * then we create an empty up an empty PtolemyEffigy. 400 * @return A configuration for when there no command-line arguments. 401 * @exception Exception If the configuration cannot be opened. 402 */ 403 @Override 404 protected Configuration _createEmptyConfiguration() throws Exception { 405 Configuration configuration = _createDefaultConfiguration(); 406 URL welcomeURL = null; 407 URL introURL = null; 408 409 ModelDirectory directory = configuration.getDirectory(); 410 411 Parameter applicationBlankPtolemyEffigyAtStartup = (Parameter) configuration 412 .getAttribute("_applicationBlankPtolemyEffigyAtStartup", 413 Parameter.class); 414 if (applicationBlankPtolemyEffigyAtStartup != null 415 && applicationBlankPtolemyEffigyAtStartup.getExpression() 416 .equals("true")) { 417 418 EffigyFactory factory = null; 419 420 Parameter startupEffigyFactoryName = (Parameter) applicationBlankPtolemyEffigyAtStartup 421 .getAttribute("_startupEffigyFactoryName"); 422 423 // See if there's a startup name 424 if (startupEffigyFactoryName != null) { 425 String startupName = startupEffigyFactoryName.getExpression(); 426 EffigyFactory factoryContainer = (EffigyFactory) configuration 427 .getEntity("effigyFactory"); 428 429 // Make sure there is effigyFactory 430 if (factoryContainer == null) { 431 throw new IllegalActionException( 432 "Could not find effigyFactory."); 433 } 434 435 // Search through the effigy factories for the startup name. 436 List factoryList = factoryContainer 437 .entityList(EffigyFactory.class); 438 Iterator factories = factoryList.iterator(); 439 440 while (factories.hasNext()) { 441 final EffigyFactory currentFactory = (EffigyFactory) factories 442 .next(); 443 if (currentFactory.getName().equals(startupName)) { 444 factory = currentFactory; 445 } 446 } 447 448 // Make sure we found it. 449 if (factory == null) { 450 throw new IllegalActionException( 451 "Could not find effigy factory " + startupName); 452 } 453 454 } else { 455 factory = new PtolemyEffigy.Factory(directory, 456 directory.uniqueName("ptolemyEffigy")); 457 } 458 Effigy effigy = factory.createEffigy(directory, null, null); 459 configuration.createPrimaryTableau(effigy); 460 } 461 462 try { 463 // First, we see if we can find the welcome window by 464 // looking at the default configuration. 465 // FIXME: this seems wrong, we should be able to get 466 // an attribute from the configuration that names the 467 // welcome window. 468 String configurationURLString = _configurationURL.toExternalForm(); 469 String base = configurationURLString.substring(0, 470 configurationURLString.lastIndexOf("/")); 471 472 welcomeURL = specToURL(base + "/welcomeWindow.xml"); 473 introURL = specToURL(base + "/intro.htm"); 474 _parser.reset(); 475 _parser.setContext(configuration); 476 _parser.parse(welcomeURL, welcomeURL); 477 } catch (Throwable throwable) { 478 // OK, that did not work, try a different method. 479 if (_configurationSubdirectory == null) { 480 _configurationSubdirectory = "full"; 481 } 482 483 // FIXME: This code is Dog slow for some reason. 484 welcomeURL = specToURL(_basePath + "/" + _configurationSubdirectory 485 + "/welcomeWindow.xml"); 486 introURL = specToURL(_basePath + "/" + _configurationSubdirectory 487 + "/intro.htm"); 488 _parser.reset(); 489 _parser.setContext(configuration); 490 _parser.parse(welcomeURL, welcomeURL); 491 } 492 493 Effigy doc = (Effigy) configuration.getEntity("directory.doc"); 494 495 if (doc == null) { 496 throw new InternalErrorException( 497 "Configuration does not have a directory.doc entity?"); 498 } 499 doc.identifier.setExpression(introURL.toExternalForm()); 500 501 return configuration; 502 } 503 504 /** Parse the command-line arguments. 505 * @exception Exception If an argument is not understood or triggers 506 * an error. 507 */ 508 @Override 509 protected void _parseArgs(final String[] args) throws Exception { 510 _commandTemplate = "vergil [ options ] [file ...]"; 511 512 // VergilApplication.super._parseArgs(args) 513 // calls _createDefaultConfiguration() asap, 514 // so delay calling it until we process 515 // the arguments and possibly get a configuration. 516 List processedArgsList = new LinkedList(); 517 518 for (int i = 0; i < args.length; i++) { 519 // Parse any configuration specific args first so that we can 520 // set up the configuration for later use by the parent class. 521 if (!_configurationParseArg(args[i])) { 522 // If we did not parse the arg, the 523 // add it to the list of args to be passed 524 // to the super class. 525 processedArgsList.add(args[i]); 526 } 527 } 528 529 if (_expectingConfiguration) { 530 throw new IllegalActionException("Missing configuration"); 531 } 532 533 String[] processedArgs = (String[]) processedArgsList 534 .toArray(new String[processedArgsList.size()]); 535 536 super._parseArgs(processedArgs); 537 } 538 539 /** Return a string summarizing the command-line arguments. 540 * @return A usage string. 541 */ 542 @Override 543 protected String _usage() { 544 return _configurationUsage(_commandTemplate, _commandOptions, 545 new String[] {}); 546 } 547 548 /////////////////////////////////////////////////////////////////// 549 //// protected variables //// 550 // _commandOptions is static because 551 // _usage() may be called from the constructor of the parent class, 552 // in which case non-static variables are null? 553 554 /** The command-line options that take arguments. */ 555 protected static String[][] _commandOptions = { { "-configuration", 556 "<configuration URL, defaults to ptolemy/configs/full/configuration.xml>" }, }; 557 558 /////////////////////////////////////////////////////////////////// 559 //// private methods //// 560 561 /** Parse a command-line argument. Usually, we would name this 562 * method _parseArg(), but we want to handle any arguments that 563 * handle configuration changes before calling the parent class 564 * _parseArg() because the parent class depends either having a 565 * configuration to work with, or the parent class sets up a 566 * configuration. 567 * @return True if the argument is understood, false otherwise. 568 * @exception Exception If something goes wrong. 569 */ 570 private boolean _configurationParseArg(String arg) throws Exception { 571 if (arg.startsWith("-conf")) { 572 _expectingConfiguration = true; 573 } else if (arg.startsWith("-")) { 574 // If the argument names a directory in ptolemy/configs 575 // that contains a file named configuration.xml that can 576 // be found either as a URL or in the classpath, then 577 // assume that it is a configuration. For example, -ptiny 578 // will look for ptolemy/configs/ptiny/configuration.xml 579 // If the argument does not name a configuration, then 580 // we return false so that the argument can be processed 581 // by the parent class. 582 try { 583 _configurationSubdirectory = arg.substring(1); 584 585 String potentialConfiguration = _basePath + "/" 586 + _configurationSubdirectory + "/configuration.xml"; 587 588 // This will throw an Exception if we can't find the config. 589 _configurationURL = specToURL(potentialConfiguration); 590 if (_configurationSubdirectory.startsWith("ptiny")) { 591 // If the configuration startes with ptiny, then 592 // filter out codegenerators 593 LinkedList filters = new LinkedList(); 594 filters.add(new RemoveNonPtinyClasses()); 595 MoMLParser.addMoMLFilters(filters); 596 } 597 } catch (Throwable ex) { 598 // The argument did not name a configuration, let the parent 599 // class have a shot. 600 return false; 601 } 602 } else if (_expectingConfiguration) { 603 _expectingConfiguration = false; 604 _configurationURL = specToURL(arg); 605 } else { 606 return false; 607 } 608 609 return true; 610 } 611 612 /////////////////////////////////////////////////////////////////// 613 //// private variables //// 614 // The subdirectory (if any) of ptolemy/configs where the configuration 615 // may be found. For example if vergil was called with -ptiny, 616 // then this variable will be set to "ptiny", and the configuration 617 // should be at ptolemy/configs/ptiny/configuration.xml 618 private String _configurationSubdirectory; 619 620 // URL of the configuration to read. 621 // The URL may absolute, or relative to the Ptolemy II tree root. 622 // We use the URL instead of the string so that if the configuration 623 // is set as a command line argument, we can use the processed value 624 // from the command line instead of calling specToURL() again, which 625 // might be expensive. 626 private URL _configurationURL; 627 628 // Flag indicating that the previous argument was -conf 629 private boolean _expectingConfiguration = false; 630 631 /** True if we have printed the securityExceptionMessage. */ 632 private static boolean _printedSecurityExceptionMessage = false; 633}