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}