001/* Utility methods to handle HTML Viewer about: calls
002
003 Copyright (c) 2003-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 */
027package ptolemy.actor.gui;
028
029import java.io.BufferedReader;
030import java.io.File;
031import java.io.FileWriter;
032import java.io.IOException;
033import java.io.InputStreamReader;
034import java.net.URI;
035import java.net.URISyntaxException;
036import java.net.URL;
037import java.util.Enumeration;
038import java.util.HashSet;
039import java.util.Iterator;
040import java.util.LinkedList;
041import java.util.List;
042import java.util.Set;
043
044import javax.swing.JFrame;
045import javax.swing.event.HyperlinkEvent;
046
047import ptolemy.actor.CompositeActor;
048import ptolemy.actor.Manager;
049import ptolemy.data.ArrayToken;
050import ptolemy.data.StringToken;
051import ptolemy.data.expr.FileParameter;
052import ptolemy.data.expr.Parameter;
053import ptolemy.kernel.CompositeEntity;
054import ptolemy.kernel.attributes.VersionAttribute;
055import ptolemy.kernel.util.Attribute;
056import ptolemy.kernel.util.IllegalActionException;
057import ptolemy.kernel.util.InternalErrorException;
058import ptolemy.kernel.util.NamedObj;
059import ptolemy.kernel.util.StringAttribute;
060import ptolemy.kernel.util.Workspace;
061import ptolemy.moml.MoMLParser;
062import ptolemy.moml.filter.BackwardCompatibility;
063import ptolemy.util.ClassUtilities;
064import ptolemy.util.FileUtilities;
065import ptolemy.util.StringUtilities;
066
067///////////////////////////////////////////////////////////////////
068//// HTMLAbout
069
070/**
071 This class contains static methods that are called
072 by when HTMLViewer.hyperlinkUpdate() is invoked on a hyperlink
073 that starts with <code>about:</code>.  This facility is primarily
074 used for testing.
075
076 @author Christopher Hylands
077 @version $Id$
078 @since Ptolemy II 3.0
079 @Pt.ProposedRating Red (cxh)
080 @Pt.AcceptedRating Red (cxh)
081 @see HTMLViewer#hyperlinkUpdate(HyperlinkEvent)
082 */
083public class HTMLAbout {
084    // This class is separate from HTMLViewer because this class
085    // import lots of Ptolemy specify classes that HTMLViewer does
086    // otherwise need to import
087    ///////////////////////////////////////////////////////////////////
088    ////                         public methods                    ////
089
090    /** Return a string containing HTML that describes the about:
091     *  features.
092     *
093     *  <p>If the configuration contains an _applicationName attribute
094     *  then that attribute is used as the name of the application
095     *  in the generated text.  If _applicationName is not present,
096     *  then the default name is "Ptolemy II".
097     *
098     *  <p>If the configuration contains an _applicationDemos Parameter
099     *  then that parameter is assumed to be an array of strings name
100     *  naming HTML files that should be searched for demos and expanded.
101
102     *  @param configuration The configuration to look for the
103     *  _applicationName attribute in
104     *  @return A string containing HTML that describes the about: features.
105     */
106    public static String about(Configuration configuration) {
107        // Use an explicit version here - the name of the whatsNew file
108        // does not changes as quickly as the version.
109        String version = VersionAttribute.majorCurrentVersion();
110
111        String applicationName = "Ptolemy II";
112
113        try {
114            StringAttribute applicationNameAttribute = (StringAttribute) configuration
115                    .getAttribute("_applicationName", StringAttribute.class);
116
117            if (applicationNameAttribute != null) {
118                applicationName = applicationNameAttribute.getExpression();
119            }
120        } catch (Throwable throwable) {
121            // Ignore and use the default applicationName
122        }
123
124        StringBuffer htmlBuffer = new StringBuffer();
125        htmlBuffer.append("<html><head><title>About " + applicationName
126                + "</title></head>" + "<body><h1>About " + applicationName
127                + "</h1>\n" + "The HTML Viewer in " + applicationName
128                + " handles the <code>about:</code>\n" + "tag specially.\n"
129                + "<br>The following urls are handled:\n" + "<ul>\n"
130                + "<li><a href=\"about:configuration\">"
131                + "<code>about:configuration</code></a> "
132                + "Expand the configuration (good way to test for "
133                + "missing classes).\n" + "<li><a href=\"about:expandLibrary\">"
134                + "<code>about:expandLibrary</code></a> "
135                + "Open a model and expand library tree (good way to test for "
136                + "missing classes, check standard out).\n"
137                + "<li><a href=\"about:copyright\">"
138                + "<code>about:copyright</code></a> "
139                + " Display information about the copyrights.\n");
140
141        if (_configurationExists("full")) {
142            htmlBuffer.append("<li><a href=\"about:checkCompleteDemos\">"
143                    + "<code>about:checkCompleteDemos</code></a> "
144                    + "Check that each of the demos listed in the individual "
145                    + "files is present in "
146                    + "<code>ptolemy/configs/doc/completeDemos.htm</code>.\n");
147        }
148
149        htmlBuffer.append("</ul>\n<table>\n");
150
151        _demosURLs = new LinkedList();
152        if (_configurationExists("full")) {
153            htmlBuffer
154                    .append("<tr rowspan=4><center><b>Full</b></center></tr>\n"
155                            + _aboutHTML(
156                                    "ptolemy/configs/doc/completeDemos.htm")
157                            + _aboutHTML("ptolemy/configs/doc/demos.htm")
158                            + _aboutHTML("ptolemy/configs/doc/whatsNew"
159                                    + version + ".htm")
160                            + _aboutHTML("ptolemy/configs/doc/whatsNew11.0.htm")
161                            + _aboutHTML("ptolemy/configs/doc/whatsNew10.0.htm")
162                            + _aboutHTML("ptolemy/configs/doc/whatsNew8.0.htm")
163                            + _aboutHTML("ptolemy/configs/doc/whatsNew7.0.htm")
164                            + _aboutHTML("ptolemy/configs/doc/whatsNew6.0.htm")
165                            + _aboutHTML("ptolemy/configs/doc/whatsNew5.1.htm")
166                            + _aboutHTML("ptolemy/configs/doc/whatsNew5.0.htm")
167                            + _aboutHTML("ptolemy/configs/doc/whatsNew4.0.htm")
168                            + _aboutHTML(
169                                    "ptolemy/configs/doc/whatsNew3.0.2.htm"));
170        }
171
172        if (_configurationExists("bcvtb")) {
173            htmlBuffer
174                    .append("<tr rowspan=4><center><b>BCVTB</b></center></tr>\n"
175                            + _aboutHTML("ptolemy/configs/bcvtb/intro.htm")
176                            + _aboutHTML(
177                                    "ptolemy/configs/doc/completeDemosBcvtb.htm")
178                            + _aboutHTML("ptolemy/configs/doc/demosBcvtb.htm")
179                            + _aboutHTML("ptolemy/configs/doc/docsBcvtb.htm"));
180        }
181        if (_configurationExists("capecode")) {
182            htmlBuffer.append(
183                    "<tr rowspan=4><center><b>CapeCode</b></center></tr>\n"
184                            + _aboutHTML("ptolemy/configs/capecode/intro.htm")
185                            + _aboutHTML("ptolemy/configs/capecode/docs.htm")
186                            + _aboutHTML(
187                                    "ptolemy/configs/capecode/demonstrations.htm")
188                            + _aboutHTML("ptolemy/configs/capecode/tour.htm"));
189        }
190        if (_configurationExists("cyphysim")) {
191            htmlBuffer.append(
192                    "<tr rowspan=4><center><b>CyPhySim</b></center></tr>\n"
193                            + _aboutHTML("ptolemy/configs/cyphysim/intro.htm")
194                            + _aboutHTML(
195                                    "ptolemy/configs/cyphysim/demonstrations.htm")
196                            + _aboutHTML("ptolemy/configs/cyphysim/docs.htm")
197                            + _aboutHTML("ptolemy/configs/doc/docs.htm"));
198        }
199        // Don't include DSP here, it uses the Ptiny demos anyway.
200        if (_configurationExists("hyvisual")) {
201            htmlBuffer.append(
202                    "<tr rowspan=4><center><b>HyVisual</b></center></tr>\n"
203                            + _aboutHTML("ptolemy/configs/hyvisual/intro.htm"));
204        }
205
206        if (_configurationExists("ptiny")) {
207            htmlBuffer
208                    .append("<tr rowspan=4><center><b>Ptiny</b></center></tr>\n"
209                            + _aboutHTML(
210                                    "ptolemy/configs/doc/completeDemosPtiny.htm")
211                            + _aboutHTML("ptolemy/configs/doc/demosPtiny.htm")
212
213                            + _aboutHTML("doc/mainVergilPtiny.htm"));
214        }
215
216        if (_configurationExists("ptinyKepler")) {
217            htmlBuffer.append(
218                    "<tr rowspan=4><center><b>Ptiny for Kepler</b></center></tr>\n"
219                            + _aboutHTML("ptolemy/configs/kepler/doc-index.htm")
220                            + _aboutHTML("doc/mainVergilPtinyKepler.htm")
221                            + _aboutHTML(
222                                    "ptolemy/configs/doc/demosPtinyKepler.htm")
223                            + _aboutHTML(
224                                    "ptolemy/configs/doc/docsPtinyKepler.htm")
225                            + _aboutHTML(
226                                    "ptolemy/configs/doc/completeDemosPtinyKepler.htm"));
227
228        }
229        if (_configurationExists("visualsense")) {
230            htmlBuffer.append(
231                    "<tr rowspan=4><center><b>VisualSense</b></center></tr>\n"
232                            + _aboutHTML(
233                                    "ptolemy/configs/visualsense/intro.htm"));
234        }
235
236        try {
237            // Check for the _applicationDemos parameter
238            Parameter applicationDemos = (Parameter) configuration
239                    .getAttribute("_applicationDemos", Parameter.class);
240
241            if (applicationDemos != null) {
242                htmlBuffer.append("<tr rowspan=4><center><b>" + applicationName
243                        + "</b></center></tr>\n");
244
245                ArrayToken demoTokens = (ArrayToken) applicationDemos
246                        .getToken();
247
248                for (int i = 0; i < demoTokens.length(); i++) {
249                    StringToken demoToken = (StringToken) demoTokens
250                            .getElement(i);
251                    htmlBuffer.append(_aboutHTML(demoToken.stringValue()));
252                    System.out.println(
253                            "HTMLAbout: adding " + demoToken.stringValue());
254                }
255            }
256        } catch (Exception ex) {
257            throw new InternalErrorException(configuration, ex,
258                    "Bad configuration for " + applicationName);
259        }
260
261        htmlBuffer.append("</table>\n");
262        htmlBuffer.append("</body>\n</html>\n");
263        return htmlBuffer.toString();
264    }
265
266    /** Check that all the demos in otherDemos are in completeDemos.
267     *  Be sure to call {@link #about(Configuration)} before calling this method.
268     *  @param completeDemos A URL pointing to the completeDemos.htm file
269     *  @return HTML listing demos in otherDemos that are not in completeDemos.
270     *  @exception IOException If there is a problem reading the
271     *  completeDemos.htm file.
272     */
273    public static String checkCompleteDemos(String completeDemos)
274            throws IOException {
275        URL demosURL = _getDemoURL(completeDemos);
276        if (demosURL == null) {
277            throw new IOException("Could not find any demos in " + completeDemos
278                    + ". Does that file exist?");
279        }
280        StringBuffer results = new StringBuffer(
281                "<h1>Results of checking for demos not listed in full "
282                        + "demos</h1>\n"
283                        + "For each of the files below, we list demos that are "
284                        + "not included in <a href=\"" + demosURL + "\">"
285                        + "<code>" + demosURL + "</code></a>\n");
286        List completeDemosList = _getURLs(demosURL, ".*.xml$", true, 1);
287        if (_demosURLs == null) {
288            throw new NullPointerException(
289                    "_demosURLs is null.  Call HTMLAbout.about(Configuration) first.");
290        }
291        Iterator demosFileNames = _demosURLs.iterator();
292        while (demosFileNames.hasNext()) {
293            String demosFileName = (String) demosFileNames.next();
294            URL demoURL = _getDemoURL(demosFileName);
295            if (demoURL != null) {
296                results.append("<h2><a href=\"" + demoURL + "\"><code>"
297                        + demoURL + "</code></a></h2>\n<ul>\n");
298
299                List demosList = _getURLs(demoURL, ".*.xml$", true, 0);
300                Iterator demos = demosList.iterator();
301                while (demos.hasNext()) {
302                    String demo = (String) demos.next();
303                    if (!completeDemosList.contains(demo)) {
304                        try {
305                            URL missingDemoURL = ConfigurationApplication
306                                    .specToURL(demo);
307                            results.append(" <li><a href=\"" + missingDemoURL
308                                    + "\">" + missingDemoURL + "</a></li>\n");
309                        } catch (IOException ex) {
310                            results.append(" <li><a href=\"file:/" + demo
311                                    + "\">" + demo + "</a></li>\n");
312                        }
313                    }
314                }
315                results.append("</ul>\n");
316            }
317        }
318        return results.toString();
319    }
320
321    /** Call Configuration.openModel() on relative URLs that match a regexp.
322     *  files are linked to from an HTML file.
323     *  @param demosFileName The name of the HTML file that contains links
324     *  to the .xml, .htm and .html files.
325     *  If this argument is the empty string, then
326     *  "ptolemy/configs/doc/completeDemos.htm" is used.
327     *  @param regexp The regular expression of the links we are interested
328     *  in.
329     *  @param configuration  The configuration to open the files in.
330     *  @return the URL of the HTML file that was searched or null
331     *  if demosFileName does not exist.
332     *  @exception Exception If there is a problem opening a model.
333     */
334    public static URL generateLinks(String demosFileName, String regexp,
335            Configuration configuration) throws Exception {
336        URL demosURL = _getDemoURL(demosFileName);
337        if (demosURL == null) {
338            return null;
339        }
340        List modelList = _getURLs(demosURL, regexp);
341        Iterator models = modelList.iterator();
342
343        while (models.hasNext()) {
344            String model = (String) models.next();
345            if (model.startsWith("ptdoc:")) {
346                Effigy context = configuration.getDirectory()
347                        .entityList(Effigy.class).iterator().next();
348                HTMLViewer.getDocumentation(configuration, model.substring(6),
349                        context);
350            } else {
351                URL modelURL = new URL(demosURL, model);
352
353                try {
354                    configuration.openModel(demosURL, modelURL,
355                            modelURL.toExternalForm());
356                } catch (Throwable throwable) {
357                    throw new Exception("Failed to open '" + modelURL + "'",
358                            throwable);
359                }
360            }
361        }
362
363        return demosURL;
364    }
365
366    /** Process an "about:" HyperlinkEvent.
367     *  @param event The HyperlinkEvent to process.  The description of
368     *  the event should start with "about:".  If there are no specific
369     *  matches for the description, then a general usage message is
370     *  returned.
371     *  @param configuration The configuration in which we are operating.
372     *  @return A URL that points to the results.
373     *  @exception Throwable If there is a problem invoking the about
374     *  task.
375     */
376    public static URL hyperlinkUpdate(HyperlinkEvent event,
377            Configuration configuration) throws Throwable {
378        URL newURL = null;
379
380        if (event.getDescription().equals("about:allcopyrights")) {
381            // Note that if we have a link that is
382            // <a href="about:copyright">about:allcopyrights</a>
383            // then event.getURL() will return null, so we have
384            // to use getDescription()
385            try {
386                newURL = _temporaryHTMLFile("generatecopyright", ".htm",
387                        GenerateCopyrights.generateHTML(configuration));
388            } catch (SecurityException ex) {
389                // Could be that we were running with -sandbox and
390                // cannot write the temporary file.
391                newURL = FileUtilities.nameToURL(
392                        "$CLASSPATH/ptolemy/configs/doc/copyright.htm", null,
393                        null);
394            }
395
396        } else if (event.getDescription()
397                .startsWith("about:checkCompleteDemos")) {
398            newURL = _temporaryHTMLFile("checkCompleteDemos", ".htm",
399                    checkCompleteDemos(
400                            "ptolemy/configs/doc/completeDemos.htm"));
401        } else if (event.getDescription().startsWith("about:checkModelSizes")) {
402            // Expand all the local .xml files in the fragment
403            // and check their sizes and locations
404            URI aboutURI = new URI(event.getDescription());
405            URL demosURL = _getDemoURL(aboutURI.getFragment());
406            // Third arg is true so modelList should contain absolute URLs.
407            List modelList = _getURLs(demosURL, ".*(.htm|.html|.xml)", true, 2);
408            // Convert the list to a Set and avoid duplicates.
409            Set modelSet = new HashSet(modelList);
410            newURL = _temporaryHTMLFile("checkModelSizes", ".htm",
411                    CheckModelSize.checkModelSize(configuration,
412                            (String[]) modelSet
413                                    .toArray(new String[modelSet.size()])));
414        } else if (event.getDescription().equals("about:copyright")) {
415            // Note that if we have a link that is
416            // <a href="about:copyright">about:copyright</a>
417            // then event.getURL() will return null, so we have
418            // to use getDescription()
419            try {
420                newURL = _temporaryHTMLFile("copyright", ".htm",
421                        GenerateCopyrights
422                                .generatePrimaryCopyrightHTML(configuration)
423                                + "<p>Other <a href=\"about:allcopyrights\">copyrights</a>\n"
424                                + "about this configuration \n"
425                                + "(<i>may take a moment to run</i>).\n"
426                                + "</body>\n</html>");
427            } catch (SecurityException ex) {
428                // Could be that we were running with -sandbox and
429                // cannot write the temporary file.
430                newURL = FileUtilities.nameToURL(
431                        "$CLASSPATH/ptolemy/configs/doc/copyright.htm", null,
432                        null);
433            }
434        } else if (event.getDescription().equals("about:configuration")) {
435            // about:configuration will expand the configuration
436            // and report any problems such as missing classes.
437            // Open up the configuration as a .txt file because if
438            // we open it up as a .xml file, we get a graphical browser
439            // that does not tell us much.  If we open it up as a .htm,
440            // then the output is confusing.
441            newURL = _temporaryHTMLFile("configuration", ".txt",
442                    configuration.check() + configuration.exportMoML());
443        } else if (event.getDescription().startsWith("about:demos")) {
444            // Expand all the local .xml files in the fragment
445            // and return a URL pointing to the fragment.
446            // If there is no fragment, then use
447            // "ptolemy/configs/doc/completeDemos.htm"
448            URI aboutURI = new URI(event.getDescription());
449            newURL = generateLinks(aboutURI.getFragment(), ".*.xml$",
450                    configuration);
451        } else if (event.getDescription().startsWith("about:links")) {
452            // Expand all the local .html, .htm, .pdf, .xml files in
453            // the fragment and return a URL pointing to the fragment.
454            // If there is no fragment, then use
455            // "ptolemy/configs/doc/completeDemos.htm"
456            URI aboutURI = new URI(event.getDescription());
457            newURL = generateLinks(aboutURI.getFragment(),
458                    "(ptdoc:.*|.*(.htm|.html|.pdf|.xml))", configuration);
459        } else if (event.getDescription().startsWith("about:runAllDemos")) {
460            URI aboutURI = new URI(event.getDescription());
461            newURL = runAllDemos(aboutURI.getFragment(), configuration);
462        } else if (event.getDescription().startsWith("about:expandLibrary")) {
463            //URI aboutURI = new URI(event.getDescription());
464            newURL = _expandLibrary(".*.xml", configuration);
465        } else {
466            // Display a message about the about: facility
467            newURL = _temporaryHTMLFile("about", ".htm", about(configuration));
468        }
469
470        return newURL;
471    }
472
473    /** Generate a file that contains urls of models.
474     *  @param args The optional name of the file containing the demos
475     *  followed by the optional name of the output file.  The default
476     *  demo file is ptolemy/configs/doc/completeDemos.htm, the default
477     *  output file is models.txt.
478     *  @exception IOException If there is a problem reading the demo
479     *  file or writing the model file.
480     */
481    public static void main(String[] args) throws IOException {
482        String demoFileName = "ptolemy/configs/doc/completeDemos.htm";
483        String outputFileName = "models.txt";
484
485        if (System.getenv("DISPLAY") != null
486                && System.getenv("DISPLAY").length() > 0) {
487            System.err.println(
488                    "Because of issues with webcam-capture hanging during discovery "
489                            + "it is best if HTMLAbout is invoked with DISPLAY=\"\".  "
490                            + "See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/Finalizers.");
491        }
492        if (args.length > 2) {
493            System.err.println("Usage: [demoFileName [outputFilename]\n"
494                    + "demoFileName defaults to " + demoFileName + "\n"
495                    + "outputFileName defaults to " + outputFileName + "\n");
496            StringUtilities.exit(3);
497        }
498        if (args.length >= 1) {
499            demoFileName = args[0];
500        }
501        if (args.length == 2) {
502            outputFileName = args[1];
503        }
504        System.out.println(
505                "Printing '.' for regular models, 'P' for models with LiveLinks.");
506        writeDemoURLs(demoFileName, outputFileName);
507        ptolemy.moml.MoMLSimpleApplication.closeVertx();
508    }
509
510    /** Run all the local .xml files that are linked to from an HTML file.
511     *  @param demosFileName The name of the HTML file that contains links
512     *  to the .xml files.  If this argument is the empty string, then
513     *  "ptolemy/configs/doc/completeDemos.htm" is used.
514     *  @param configuration  The configuration to run the files in.
515     *  @return the URL of the HTML file that was searched.
516     *  @exception Exception If there is a problem running a demo.
517     */
518    public static URL runAllDemos(String demosFileName,
519            Configuration configuration) throws Exception {
520        URL demosURL = _getDemoURL(demosFileName);
521        List modelList = _getURLs(demosURL, ".*.xml$");
522        Iterator models = modelList.iterator();
523
524        while (models.hasNext()) {
525            String model = (String) models.next();
526            URL modelURL = new URL(demosURL, model);
527
528            Tableau tableau = configuration.openModel(demosURL, modelURL,
529                    modelURL.toExternalForm());
530
531            if ((Effigy) tableau.getContainer() instanceof PtolemyEffigy) {
532                PtolemyEffigy effigy = (PtolemyEffigy) tableau.getContainer();
533                CompositeActor actor = (CompositeActor) effigy.getModel();
534
535                // Create a manager if necessary.
536                Manager manager = actor.getManager();
537
538                if (manager == null) {
539                    manager = new Manager(actor.workspace(), "manager");
540                    actor.setManager(manager);
541                }
542
543                //manager.addExecutionListener(this);
544                manager.execute();
545            }
546        }
547
548        return demosURL;
549    }
550
551    /** Write the urls of the demo urls.  The HTML file referred to by
552     * demoURLName is scanned for links to .xml files and for links to
553     * other .htm* files.  The children of demoURLName are scanned,
554     * but not the grandchildren.  The names of the demos will have
555     * $CLASSPATH/ prepended. This method is used to generate a list of all
556     * demos in ptolemy/configs/doc/models.txt.
557     * @param demosFileName The name of the demo file.
558     * @param outputFileName The name of the file that is generated.
559     * @exception IOException If there is a problem reading the demo file
560     * or writing the output file.
561     */
562    public static void writeDemoURLs(String demosFileName,
563            String outputFileName) throws IOException {
564        // Get PTII as C:/cxh/ptII
565        String ptII = null;
566        try {
567            ptII = new URI(StringUtilities.getProperty("ptolemy.ptII.dirAsURL"))
568                    .normalize().getPath();
569            // Under Windows, convert /C:/foo/bar to C:/foo/bar
570            ptII = new File(ptII).getCanonicalPath().replace('\\', '/');
571        } catch (URISyntaxException ex) {
572            throw new InternalErrorException(null, ex,
573                    "Failed to process PTII " + ptII);
574        }
575        if (ptII.length() == 0) {
576            throw new InternalErrorException("Failed to process "
577                    + "ptolemy.ptII.dirAsURL property, ptII = null?");
578        }
579        URL demoURL = ConfigurationApplication.specToURL(demosFileName);
580        List demosList = _getURLs(demoURL, ".*.xml", true, 2);
581        Set demosSet = new HashSet(demosList);
582        FileWriter fileWriter = null;
583        try {
584            fileWriter = new FileWriter(outputFileName);
585            Iterator demos = demosSet.iterator();
586            while (demos.hasNext()) {
587                String demo = (String) demos.next();
588                // Look for the value of $PTII and substitute in $CLASSPATH
589                // so that we can use FileUtilities.nameToURL() from within
590                // ptolemy.moml.filter.ActorIndex
591                fileWriter.write(
592                        StringUtilities.substitute(demo, ptII, "$CLASSPATH")
593                                + "\n");
594                try {
595                    // Open the model, look for any LiveLinks and write them.
596                    System.out.print(".");
597                    writeLiveLinks(fileWriter, demo, ptII);
598                } catch (Throwable throwable) {
599                    System.err.println("Warning: Could not open " + demo
600                            + ".  This means that any LiveLinks in that model "
601                            + "will not be added to the list of all demos. "
602                            + "This is not a big problem and can be safely ignored.: "
603                            + throwable);
604                }
605            }
606        } finally {
607            if (fileWriter != null) {
608                fileWriter.close();
609            }
610        }
611    }
612
613    /** Open the model, look for any LiveLinks and write their names.
614     *  @param fileWriter The FileWriter to write the file names to..
615     *  @param demo The string path to the demo to be searched for live links.
616     *  @param ptII The Ptolemy II home directory.
617     *  @exception Throwable If there is a problem opening the demo.
618     */
619    public static void writeLiveLinks(FileWriter fileWriter, String demo,
620            String ptII) throws Throwable {
621        // Read the model file and look for LiveLinks.  This may
622        // miss some LiveLinks that are not in the toplevel model file, but
623        // it is much faster than parsing each model.
624        boolean matches = false;
625        BufferedReader in = null;
626        try {
627            in = new BufferedReader(new InputStreamReader(
628                    new File(demo).toURI().toURL().openStream()));
629
630            String inputLine;
631
632            while ((inputLine = in.readLine()) != null) {
633                if (inputLine.matches(".*ptolemy.actor.gui.LiveLink.*")) {
634                    matches = true;
635                    break;
636                }
637            }
638        } finally {
639            if (in != null) {
640                in.close();
641            }
642        }
643
644        if (matches) {
645            Workspace workspace = new Workspace("MyWorkspace");
646            MoMLParser parser = new MoMLParser();
647            parser.resetAll();
648            List myFilters = BackwardCompatibility.allFilters();
649            MoMLParser.addMoMLFilters(myFilters, workspace);
650            System.out.print("P");
651            //System.out.println("Parsing " + demo);
652            NamedObj namedObj = parser.parseFile(demo);
653            if (namedObj instanceof CompositeEntity) {
654                CompositeEntity model = (CompositeEntity) namedObj;
655
656                // Look for LiveLinks inside regular models, where
657                // the LiveLink is inside an Attribute.
658                Enumeration attributes = model.getAttributes();
659                while (attributes.hasMoreElements()) {
660                    Object object = attributes.nextElement();
661                    // We could check only TextAttributes, that would
662                    // make a dependency on
663                    // ptolemy.vergil.kernel.attributes.TextAttribute.
664                    // Actor.gui does not depend on vergil.
665                    // This method is for LiveLink, which is in actor.gui,
666                    // so we should not add the dependency.
667                    if (object instanceof Attribute) {
668                        Enumeration innerAttributes = ((Attribute) object)
669                                .getAttributes();
670
671                        _writeLiveLinksAttributes(fileWriter, demo, ptII,
672                                innerAttributes);
673                    }
674                }
675
676                // Look for LiveLinks inside Ontologies, like
677                // $PTII/ptolemy/demo/ElectricPowerSystem/Overview.xml
678                // where the LiveLink is inside a FiniteConcept, which is
679                // a CompositeEntity.
680                Enumeration entities = model.getEntities();
681                while (entities.hasMoreElements()) {
682                    Object entity = entities.nextElement();
683                    if (entity instanceof NamedObj) {
684                        attributes = ((NamedObj) entity).getAttributes();
685                        _writeLiveLinksAttributes(fileWriter, demo, ptII,
686                                attributes);
687                    }
688                }
689                model.setContainer(null);
690            }
691        }
692    }
693
694    /** Look for LiveLinks in the Enumeration of attributes.
695     *  @param fileWriter The FileWriter to write the file names to..
696     *  @param demo The string path to the demo to be searched for live links.
697     *  @param ptII The Ptolemy II home directory.
698     *  @param innerAttributes An enumeration of attributes
699     *  @exception Throwable If there is a problem opening the demo.
700     */
701    private static void _writeLiveLinksAttributes(FileWriter fileWriter,
702            String demo, String ptII, Enumeration innerAttributes)
703            throws Throwable {
704        while (innerAttributes.hasMoreElements()) {
705            Object innerObject = innerAttributes.nextElement();
706            // System.out.println("Attribute: " + ((Attribute)innerObject).getFullName());
707            if (innerObject instanceof LiveLink) {
708                LiveLink liveLink = (LiveLink) innerObject;
709                File liveLinkFile = liveLink.asFile();
710                String liveLinkValue = liveLink.stringValue();
711
712                if (liveLinkValue.contains("CLASSPATH")
713                        && liveLinkValue.contains(".xml")) {
714                    // Look for the value of $PTII and substitute in $CLASSPATH
715                    // so that we can use FileUtilities.nameToURL() from within
716                    // ptolemy.moml.filter.ActorIndex
717                    String demoPath = StringUtilities.substitute(
718                            liveLinkFile.getCanonicalPath(), ptII,
719                            "$CLASSPATH");
720                    // Remove anything after the .xml so as to support
721                    // references to internal entities.
722                    demoPath = demoPath.substring(0,
723                            demoPath.lastIndexOf(".xml") + 4);
724                    // System.out.println( demo + ": contains a LiveLink: " + demoPath);
725                    fileWriter.write(demoPath + "\n");
726                } else if (!liveLinkValue.contains("/")
727                        && !liveLinkValue.contains("\\")
728                        && liveLinkValue.contains(".xml")) {
729                    // The link target is in the same directory as the model.
730                    // FIXME: It could be that models with relative LiveLinks will not be found when
731                    // the system is using jar files.
732                    String demoPath = StringUtilities.substitute(
733                            liveLinkFile.getCanonicalPath(), ptII,
734                            "$CLASSPATH");
735                    // Remove anything after the .xml.
736                    demoPath = demoPath.substring(0,
737                            demoPath.lastIndexOf(".xml") + 4);
738                    // System.out.println( demo + ": contains a LiveLink: " + demoPath);
739                    fileWriter.write(demoPath + "\n");
740                }
741            }
742        }
743    }
744
745    ///////////////////////////////////////////////////////////////////
746    ////                         private methods                   ////
747    // Return a string containing HTML with links the the about:demos
748    // and about:links pages
749    private static String _aboutHTML(String fileName) {
750        _demosURLs.add(fileName);
751        return "  <tr>\n" + "    <code>" + fileName + "</code>\n"
752                + "    <td><a href=\"about:demos#" + fileName
753                + "\">&nbsp;Open the .xml&nbsp;</a></td>\n"
754                + "    <td><a href=\"about:links#" + fileName
755                + "\">&nbsp;Open the ptdoc: .htm, .html, .xml and .pdf&nbsp;</a></td>\n"
756                + "    <td><a href=\"about:checkModelSizes#" + fileName
757                + "\">&nbsp;Check the sizes/centering of the models&nbsp;</a></td>\n"
758                // RunAllDemos does not work, it runs in the wrong thread?
759                // + "    <td><a href=\"about:runAllDemos#" + fileName
760                // + "\">&nbsp;Run all demos&nbsp;</a></td>\n"
761                + "  </tr>\n";
762    }
763
764    /** Expand the left hand library pane.  We search for a model,
765     *  first in the intro.htm file, then the complete demos page and
766     *  then on the Ptolemy website.  The model is opened and then the
767     *  left hand library pane is expanded.  This test is useful for
768     *  looking for problem with icons, such as icons that cause
769     *  ChangeRequests.
770     *  @param regexp The regular expression of the links we are interested
771     *  in.
772     *  @param configuration  The configuration to open the files in.
773     *  @return the URL of the HTML file that was searched.
774     *  @exception Exception If there is a problem opening a model.
775     */
776    public static URL _expandLibrary(String regexp, Configuration configuration)
777            throws Exception {
778        FileParameter aboutAttribute = (FileParameter) configuration
779                .getAttribute("_about", FileParameter.class);
780
781        URL baseURL = null;
782        URL modelURL = null;
783
784        // HyVisual, VisualSense: Search for models in the _about
785        // attribute of the configuration.
786        // If we don't have an _about, or _about returns
787        // no models, then look in the complete demos location
788
789        if (aboutAttribute != null) {
790            baseURL = aboutAttribute.asURL();
791            String aboutURLString = baseURL.toExternalForm();
792            String base = aboutURLString.substring(0,
793                    aboutURLString.lastIndexOf("/"));
794            baseURL = ConfigurationApplication.specToURL(base + "/intro.htm");
795            System.out.println(
796                    "HTMLAbout._expandLibrary(): looking in about URL: "
797                            + baseURL);
798            List modelList = _getURLs(baseURL, regexp);
799            if (modelList.size() > 0) {
800                // Get the first model and open it
801                String model = (String) modelList.get(0);
802                System.out.println(
803                        "HTMLAbout._expandLibrary(): looking for model relative to about URL: "
804                                + model);
805                modelURL = new URL(baseURL, model);
806            } else {
807                // Get the first url from intro.htm, look in it and get the
808                // first model
809                System.out.println("HTMLAbout._expandLibrary(): looking inside "
810                        + baseURL + " for .htm files");
811                List urlList = _getURLs(baseURL, ".*.htm");
812                Iterator urls = urlList.iterator();
813                while (urls.hasNext() && modelURL == null) {
814                    // Looping through files, looking for a model
815                    String model = (String) urls.next();
816                    System.out.println(
817                            "HTMLAbout._expandLibrary(): looking inside "
818                                    + model);
819                    URL possibleModelURL = new URL(baseURL, model);
820                    modelList = _getURLs(possibleModelURL, regexp);
821                    if (modelList.size() > 0) {
822                        model = (String) modelList.get(0);
823                        // Get the first model and open it
824                        System.out.println(
825                                "HTMLAbout._expandLibrary(): looking for model relative to first URL: "
826                                        + model);
827                        modelURL = new URL(baseURL, model);
828                    }
829                }
830            }
831        }
832        if (modelURL == null) {
833            // Get completeDemos.htm
834            baseURL = _getDemoURL(null);
835            System.out.println(
836                    "HTMLAbout._expandLibrary(): looking in completeDemos URL: "
837                            + baseURL);
838            List modelList = _getURLs(baseURL, regexp);
839            if (modelList.size() > 0) {
840                // Get the first model and open it
841                String model = (String) modelList.get(0);
842                System.out.println(
843                        "HTMLAbout._expandLibrary(): looking for model relative to completeDemos URL: "
844                                + model);
845                modelURL = new URL(baseURL, model);
846            } else {
847                String model = "http://ptolemy.eecs.berkeley.edu/ptolemyII/ptIIlatest/ptII/ptolemy/domains/sdf/demo/Butterfly/Butterfly.xml";
848                System.out.println(
849                        "HTMLAbout._expandLibrary(): looking for model relative to completeDemos URL: "
850                                + model);
851                modelURL = new URL(model);
852            }
853        }
854
855        System.out.println("HTMLAbout._expandLibrary(): baseURL: " + baseURL);
856        System.out.println("HTMLAbout._expandLibrary(): modelURL: " + modelURL);
857        Tableau tableau = configuration.openModel(baseURL, modelURL,
858                modelURL.toExternalForm());
859        final JFrame jFrame = tableau.getFrame();
860        //jFrame.show();
861
862        String errorMessage = "Expanding the library <b>should</b> result in expanding "
863                + "everything in the left hand tree pane. "
864                + "<p>If the left hand tree pane expands and then contracts, "
865                + "there is a problem with one of the leaves of the tree "
866                + "such as invoking a change request in an "
867                + "<i>XXX</i>Icon.xml. "
868                + "<p>The quickest way to find this is to restart vergil "
869                + "and expand each branch in the tree by hand.";
870        try {
871            ((PtolemyFrame) jFrame).expandAllLibraryRows();
872        } catch (Throwable throwable) {
873            throw new IllegalActionException(tableau, throwable,
874                    "Failed to expand library.\n" + errorMessage);
875        }
876
877        return _temporaryHTMLFile("expandLibrary", ".htm", errorMessage);
878
879    }
880
881    // Return the URL of the file that contains links to .xml files
882    private static URL _getDemoURL(String demosFileName) throws IOException {
883        // Open the completeDemos.htm file and read the contents into
884        // a String
885        if (demosFileName == null || demosFileName.length() == 0) {
886            demosFileName = "ptolemy/configs/doc/completeDemos.htm";
887        }
888
889        URL url = null;
890        try {
891            url = ConfigurationApplication.specToURL(demosFileName);
892        } catch (Exception ex) {
893            System.out
894                    .println("Warning: " + demosFileName + " not found: " + ex);
895        }
896        return url;
897    }
898
899    /** Open up a file, return a list of relative URLs that match a regexp.
900     *  @param demosURL The URL of the file containing URLs.
901     *  @param regexp The regular expression, for example ".*.xml$".
902     *  @return a list of relative URLS
903     */
904    private static List _getURLs(URL demosURL, String regexp)
905            throws IOException {
906        // Return relative URLS
907        return _getURLs(demosURL, regexp, false, 0);
908    }
909
910    /** Open up a file, return a list of relative or absolute URLs
911     *  that match a regexp.
912     *  @param demosURL The URL of the file containing URLs.
913     *  @param regexp The regular expression, for example ".*.xml$".
914     *  @param absoluteURLs True if we should return absolute URLs.
915     *  @param depth Depth to recurse.  Depth of 0 do not recurse.
916     *  Recursing only makes sense if the regexp argument includes .htm* files:
917     *  ".*(.htm|.html|.xml)"
918     *  @return a list of Strings naming absolute or relative URLs.
919     */
920    private static List _getURLs(URL demosURL, String regexp,
921            boolean absoluteURLs, int depth) throws IOException {
922        //System.out.println("HTMLAbout._getURLs(" + demosURL + ", " + regexp + ", " + absoluteURLs + ", " + depth);
923        StringBuffer demosBuffer = new StringBuffer();
924        BufferedReader in = null;
925        String demosURLParent = demosURL.toString().substring(0,
926                demosURL.toString().lastIndexOf("/") + 1);
927        try {
928            in = new BufferedReader(
929                    new InputStreamReader(demosURL.openStream()));
930
931            String inputLine;
932
933            while ((inputLine = in.readLine()) != null) {
934                demosBuffer.append(inputLine);
935            }
936        } catch (Exception ex) {
937            System.out.println(
938                    "HTMLAbout: failed to open " + demosURL + "\n" + ex);
939            return new LinkedList();
940        } finally {
941            if (in != null) {
942                in.close();
943            }
944        }
945
946        // demos contains the contents of the html file that has
947        // links to the demos we are interested in.
948        String demos = demosBuffer.toString();
949
950        // All the models we find go here.
951        List modelList = new LinkedList();
952
953        // Loop through the html file that contains links to the demos
954        // and pull out all the links by looking for href=" and then
955        // for the closing "
956        int modelStartIndex = demos.indexOf("href=\"");
957
958        while (modelStartIndex != -1) {
959            int modelEndIndex = demos.indexOf("\"", modelStartIndex + 6);
960
961            if (modelEndIndex != -1) {
962                String modelLink = demos.substring(modelStartIndex + 6,
963                        modelEndIndex);
964
965                if (!modelLink.startsWith("http://")
966                        && modelLink.matches(regexp)) {
967                    // If the link does not start with http://, but ends
968                    // with .xml, then we add it to the list
969                    String model = modelLink;
970                    //System.out.println("HTMLAbout: modelLink: " + modelLink);
971                    if (absoluteURLs) {
972                        model = demosURLParent + modelLink;
973                        Exception ex1 = null;
974                        try {
975                            model = new URI(demosURLParent + modelLink)
976                                    .normalize().getPath();
977                            // Under Windows, convert /C:/foo/bar to C:/foo/bar
978                            model = new File(model).toString().replace('\\',
979                                    '/');
980                        } catch (URISyntaxException ex) {
981                            ex1 = ex;
982                        } catch (NullPointerException ex2) {
983                            // model == null, probably a jar url in Webstart
984                        }
985                        if (model == null) {
986                            try {
987                                // Could be a jar url
988                                model = ConfigurationApplication
989                                        .specToURL(modelLink).toString();
990                            } catch (Exception ex) {
991                                if (modelLink.startsWith("/")) {
992                                    modelLink = modelLink.substring(1);
993                                    try {
994                                        model = ConfigurationApplication
995                                                .specToURL(modelLink)
996                                                .toString();
997                                    } catch (Exception ex2) {
998                                        System.out.println("Failed to look up "
999                                                + demosURLParent + modelLink
1000                                                + " and " + modelLink + "\n"
1001                                                + ex2);
1002                                    }
1003                                } else {
1004                                    String absoluteModelLink = demosURLParent
1005                                            + modelLink;
1006                                    try {
1007                                        model = ConfigurationApplication
1008                                                .specToURL(absoluteModelLink)
1009                                                .toString();
1010                                    } catch (Exception ex3) {
1011                                        System.out.println("Failed to look up "
1012                                                + demosURLParent + modelLink
1013                                                + " and " + modelLink + " and "
1014                                                + absoluteModelLink + "\n" + ex1
1015                                                + "\n" + ex3);
1016                                    }
1017
1018                                }
1019                            }
1020                        }
1021                    }
1022                    if (model != null) {
1023                        URL modelURL = null;
1024                        if (model.startsWith("jar:file:/")) {
1025                            modelURL = new URL(model);
1026                            //System.out.print("HTMLAbout._getURLs(): jar:file:/: " + model);
1027                        } else {
1028                            if (model.startsWith("file:/")) {
1029                                model = model.substring("file:/".length());
1030                            }
1031                            //System.out.print("HTMLAbout._getURLs(): file:/: " + model);
1032                            modelURL = new File(model).toURI().toURL();
1033                        }
1034                        //System.out.println(" " + modelURL);
1035                        boolean sawModel = modelList.contains(model);
1036                        if (!sawModel) {
1037                            modelList.add(model);
1038                            if (depth > 0 && model.matches(".*(.htm|.html)")) {
1039                                modelList.addAll(_getURLs(modelURL, regexp,
1040                                        absoluteURLs, depth - 1));
1041                            }
1042                        }
1043                    }
1044                }
1045            }
1046
1047            modelStartIndex = demos.indexOf("href=\"", modelEndIndex);
1048        }
1049
1050        return modelList;
1051    }
1052
1053    // Return true if a configuration can be found
1054    // @param configurationName The name of the configuration, for
1055    // example "full"
1056    // @return True if ptolemy/configs/<i>configuration</i>/configuration.xml
1057    // can be found
1058    private static boolean _configurationExists(String configurationName) {
1059        boolean configurationExists = false;
1060
1061        try {
1062            String spec = "ptolemy/configs/" + configurationName
1063                    + "/configuration.xml";
1064            URL url = ClassUtilities.getResource(spec);
1065            if (url != null) {
1066                configurationExists = true;
1067            }
1068        } catch (Throwable throwable) {
1069            // Ignored, the configuration does not exist.
1070        }
1071
1072        return configurationExists;
1073    }
1074
1075    // Save a string in a temporary html file and return a URL to it.
1076    // @param prefix The prefix string to be used in generating the temporary
1077    // file name; must be at least three characters long.
1078    // @param suffix The suffix string to be used in generating the temporary
1079    // file name.
1080    // @param contents  The contents of the temporary file
1081    // @return A URL pointing to a temporary file.
1082    /*private*/static URL _temporaryHTMLFile(String prefix, String suffix,
1083            String contents) throws IOException {
1084        // Generate a copyright page in a temporary file
1085        File temporaryFile = File.createTempFile(prefix, suffix);
1086        temporaryFile.deleteOnExit();
1087
1088        FileWriter fileWriter = null;
1089
1090        try {
1091            fileWriter = new FileWriter(temporaryFile);
1092            fileWriter.write(contents, 0, contents.length());
1093        } finally {
1094            if (fileWriter != null) {
1095                fileWriter.close();
1096            }
1097        }
1098
1099        return temporaryFile.toURI().toURL();
1100    }
1101
1102    ///////////////////////////////////////////////////////////////////
1103    ////                         private variables                 ////
1104
1105    private static List _demosURLs;
1106}