001/* Export a model as an image or set of html files. 002 003 Copyright (c) 2011-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 */ 028 029package ptolemy.vergil.basic.export; 030 031import java.awt.Color; 032import java.io.File; 033import java.io.FileOutputStream; 034import java.io.IOException; 035import java.io.OutputStream; 036import java.net.URI; 037import java.net.URL; 038import java.util.Iterator; 039import java.util.List; 040import java.util.Locale; 041import java.util.Timer; 042import java.util.TimerTask; 043 044import javax.swing.JFrame; 045import javax.swing.SwingUtilities; 046 047import ptolemy.actor.CompositeActor; 048import ptolemy.actor.Director; 049import ptolemy.actor.Manager; 050import ptolemy.actor.TypedCompositeActor; 051import ptolemy.actor.gui.BrowserEffigy; 052import ptolemy.actor.gui.Configuration; 053import ptolemy.actor.gui.ConfigurationApplication; 054import ptolemy.actor.gui.Effigy; 055import ptolemy.actor.gui.ModelDirectory; 056import ptolemy.actor.gui.PtolemyEffigy; 057import ptolemy.actor.gui.Tableau; 058import ptolemy.actor.gui.TableauFrame; 059import ptolemy.actor.lib.gui.UsesInvokeAndWait; 060import ptolemy.data.BooleanToken; 061import ptolemy.kernel.CompositeEntity; 062import ptolemy.kernel.Entity; 063import ptolemy.kernel.util.BasicModelErrorHandler; 064import ptolemy.kernel.util.IllegalActionException; 065import ptolemy.util.FileUtilities; 066import ptolemy.util.StringUtilities; 067import ptolemy.vergil.basic.BasicGraphFrame; 068import ptolemy.vergil.basic.ExportParameters; 069import ptolemy.vergil.basic.export.html.ExportHTMLAction; 070import ptolemy.vergil.basic.export.web.WebExportParameters; 071 072/////////////////////////////////////////////////////////////////// 073//// ExportModel 074/** 075 * Export a model as an image or set of html files. 076 * 077 * <p>The default is to export a .gif file with the same name as the model. 078 * See {@link #main(String[])} for usage.</p> 079 * 080 * <p> See <a href="https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/HTMLExport">https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/HTMLExport</a> 081 * for detailed instructions about how to create web pages on the 082 * Ptolemy website for models.</p> 083 * 084 * @author Christopher Brooks 085 * @version $Id$ 086 * @since Ptolemy II 10.0 087 * @Pt.ProposedRating Red (cxh) 088 * @Pt.AcceptedRating Red (cxh) 089 */ 090public class ExportModel { 091 /** Export an image of a model to a file or directory. 092 093 * <p>The image is written to a file or directory with the same name 094 * as the model. If formatName starts with "HTM" or "htm", then 095 * a directory with the same name as the basename of the model is 096 * created. If the formatName is "GIF", "gif", "PNG" or "png", 097 * then a file with the same basename as the basename of the 098 * model is created.</p> 099 * 100 * <p>The time out defaults to 30 seconds.</p> 101 * 102 * @param copyJavaScriptFiles True if the javascript files should 103 * be copied. Used only if <i>formatName</i> starts with "htm" 104 * or "HTM". 105 * 106 * @param force If true, then remove the image file or htm 107 * directory to be created in advance before creating the image 108 * file or htm directory. This parameter is primarily used to 109 * avoid prompting the user with questions about overwriting 110 * files after this command is invoked. 111 * 112 * @param formatName The file format of the file to be generated. 113 * One of "GIF", "gif", "HTM", "htm", "PNG", "png". 114 * 115 * @param modelFileName A Ptolemy model in MoML format. 116 * The string may start with $CLASSPATH, $HOME or other formats 117 * suitable for {@link ptolemy.util.FileUtilities#nameToFile(String, URI)}. 118 * 119 * @param run True if the model should be run first. If <i>run</i> 120 * is true, and if <i>formatName</i> starts with "htm" or "HTM", then 121 * the output will include images of any plots. 122 * 123 * @param openComposites True if the CompositeEntities should be 124 * open. The <i>openComposites</i> parameter only has an effect 125 * if <i>formatName</i> starts with "htm" or "HTM". 126 * 127 * @param openResults open the resulting image file or web page. 128 * 129 * @param outputFileOrDirectory If non-null, then the file or directory 130 * in which to generate the file(s). 131 * 132 * @param save True if the model should be saved after being run. 133 * 134 * @param whiteBackground True if the model background should be set to white. 135 * 136 * @exception Exception Thrown if there is a problem reading the model 137 * or exporting the image. 138 */ 139 public void exportModel(final boolean copyJavaScriptFiles, 140 final boolean force, final String formatName, 141 final String modelFileName, final boolean run, 142 final boolean openComposites, final boolean openResults, 143 final String outputFileOrDirectory, final boolean save, 144 final boolean whiteBackground) throws Exception { 145 146 exportModel(copyJavaScriptFiles, force, formatName, modelFileName, run, 147 openComposites, openResults, outputFileOrDirectory, save, 30000, 148 whiteBackground); 149 } 150 151 /** Export an image of a model to a file or directory. 152 * The image is written to a file or directory with the same name as the model. 153 * If formatName starts with "HTM" or "htm", then a directory with the 154 * same name as the basename of the model is created. 155 * If the formatName is "GIF", "gif", "PNG" or "png", then a file 156 * with the same basename as the basename of the model is created. 157 * 158 * @param copyJavaScriptFiles True if the javascript files should be copied. 159 * Used only if <i>formatName</i> starts with "htm" or "HTM". 160 * 161 * @param force If true, then remove the image file or htm directory to be created 162 * in advance before creating the image file or htm directory. This parameter 163 * is primarily used to avoid prompting the user with questions about overwriting files 164 * after this command is invoked. 165 * 166 * @param formatName The file format of the file to be generated. 167 * One of "GIF", "gif", "HTM", "htm", "PNG", "png". 168 * 169 * @param modelFileName A Ptolemy model in MoML format. 170 * The string may start with $CLASSPATH, $HOME or other formats 171 * suitable for {@link ptolemy.util.FileUtilities#nameToFile(String, URI)}. 172 * 173 * @param run True if the model should be run first. If <i>run</i> 174 * is true, and if <i>formatName</i> starts with "htm" or "HTM", then 175 * the output will include images of any plots. 176 * 177 * @param openComposites True if the CompositeEntities should be 178 * open. The <i>openComposites</i> parameter only has an effect 179 * if <i>formatName</i> starts with "htm" or "HTM". 180 * 181 * @param openResults open the resulting image file or web page. 182 * 183 * @param outputFileOrDirectory If non-null, then the file or directory 184 * in which to generate the file(s). 185 * 186 * @param save True if the model should be saved after being run. 187 * 188 * @param timeOut Time out in milliseconds. 30000 is a good value. 189 * 190 * @param whiteBackground True if the model background should be set to white. 191 * 192 * @exception Exception Thrown if there is a problem reading the model 193 * or exporting the image. 194 */ 195 public void exportModel(final boolean copyJavaScriptFiles, 196 final boolean force, final String formatName, 197 final String modelFileName, final boolean run, 198 final boolean openComposites, final boolean openResults, 199 final String outputFileOrDirectory, final boolean save, 200 final long timeOut, final boolean whiteBackground) 201 throws Exception { 202 // FIXME: Maybe we should pass an ExportParameter here? 203 204 // FIXME: this seem wrong: The inner classes are in different 205 // threads and can only access final variables. However, we 206 // use an array as a final variable, but we change the value 207 // of the element of the array. Is this thread safe? 208 // Perhaps we should make this a field? 209 //final TypedCompositeActor[] model = new TypedCompositeActor[1]; 210 final CompositeEntity[] model = new CompositeEntity[1]; 211 212 ///// 213 // Open the model. 214 // FIXME: Refactor this and KielerLayoutJUnitTest to a common class. 215 Runnable openModelAction = new Runnable() { 216 @Override 217 public void run() { 218 try { 219 model[0] = ConfigurationApplication 220 .openModelOrEntity(modelFileName); 221 } catch (Throwable throwable) { 222 throwable.printStackTrace(); 223 throw new RuntimeException(throwable); 224 } 225 } 226 }; 227 SwingUtilities.invokeAndWait(openModelAction); 228 _sleep(); 229 230 _basicGraphFrame = BasicGraphFrame.getBasicGraphFrame(model[0]); 231 232 // Set temporary variables before setting the final versions 233 // for use inside inner classes. 234 File temporaryHTMLDirectory = null; 235 File temporaryImageFile = null; 236 237 // Use the model name as the basis for the directory containing 238 // the html or as the basis for the image file. 239 final boolean isHTM = formatName.toLowerCase(Locale.getDefault()) 240 .startsWith("htm"); 241 if (isHTM) { 242 if (outputFileOrDirectory != null) { 243 temporaryHTMLDirectory = new File(outputFileOrDirectory); 244 temporaryImageFile = new File( 245 outputFileOrDirectory + File.separator + "index.html"); 246 } else { 247 temporaryHTMLDirectory = new File(model[0].getName()); 248 temporaryImageFile = new File( 249 model[0].getName() + File.separator + "index.html"); 250 251 } 252 } else { 253 String suffix = "." + formatName.toLowerCase(Locale.getDefault()); 254 if (outputFileOrDirectory != null) { 255 // If the filename does not end in the formatName, 256 // append the format name. 257 if (outputFileOrDirectory 258 .endsWith(formatName.toLowerCase(Locale.getDefault())) 259 || outputFileOrDirectory.endsWith( 260 formatName.toUpperCase(Locale.getDefault()))) { 261 suffix = ""; 262 } 263 temporaryImageFile = new File(outputFileOrDirectory + suffix); 264 } else { 265 // The user did not specify an outputFileOrDirectory, 266 // so use the model name. 267 temporaryImageFile = new File(model[0].getName() + suffix); 268 } 269 } 270 271 // The directory where an html file would be generated. 272 final File htmlDirectory = temporaryHTMLDirectory; 273 // The name of the index.html file or image file. 274 final File imageFile = temporaryImageFile; 275 276 // We optionally delete the directory containing the .html file or 277 // delete the image file. Do this after loading the model so that 278 // we can get the directory in which the model resides 279 if (force) { 280 // Delete the directory containing the .html file or 281 // delete the image file. 282 if (isHTM) { 283 if (htmlDirectory.exists() 284 && !FileUtilities.deleteDirectory(htmlDirectory)) { 285 System.err.println( 286 "Could not delete \"" + htmlDirectory + "\"."); 287 } 288 } else { 289 // A gif/jpg/png file 290 if (imageFile.exists() && !imageFile.delete()) { 291 System.err 292 .println("Could not delete \"" + imageFile + "\"."); 293 } 294 } 295 } 296 297 if (run) { 298 if (!_runnable(model[0])) { 299 System.out.println("Model \"" + model[0].getFullName() 300 + "\" contains actors such cannot be run " 301 + " as part of the export process from ExportModel or " 302 + "it has a WebExportParameters value that runBeforeExport set to false. " 303 + "To export run this model and export it, use vergil."); 304 } else { 305 // Optionally run the model. 306 Runnable runAction = new Runnable() { 307 @Override 308 public void run() { 309 try { 310 if (!(model[0] instanceof TypedCompositeActor)) { 311 System.out.println(model[0].getFullName() 312 + " is a " 313 + model[0].getClass().getName() 314 + " not a TypedCompositeActor, so it cannot be run."); 315 return; 316 } 317 TypedCompositeActor composite = (TypedCompositeActor) model[0]; 318 System.out.println( 319 "Running " + composite.getFullName()); 320 Manager manager = composite.getManager(); 321 if (manager == null) { 322 manager = new Manager(composite.workspace(), 323 "MyManager"); 324 composite.setManager(manager); 325 } 326 composite.setModelErrorHandler( 327 new BasicModelErrorHandler()); 328 _timer = new Timer(true); 329 final Director finalDirector = composite 330 .getDirector(); 331 TimerTask doTimeToDie = new TimerTask() { 332 @Override 333 public void run() { 334 System.out.println( 335 "ExportHTMLTimer went off after " 336 + timeOut 337 + " ms., calling getDirector().finish and getDirector().stopFire()"); 338 339 // NOTE: This used to call stop() on 340 // the manager, but that's not the 341 // right thing to do. In particular, 342 // this could be used inside a 343 // RunCompositeActor, and it should 344 // only stop the inside execution, not 345 // the outside one. It's also not 346 // correct to call stop() on the 347 // director, because stop() requests 348 // immediate stopping. To give 349 // determinate stopping, this actor 350 // needs to complete the current 351 // iteration. 352 353 // The Stop actor has similar code. 354 finalDirector.finish(); 355 356 // To support multithreaded domains, 357 // also have to call stopFire() to 358 // request that all actors conclude 359 // ongoing firings. 360 finalDirector.stopFire(); 361 } 362 }; 363 _timer.schedule(doTimeToDie, timeOut); 364 365 // Calling finish() and stopFire() is not 366 // sufficient if the model is still 367 // initializing, so we call stop() on the 368 // manager after 2x the timeout. 369 // To replicate: 370 371 // $PTII/bin/ptinvoke ptolemy.vergil.basic.export.ExportModel -force htm -run -openComposites -timeOut 30000 -whiteBackground ptolemy/domains/ddf/demo/RijndaelEncryption/RijndaelEncryption.xml $PTII/ptolemy/domains/ddf/demo/RijndaelEncryption/RijndaelEncryption 372 373 final Manager finalManager = manager; 374 _failSafeTimer = new Timer(true); 375 TimerTask doFailSafeTimeToDie = new TimerTask() { 376 @Override 377 public void run() { 378 System.out.println( 379 "ExportHTMLTimer went off after " 380 + timeOut * 2 381 + " ms., calling manager.stop()."); 382 383 finalManager.stop(); 384 } 385 }; 386 _failSafeTimer.schedule(doFailSafeTimeToDie, 387 timeOut * 2); 388 389 try { 390 manager.execute(); 391 } finally { 392 _timer.cancel(); 393 _failSafeTimer.cancel(); 394 } 395 } catch (Exception ex) { 396 ex.printStackTrace(); 397 throw new RuntimeException(ex); 398 } 399 } 400 }; 401 SwingUtilities.invokeAndWait(runAction); 402 _sleep(); 403 } 404 } 405 406 if (save) { 407 // Optionally save the model. 408 // Sadly, running the DOPCenter.xml model does not seem to update the 409 // graph. So, we run it and save it and then open it again. 410 Runnable saveAction = new Runnable() { 411 @Override 412 public void run() { 413 try { 414 System.out.println("Saving " + model[0].getFullName()); 415 ((PtolemyEffigy) _basicGraphFrame.getTableau() 416 .getContainer()) 417 .writeFile(new File(modelFileName)); 418 } catch (Exception ex) { 419 ex.printStackTrace(); 420 throw new RuntimeException(ex); 421 } 422 } 423 }; 424 SwingUtilities.invokeAndWait(saveAction); 425 _sleep(); 426 } 427 428 if (openComposites && !isHTM) { 429 // Optionally open any composites. 430 Runnable openCompositesAction = new Runnable() { 431 @Override 432 public void run() { 433 try { 434 System.out.println("Opening submodels of " 435 + model[0].getFullName()); 436 Configuration configuration = (Configuration) Configuration 437 .findEffigy(model[0].toplevel()).toplevel(); 438 439 List<CompositeEntity> composites = model[0] 440 .deepCompositeEntityList(); 441 for (CompositeEntity composite : composites) { 442 // Don't open class definitions, then tend not to get closed. 443 //if (!composite.isClassDefinition()) { 444 System.out.println( 445 "Opening " + composite.getFullName()); 446 Tableau tableau = configuration 447 .openInstance(composite); 448 if (whiteBackground) { 449 JFrame frame = tableau.getFrame(); 450 frame.setBackground(java.awt.Color.WHITE); 451 ((ptolemy.vergil.basic.BasicGraphFrame) frame) 452 .getJGraph().getCanvasPane().getCanvas() 453 .setBackground(java.awt.Color.WHITE); 454 } 455 //} 456 } 457 } catch (Exception ex) { 458 ex.printStackTrace(); 459 throw new RuntimeException(ex); 460 } 461 } 462 }; 463 SwingUtilities.invokeAndWait(openCompositesAction); 464 _sleep(); 465 } 466 467 if (whiteBackground && !isHTM) { 468 // Optionally set the background to white. The 469 // ExportParameters facility handles this for us for 470 // exporting htm. 471 Runnable whiteBackgroundAction = new Runnable() { 472 @Override 473 public void run() { 474 try { 475 System.out.println("Setting the background to white."); 476 Configuration configuration = (Configuration) Configuration 477 .findEffigy(model[0].toplevel()).toplevel(); 478 ModelDirectory directory = (ModelDirectory) configuration 479 .getEntity(Configuration._DIRECTORY_NAME); 480 Iterator effigies = directory.entityList().iterator(); 481 482 while (effigies.hasNext()) { 483 Effigy effigy = (Effigy) effigies.next(); 484 Iterator tableaux = effigy.entityList(Tableau.class) 485 .iterator(); 486 //System.out.println("Effigy: " + effigy); 487 while (tableaux.hasNext()) { 488 Tableau tableau = (Tableau) tableaux.next(); 489 //System.out.println("Tableau: " + tableau); 490 JFrame frame = tableau.getFrame(); 491 if (frame instanceof TableauFrame) { 492 // FIXME: lamely, we skip by the configuration directory and UserLibrary by name? 493 if (!tableau.getFullName().equals( 494 ".configuration.directory.configuration.graphTableau") 495 && !tableau.getFullName().equals( 496 ".configuration.directory.UserLibrary.graphTableau")) { 497 try { 498 // Set the background to white. 499 500 frame.setBackground( 501 java.awt.Color.WHITE); 502 ((ptolemy.vergil.basic.BasicGraphFrame) frame) 503 .getJGraph().getCanvasPane() 504 .getCanvas().setBackground( 505 java.awt.Color.WHITE); 506 507 // FIXME: It should be 508 // possible to use 509 // PtolemyPreference here, 510 // but it does not work, 511 // we have to set the 512 // frame background by 513 // hand. 514 515 // PtolemyPreferences.setDefaultPreferences(configuration); 516 // PtolemyPreferences preferences = PtolemyPreferences 517 // .getPtolemyPreferencesWithinConfiguration(configuration); 518 // preferences.backgroundColor 519 // .setExpression("{1.0, 1.0, 1.0, 1.0}"); 520 // //preferences.save(); 521 // preferences.setAsDefault(); 522 523 //System.out.println("Frame: " + frame); 524 frame.repaint(); 525 } catch (Exception ex) { 526 System.out.println( 527 "Failed to set the background to white."); 528 ex.printStackTrace(); 529 } 530 } 531 } 532 } 533 } 534 } catch (Exception ex) { 535 ex.printStackTrace(); 536 throw new RuntimeException(ex); 537 } 538 } 539 }; 540 SwingUtilities.invokeAndWait(whiteBackgroundAction); 541 _sleep(); 542 } 543 544 // Export images 545 Runnable exportModelAction = new Runnable() { 546 @Override 547 public void run() { 548 try { 549 OutputStream out = null; 550 551 try { 552 if (formatName.toLowerCase(Locale.getDefault()) 553 .startsWith("htm")) { 554 if (!htmlDirectory.isDirectory()) { 555 if (!htmlDirectory.mkdirs()) { 556 throw new Exception("Failed to create \"" 557 + htmlDirectory + "\""); 558 } 559 } 560 // FIXME: ExportParameters handles things like setting 561 // the background color, opening composites before export etc. 562 // However, we that here so that export images and export htm 563 // is the same. This could be a mistake. 564 ExportParameters parameters = new ExportParameters( 565 htmlDirectory); 566 if (whiteBackground) { 567 // Set the background of any submodels that are opened. 568 parameters.backgroundColor = Color.white; 569 } 570 parameters.copyJavaScriptFiles = copyJavaScriptFiles; 571 parameters.openCompositesBeforeExport = openComposites; 572 parameters.showInBrowser = openResults; 573 574 ExportHTMLAction.exportToWeb(_basicGraphFrame, 575 parameters); 576 System.out.println("Exported " + htmlDirectory 577 + "/index.html"); 578 } else { 579 out = new FileOutputStream(imageFile); 580 // Export the image. 581 _basicGraphFrame.getJGraph().exportImage(out, 582 formatName); 583 System.out.println( 584 "Exported " + imageFile.getCanonicalPath()); 585 } 586 } finally { 587 if (out != null) { 588 try { 589 out.close(); 590 } catch (IOException ex) { 591 ex.printStackTrace(); 592 } 593 } 594 } 595 } catch (Exception ex) { 596 ex.printStackTrace(); 597 throw new RuntimeException(ex); 598 } 599 } 600 }; 601 SwingUtilities.invokeAndWait(exportModelAction); 602 _sleep(); 603 604 if (openResults && !isHTM) { 605 // Optionally open the results. 606 Runnable openResultsAction = new Runnable() { 607 @Override 608 public void run() { 609 try { 610 System.out.println("Opening " + imageFile); 611 Configuration configuration = (Configuration) Configuration 612 .findEffigy(model[0].toplevel()).toplevel(); 613 URL imageURL = new URL( 614 imageFile.toURI().toURL().toString() 615 + "#in_browser"); 616 configuration.openModel(imageURL, imageURL, 617 imageURL.toExternalForm(), 618 BrowserEffigy.staticFactory); 619 } catch (Throwable throwable) { 620 throwable.printStackTrace(); 621 throw new RuntimeException(throwable); 622 } 623 } 624 }; 625 SwingUtilities.invokeAndWait(openResultsAction); 626 _sleep(); 627 } 628 629 ///// 630 // Close the model. 631 Runnable closeAction = new Runnable() { 632 @Override 633 public void run() { 634 try { 635 ConfigurationApplication 636 .closeModelWithoutSavingOrExiting(model[0]); 637 } catch (Exception ex) { 638 ex.printStackTrace(); 639 throw new RuntimeException(ex); 640 } 641 } 642 }; 643 SwingUtilities.invokeAndWait(closeAction); 644 _sleep(); 645 } 646 647 /** Export a model as an image. 648 * 649 * <p>Note that the a graphical display must be present, this 650 * code displays the model and executes. To use in a headless 651 * environment under Linux, install Xvfb.</p> 652 * 653 * <p>Command line arguments are:</p> 654 * <dl> 655 * <dt>-help|--help|-h</dt> 656 * <dd>Print a help message and return.</dd> 657 * <dt>-copyJavaScriptFiles</dt> 658 * <dd>Copy .js files. Useful only with -web and htm* format.</dd> 659 * <dt>-force</dt> 660 * <dd>Delete the target file or directory before generating the results.</dd> 661 * <dt>-open</dt> 662 * <dd>Open the generated file in a browser.</dd> 663 * <dt>-openComposites</dt> 664 * <dd>Open any composites before exporting the model.</dd> 665 * <dt>-run</dt> 666 * <dd>Run the model before exporting. This is useful when exporting an html file as plots 667 * are also generated.</dd> 668 * <dt>-save</dt> 669 * <dd>Save the model before closing.</dd> 670 * <dt>-web</dt> 671 * <dd>Common settings for exporting to the web. Short for: <code>-force 672 * -copyJavaScriptFiles -open -openComposites htm</code>.</dd> 673 * <dt>-whiteBackground</dt> 674 * <dd>Set the background color to white.</dd> 675 * <dt>[GIF|gif|HTM*|htm*|PNG|png]</dt> 676 * <dd>The file format. If no format is selected, then a gif format file is generated.</dd> 677 * <dt><i>model.xml</i></dt> 678 * <dd>The model to be exported. (Required)</dd> 679 * <dt><i>directoryName</i></dt> 680 * <dd>The directory in which to export the file(s) (Optional)</dd> 681 * </dl> 682 * 683 * <p>Typical usage:</p> 684 * <p> To save a gif:</p> 685 * <pre> 686 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel model.xml 687 * </pre> 688 * 689 * <p>or, to save the current view of model in HTML format without any plots:</p> 690 * <pre> 691 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel htm model.xml 692 * </pre> 693 * 694 * <p>or, to run the model and save the current view of model in 695 * HTML format with any plots:</p> 696 * <pre> 697 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run htm model.xml 698 * </pre> 699 * 700 * <p>or, to run the model, open any composites and save the 701 * current view of model and the composites HTML format with any 702 * plots:</p> 703 * <pre> 704 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run -openComposites htm model.xml 705 * </pre> 706 * 707 * <p>Standard setting for exporting to html can be invoked with <code>-web</code>, 708 * which is like <code>-copyJavaScriptFiles -open -openComposites htm</code>.</p> 709 * <pre> 710 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -web model.xml 711 * </pre> 712 * 713 * <p>or, to save a png:</p> 714 * <pre> 715 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel png model.xml 716 * </pre> 717 * 718 * <p>or, to run the model and then save a png:</p> 719 * <pre> 720 * java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run png model.xml 721 * </pre> 722 * 723 * <p>To set the background to white, invoke with 724 * <code>-whiteBackground</code>.</p> 725 * 726 * <p>To export an html version in a format suitable for the 727 * Ptolemy website, set the 728 * "ptolemy.ptII.exportHTML.usePtWebsite" property to true, 729 * perhaps by including the following in the command line:</p> 730 * <pre> 731 * -Dptolemy.ptII.exportHTML.usePtWebsite=true 732 * </pre> 733 * <p>For example:</p> 734 * <pre> 735 * export JAVAFLAGS=-Dptolemy.ptII.exportHTML.usePtWebsite=true 736 * $PTII/bin/ptweb $PTII/ptolemy/moml/demo/modulation.xml 737 * </pre> 738 * 739 * <p>To include a link to a <code><i>sanitizedModelName</i>.jnlp</code> file, 740 * set -Dptolemy.ptII.exportHTML.linkToJNLP=true.</p> 741 * 742 * <p>Note that the Ptolemy menus will not appear unless you view 743 * the page with a web server that has Server Side Includes (SSI) 744 * enabled and has the appropriate scripts. Also, the .html 745 * files must be executable.</p> 746 * 747 * <p>Include a link to the a 748 * <code><i>sanitizedModelName</i>.jnlp</code> file, set the 749 * "ptolemy.ptII.exportHTML.linkToJNLP" property to true.</p> 750 * 751 * @param args The arguments for the export image operation. 752 * The arguments should be in the format: 753 * [-help|-h|--help] | [-copyJavaScriptFiles] [-force] [-open] [-openComposites] [-run] [-save] 754 * [-timeOut ms] 755 * [-web] [-whiteBackground] [GIF|gif|HTM*|htm*|PNG|png] model.xml 756 * 757 * @exception IllegalArgumentException If there is 1 argument, then it names a 758 * Ptolemy MoML file and the model is exported as a .gif file. 759 * If there are two arguments, then the first argument names a 760 * format, current formats are GIF, gif, HTM, htm, PNG and png 761 * and the second argument names a Ptolemy MoML file. 762 */ 763 public static void main(String args[]) { 764 String eol = System.getProperty("line.separator"); 765 String usage = "Usage:" + eol + "java -classpath $PTII " 766 + "ptolemy.vergil.basic.export.ExportModel " 767 + "[-help|-h|--help] | [-copyJavaScript] [-force] [-open] [-openComposites] " 768 + "[-run] [-save] [-web] [-whiteBackground] [GIF|gif|HTM*|htm*|PNG|png] model.xml" 769 + eol + "Command line arguments are: " + eol 770 + " -help Print this message." + eol 771 + " -copyJavaScriptFiles Copy .js files. Useful only with -web and htm* format." 772 + eol 773 + " -force Delete the target file or directory before generating the results." 774 + eol + " -open Open the generated file." + eol 775 + " -openComposites Open any composites before exporting the model." 776 + eol 777 + " -run Run the model before exporting. -web and htm*: plots are also generated." 778 + eol + " -save Save the model before closing." + eol 779 + " -timeOut milliseconds Timeout in milliseconds." + eol 780 + " -web Common web export args. Short for: -force -copyJavaScriptFiles -open -openComposites htm." 781 + eol 782 + " -whiteBackground Set the background color to white." 783 + eol + " GIF|gif|HTM*|htm*|PNG|png The file format." + eol 784 + " model.xml The Ptolemy model. (Required)" + eol 785 + "To export html suitable for the Ptolemy website, invoke " 786 + eol + "Java with -Dptolemy.ptII.exportHTML.usePtWebsite=true" 787 + eol + "For example:" + eol 788 + "export JAVAFLAGS=-Dptolemy.ptII.exportHTML.usePtWebsite=true" 789 + eol + "$PTII/bin/ptweb $PTII/ptolemy/moml/demo/modulation.xml" 790 + eol + "To include a link to a sanitizedModelName.jnlp file," 791 + eol + "set -Dptolemy.ptII.exportHTML.linkToJNLP=true"; 792 793 if (args.length == 0) { 794 // FIXME: we should get the list of acceptable format names from 795 // BasicGraphFrame 796 System.err.println("Wrong number of arguments"); 797 System.err.println(usage); 798 // Use StringUtilities.exit() so that we can test unit test this code 799 // and avoid FindBugs warnings about System.exit(). 800 StringUtilities.exit(3); 801 return; 802 } 803 boolean copyJavaScriptFiles = false; 804 boolean force = false; 805 String formatName = "GIF"; 806 boolean openResults = false; 807 boolean openComposites = false; 808 String outputFileOrDirectory = null; 809 boolean run = false; 810 boolean save = false; 811 long timeOut = 30000; 812 boolean whiteBackground = false; 813 boolean web = false; 814 String modelFileName = null; 815 if (args.length == 1 && !args[0].startsWith("-")) { 816 modelFileName = args[0]; 817 } else { 818 // FIXME: this is a lame way to process arguments. 819 for (int i = 0; i < args.length; i++) { 820 if (args[i].equals("-help") || args[i].equals("--help") 821 || args[i].equals("-h")) { 822 System.out.println(usage); 823 // Use StringUtilities.exit() so that we can test unit test this code 824 // and avoid FindBugs warnings about System.exit(). 825 StringUtilities.exit(0); 826 return; 827 } else if (args[i].equals("-copyJavaScriptFiles")) { 828 copyJavaScriptFiles = true; 829 } else if (args[i].equals("-force")) { 830 force = true; 831 } else if (args[i].equals("-open") 832 || args[i].equals("-openResults")) { 833 openResults = true; 834 } else if (args[i].equals("-openComposites")) { 835 openComposites = true; 836 } else if (args[i].equals("-run")) { 837 run = true; 838 } else if (args[i].equals("-save")) { 839 save = true; 840 } else if (args[i].equals("-timeOut")) { 841 try { 842 timeOut = Long.parseLong(args[i + 1]); 843 } catch (NumberFormatException ex) { 844 System.err.println(args[i + 1] 845 + "cannot be parsed to long value for the time out." 846 + ex); 847 } 848 i++; 849 } else if (args[i].toUpperCase(Locale.getDefault()) 850 .equals("GIF") 851 || args[i].toUpperCase(Locale.getDefault()) 852 .startsWith("HTM") 853 || args[i].toUpperCase(Locale.getDefault()) 854 .equals("PNG")) { 855 // The default is GIF. 856 if (web) { 857 throw new IllegalArgumentException( 858 "Only one of " + args[i] + " and -web " 859 + "should be specified."); 860 } 861 formatName = args[i].toUpperCase(Locale.getDefault()); 862 } else if (args[i].equals("-web")) { 863 web = true; 864 copyJavaScriptFiles = true; 865 force = true; 866 formatName = "htm"; 867 openResults = true; 868 openComposites = true; 869 whiteBackground = true; 870 } else if (args[i].equals("-whiteBackground")) { 871 whiteBackground = true; 872 } else { 873 if (args[i].startsWith("-")) { 874 throw new IllegalArgumentException( 875 "The model file name " 876 + "cannot begin with a '-', the argument was: " 877 + args[i]); 878 } 879 if (i < args.length - 2) { 880 throw new IllegalArgumentException( 881 "The model file name " 882 + "should be the last or second to last argument. " 883 + "The last argument was: " + args[i]); 884 } 885 if (modelFileName != null) { 886 outputFileOrDirectory = args[i]; 887 } else { 888 modelFileName = args[i]; 889 } 890 } 891 } 892 } 893 try { 894 // FIXME: Should we use ExportParameter here? 895 new ExportModel().exportModel(copyJavaScriptFiles, force, 896 formatName, modelFileName, run, openComposites, openResults, 897 outputFileOrDirectory, save, timeOut, whiteBackground); 898 899 } catch (Exception ex) { 900 ex.printStackTrace(); 901 StringUtilities.exit(5); 902 } 903 StringUtilities.exit(0); 904 } 905 906 /////////////////////////////////////////////////////////////////// 907 //// protected methods //// 908 909 /** Sleep the current thread, which is usually not the Swing Event 910 * Dispatch Thread. 911 */ 912 protected static void _sleep() { 913 // FIXME: The problem is that we need to wait for all the 914 // images to load before getting the images. 915 916 // FIXME: we should be able to call 917 // Toolkit.getDefaultToolkit().sync(); but that does not do 918 // it. 919 try { 920 Thread.sleep(1000); 921 } catch (Throwable ex) { 922 //Ignore 923 } 924 } 925 926 /////////////////////////////////////////////////////////////////// 927 //// private fields //// 928 929 /** The BasicGraphFrame of the model. */ 930 private BasicGraphFrame _basicGraphFrame; 931 932 /** The Timer used to terminate a run by calling finish() and stopFire()*/ 933 private static Timer _timer = null; 934 935 /** The Timer used to terminate a run by calling stop on the 936 * manager. This is called fail safe after the movie by the same 937 * name. 938 */ 939 private static Timer _failSafeTimer = null; 940 941 /////////////////////////////////////////////////////////////////// 942 //// private methods //// 943 944 /** Return true if the model is runnable from this context. 945 * Models that invoke SwingUtilities.invokeAndWait() are not 946 * runnable here. To export such a model, use vergil. Models 947 * that doe not have a director are not runnable. Typically, 948 * such models contain LiveLinks to other models. If the model 949 * has a WebExportParameters parameter then the value of the 950 * runBeforeExport Parameter is returned. 951 * @param model The model to be checked. 952 * @return true if the model is runnable. 953 * @exception IllegalActionException If the WebExportParameter 954 * cannot be read. 955 */ 956 private boolean _runnable(CompositeEntity model) 957 throws IllegalActionException { 958 // Check for WebExportParameters.runBeforeExport being false. 959 List<WebExportParameters> webExportParameters = model 960 .attributeList(WebExportParameters.class); 961 if (webExportParameters.size() > 0) { 962 if (!((BooleanToken) webExportParameters.get(0).runBeforeExport 963 .getToken()).booleanValue()) { 964 return false; 965 } 966 } 967 968 // Check for actors that implement UsesInvokeAndWait. 969 Iterator atomicEntities = model.allAtomicEntityList().iterator(); 970 while (atomicEntities.hasNext()) { 971 Entity entity = (Entity) atomicEntities.next(); 972 if (entity instanceof UsesInvokeAndWait) { 973 System.out.println(entity.getFullName() 974 + " invoked SwingUtilities.invokeAndWait()"); 975 return false; 976 } 977 } 978 979 // Check to see that the CompositeEntity has a Director. If 980 // it does not have a Director, print a message and return 981 // false. If we don't do this, then we need to update 982 // ptolemy/vergil/basic/export/test/junit/ExportModelJUnitTest.java 983 // each time we add a model that has no director, but has 984 // LiveLinks. 985 if (model instanceof CompositeActor) { 986 Director director = ((CompositeActor) model).getDirector(); 987 if (director == null) { 988 return false; 989 } 990 } 991 return true; 992 } 993 994}