001/* Launch the user's default web browser.
002
003 Copyright (c) 2002-2018 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.gui;
029
030import java.awt.Desktop;
031import java.io.File;
032import java.io.IOException;
033import java.net.URI;
034import java.net.URL;
035
036import ptolemy.util.StringUtilities;
037
038/**
039 BrowserLauncher is a class that provides one static method, openURL,
040 which opens the default web browser for the current user of the system
041 to the given URL.  It may support other protocols depending on the
042 system -- mailto, ftp, etc. -- but that has not been rigorously tested
043 and is not guaranteed to work.
044
045 @author Christopher Brooks
046 @version $Id$
047 @since Ptolemy II 3.0
048 @Pt.ProposedRating Red (cxh)
049 @Pt.AcceptedRating Red (cxh)
050 */
051public class BrowserLauncher {
052    /** Launch the browser on the first argument.  If there is
053     *  no first argument, then open http://ptolemy.eecs.berkeley.edu.
054     *  Second and subsequent arguments are ignored.
055     *  It is best if the first argument is an absolute URL
056     *  as opposed to a relative URL.
057     *
058     *  <p> For example, to open the user's default browser on
059     *  http://www.eecs.berkeley.edu
060     *  <pre>
061     *  java -classpath $PTII ptolemy.actor.gui.BrowserLauncher http://www.eecs.berkeley.edu
062     *  </pre>
063     *  @param args An array of command line arguments.  The first
064     *  argument names a URL to be opened.  If there is no first
065     *  argument, then open http://ptolemy.eecs.berkeley.edu.  Second
066     *  and subsequent arguments are ignored.
067     *  @exception Exception If there is a problem launching the browser.
068     */
069    public static void main(String[] args) throws Exception {
070        if (args.length >= 1) {
071            // Ignore any arguments after the first one.
072            BrowserLauncher.openURL(args[0]);
073        } else {
074            BrowserLauncher.openURL("http://ptolemy.eecs.berkeley.edu");
075        }
076
077        if (BrowserLauncher.delayExit) {
078            System.out.println("Delaying exit for 10 seconds because we"
079                    + "may have copied a jar: file");
080
081            try {
082                Thread.sleep(10000);
083            } catch (InterruptedException e) {
084            }
085
086            StringUtilities.exit(0);
087        }
088    }
089
090    /** Set to true if we copied a file out of a jar file so that the
091     *  browser could display it.  The reason we need this flag is
092     *  that the system will delete the temporary file on exit, and
093     *  after openURL() is called, this Java process will exit unless
094     *  we delay.
095     */
096    public static boolean delayExit = false;
097
098    /**
099     * Attempts to open the default web browser to the given URL.
100     *
101     * <p> We use the following strategy to find URLs that may be inside
102     * jar files:
103     * <br> If the string does not start with "http": see if it is a
104     * file.
105     * <br> If the file cannot be found, look it up in the classpath.
106     * <br> If the file can be found in the classpath then use the
107     * found file instead of the given URL.
108     * <br>If the file cannot be found in the classpath, then pass the
109     * original given URL to the browser.
110     * <p>If the ptolemy.ptII.browser property is set, then its value
111     * is used as the value of the browser.
112     * <br>To always use Internet Explorer, one might invoke Ptolemy
113     * with:
114     * <pre>
115     * java -classpath $PTII -Dptolemy.ptII.browser=c:\\Program\ Files\\Internet\ Explorer\\iexplore.exe ptolemy.vergil.VergilApplication
116     * </pre>
117     * <p>To always use Firefox:
118     * <pre>
119     * java -classpath $PTII -Dptolemy.ptII.browser=c:\\Program\ Files\\Mozilla\ Firefox\\firefox ptolemy.vergil.VergilApplication
120     * </pre>
121     *
122     * @param url The URL to open.
123     *  It is best if the first argument is an absolute URL
124     *  as opposed to a relative URL.
125     * @exception IOException If the web browser could not be located or
126     * does not run
127     */
128    public static void openURL(String url) throws IOException {
129        if (!url.startsWith("http:") && !url.startsWith("https:")) {
130            // If the url does not start with http:, then look it up
131            // as a regular file and then possibly in the classpath.
132            File urlFile = null;
133
134            try {
135                urlFile = new File(url);
136            } catch (Exception ex) {
137                // Ignored, we try to fix this below.
138                urlFile = null;
139            }
140
141            if (urlFile == null || !urlFile.exists()) {
142                // The file could not be found, so try the search path mambo.
143                // We might be in the Swing Event thread, so
144                // Thread.currentThread().getContextClassLoader()
145                // .getResource(entry) probably will not work.
146                String refClassName = "ptolemy.kernel.util.NamedObj";
147
148                try {
149                    Class refClass = Class.forName(refClassName);
150                    URL entryURL = refClass.getClassLoader().getResource(url);
151
152                    if (entryURL != null && !url.startsWith("jar:")) {
153                        System.out.println("BrowserLauncher: Could not "
154                                + "find '" + url + "', but '" + entryURL
155                                + "' was found.");
156                        url = entryURL.toString();
157                    } else {
158                        if (url.startsWith("jar:")) {
159                            // If the URL begins with jar: then we are
160                            // inside Web Start and we should get the
161                            // resource, write it to a temporary file
162                            // and pass that value to the browser
163                            // Save the jar file as a temporary file
164                            // in the default platform dependent
165                            // directory with the same suffix as that
166                            // of the jar URL
167                            // FIXME: we should probably cache this
168                            // copy somehow.
169                            String old = url;
170                            String temporaryURL = JNLPUtilities
171                                    .saveJarURLInClassPath(url);
172
173                            if (temporaryURL != null) {
174                                url = temporaryURL;
175                            } else {
176                                url = JNLPUtilities.saveJarURLAsTempFile(url,
177                                        "tmp", null, null);
178                                delayExit = true;
179                            }
180
181                            System.out.println("BrowserLauncher: Could not "
182                                    + "find '" + old + "', but jar url'" + url
183                                    + "' was found.");
184                        }
185                    }
186                } catch (ClassNotFoundException ex) {
187                    System.err.println("BrowserLauncher: Internal error, "
188                            + " Could not find " + refClassName);
189                }
190            }
191        }
192
193        if (Desktop.isDesktopSupported()) {
194            Desktop desktop = Desktop.getDesktop();
195            URI uri = null;
196            try {
197                uri = new URI(url);
198            } catch (Throwable throwable) {
199                ;
200                IOException exception = new IOException(
201                        "Failed to convert url \"" + url + "\" to a uri.");
202                exception.initCause(throwable);
203                throw exception;
204            }
205            boolean invokeByHand = false;
206            try {
207                desktop.browse(uri);
208                return;
209            } catch (IOException ex) {
210                // System.out.println("BrowserLauncher: Failed to open " + uri + ": " + ex);
211                // ex.printStackTrace();
212                File temporaryFile = JNLPUtilities
213                        .getResourceSaveJarURLAsTempFile(uri.getPath());
214                try {
215                    desktop.browse(temporaryFile.toURI());
216                    return;
217                } catch (IOException ex2) {
218                    System.out.println(
219                            "BrowserLauncher: Failed to open " + temporaryFile
220                                    + ": " + ex2 + "\nAlso tried " + uri);
221                }
222
223                invokeByHand = true;
224            } catch (UnsupportedOperationException ex3) {
225                System.out.println("BrowserLauncher: UnsupportedOperation: "
226                        + uri + ": " + ex3);
227
228                invokeByHand = true;
229            }
230
231            if (invokeByHand) {
232                String errorMessage = "";
233                try {
234                    // Under Linux, desktop.browse() may fail with:
235
236                    // java.lang.UnsupportedOperationException: The
237                    // BROWSE action is not supported on the current
238                    // platform!
239
240                    // So, we start up Firefox.
241
242                    String browser = "firefox";
243                    String args[] = null;
244                    String osName = System.getProperty("os.name");
245                    if (osName.startsWith("Windows")) {
246                        browser = "cmd.exe";
247                        args = new String[] { browser, "/c", "start", "\"\"",
248                                '"' + url + '"' };
249                        Process process = Runtime.getRuntime().exec(args);
250                        errorMessage = "Command was: " + args[0] + " " + args[1]
251                                + " " + args[2] + " " + args[3] + " " + args[4]
252                                + ". "
253                                + "Under Windows check that the file named by "
254                                + url + " is executable.";
255                        int exitCode = 0;
256                        try {
257                            exitCode = process.waitFor();
258                            process.exitValue();
259                        } catch (InterruptedException ie) {
260                            throw new IOException("InterruptedException while "
261                                    + "launching " + browser + ": " + ie);
262                        }
263
264                        if (exitCode != 0) {
265                            throw new IOException(
266                                    "Process exec'd by BrowserLauncher returned "
267                                            + exitCode + ". \nUrl was: " + url
268                                            + "\nBrowser was: " + errorMessage);
269                        }
270
271                    } else {
272                        browser = "firefox";
273                        File macFirefox = new File(
274                                "/Applications/Firefox.app/Contents/MacOS/firefox");
275                        if (macFirefox.exists()) {
276                            browser = macFirefox.getCanonicalPath();
277                        }
278                        args = new String[] { browser, "-remote",
279                                "'openURL(" + url + ")'" };
280                        Process process = Runtime.getRuntime().exec(args);
281
282                        errorMessage = "Command was: " + args[0] + " " + args[1]
283                                + " " + args[2];
284
285                        try {
286                            // If Firefox is not open then try invoking the browser.
287                            if (process.waitFor() != 0) {
288                                if (osName.startsWith("Mac OS X")) {
289                                    browser = "safari";
290                                }
291
292                                Runtime.getRuntime()
293                                        .exec(new String[] { browser, url });
294                            }
295                        } catch (InterruptedException ie) {
296                            throw new IOException("InterruptedException while "
297                                    + "launching " + browser + ": " + ie);
298                        }
299                        return;
300                    }
301                } catch (Throwable throwable) {
302                    IOException exception = new IOException(
303                            "Failed to open \"" + uri + "\".  " + errorMessage);
304                    exception.initCause(throwable);
305                    throw exception;
306                }
307
308            }
309        } else {
310            throw new IOException(
311                    "java.awt.Desktop is not supported on this platform, so we can't open \""
312                            + url + "\"");
313        }
314
315    }
316
317    /**
318     * This class should be never be instantiated; this just ensures so.
319     */
320    private BrowserLauncher() {
321    }
322}