PageRenderTime 74ms CodeModel.GetById 3ms app.highlight 62ms RepoModel.GetById 2ms app.codeStats 0ms

/ptolemy/src/ptolemy/actor/gui/BrowserLauncher.java

https://github.com/Elblonko/kepler
Java | 977 lines | 517 code | 138 blank | 322 comment | 64 complexity | 7fb661e9314feba258c2acea36807d02 MD5 | raw file
  1/* Launch the user's default web browser.
  2
  3 Copyright (c) 2002-2013 The Regents of the University of California.
  4 All rights reserved.
  5 Permission is hereby granted, without written agreement and without
  6 license or royalty fees, to use, copy, modify, and distribute this
  7 software and its documentation for any purpose, provided that the above
  8 copyright notice and the following two paragraphs appear in all copies
  9 of this software.
 10
 11 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 12 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 13 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 14 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 15 SUCH DAMAGE.
 16
 17 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 18 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 19 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 20 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 21 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
 22 ENHANCEMENTS, OR MODIFICATIONS.
 23
 24 PT_COPYRIGHT_VERSION_2
 25 COPYRIGHTENDKEY
 26
 27 */
 28package ptolemy.actor.gui;
 29
 30import java.io.File;
 31import java.io.IOException;
 32import java.lang.reflect.Constructor;
 33import java.lang.reflect.Field;
 34import java.lang.reflect.InvocationTargetException;
 35import java.lang.reflect.Method;
 36import java.net.URL;
 37
 38import ptolemy.util.StringUtilities;
 39
 40/**
 41 BrowserLauncher is a class that provides one static method, openURL,
 42 which opens the default web browser for the current user of the system
 43 to the given URL.  It may support other protocols depending on the
 44 system -- mailto, ftp, etc. -- but that has not been rigorously tested
 45 and is not guaranteed to work.
 46
 47 <p> Yes, this is platform-specific code, and yes, it may rely on
 48 classes on certain platforms that are not part of the standard JDK.
 49 What we're trying to do, though, is to take something that's
 50 frequently desirable but inherently platform-specific -- opening a
 51 default browser -- and allow programmers (you, for example) to do so
 52 without worrying about dropping into native code or doing anything
 53 else similarly evil.</p>
 54
 55 <p> Anyway, this code is completely in Java and will run on all JDK
 56 1.1-compliant systems without modification or a need for additional
 57 libraries.  All classes that are required on certain platforms to
 58 allow this to run are dynamically loaded at runtime via reflection
 59 and, if not found, will not cause this to do anything other than
 60 returning an error when opening the browser.</p>
 61
 62
 63 <p> There are certain system requirements for this class, as it's
 64 running through Runtime.exec(), which is Java's way of making a
 65 native system call.  Currently, this requires that a Macintosh have a
 66 Finder which supports the GURL event, which is true for Mac OS 8.0
 67 and 8.1 systems that have the Internet Scripting AppleScript
 68 dictionary installed in the Scripting Additions folder in the
 69 Extensions folder (which is installed by default as far as I know
 70 under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later systems.
 71 On Windows, it only runs under Win32 systems (Windows 95, 98, and NT
 72 4.0, as well as later versions of all).  On other systems, this drops
 73 back from the inherently platform-sensitive concept of a default
 74 browser and simply attempts to launch Firefox via a shell command.</p>
 75
 76 <blockquote>
 77
 78 <p> This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu)
 79 and may be redistributed or modified in any form without restrictions as
 80 long as the portion of this comment from this paragraph through the end
 81 of the comment is not removed.  The author requests that he be notified
 82 of any application, applet, or other binary that makes use of this code,
 83 but that is more out of curiosity than anything and is not required.  This
 84 software includes no warranty.  The author is not responsible for any
 85 loss of data or functionality or any adverse or unexpected effects of
 86 using this software.</p>
 87
 88 <p>
 89 Credits:
 90
 91 <br>Steven Spencer, JavaWorld magazine (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java//Tip 66</a>)
 92
 93 <br>Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul
 94 Teitlebaum, Andrea Cantatore, Larry Barowski, Trevor Bedzek, Frank
 95 Miedrich, and Ron Rabakukk
 96 </p>
 97 </blockquote>
 98
 99 <p> On November 3, 2003, BrowserLauncher was downloaded from
100 <a href="http://browserlauncher.sourceforge.net/">http://browserlauncher.sourceforge.net/</a>.
101That URL no longer exists, there is a replacement package which is LGPL'd.
102See <a href="http://web.archive.org/web/20031028032907/http://browserlauncher.sourceforge.net/"><code>http://web.archive.org/web/20031028032907/http://browserlauncher.sourceforge.net/</code></a> for the original web page.</p>
103
104
105
106 @author Eric Albert (<a href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
107 @version $Id: BrowserLauncher.java 69510 2014-07-13 00:29:23Z cxh $, based on 1.4b1 (Released June 20, 2001)
108 @since Ptolemy II 3.0
109 @Pt.ProposedRating Red (cxh)
110 @Pt.AcceptedRating Red (cxh)
111 */
112public class BrowserLauncher {
113    /** Launch the browser on the first argument.  If there is
114     *  no first argument, then open http://ptolemy.eecs.berkeley.edu.
115     *  Second and subsequent arguments are ignored.
116     *  It is best if the first argument is an absolute URL
117     *  as opposed to a relative URL.
118     *
119     *  <p> For example, to open the user's default browser on
120     *  http://www.eecs.berkeley.edu
121     *  <pre>
122     *  java -classpath $PTII ptolemy.actor.gui.BrowserLauncher http://www.eecs.berkeley.edu
123     *  </pre>
124     *  @param args An array of command line arguments.  The first
125     *  argument names a URL to be opened.  If there is no first
126     *  argument, then open http://ptolemy.eecs.berkeley.edu.  Second
127     *  and subsequent arguments are ignored.
128     *  @exception Exception If there is a problem launching the browser.
129     */
130    public static void main(String[] args) throws Exception {
131        if (args.length >= 1) {
132            // Ignore any arguments after the first one.
133            BrowserLauncher.openURL(args[0]);
134        } else {
135            BrowserLauncher.openURL("http://ptolemy.eecs.berkeley.edu");
136        }
137
138        if (BrowserLauncher.delayExit) {
139            System.out.println("Delaying exit for 10 seconds because we"
140                    + "may have copied a jar: file");
141
142            try {
143                Thread.sleep(10000);
144            } catch (InterruptedException e) {
145            }
146
147            StringUtilities.exit(0);
148        }
149    }
150
151    /**
152     * Attempts to open the default web browser to the given URL.
153     *
154     * <p> We use the following strategy to find URLs that may be inside
155     * jar files:
156     * <br> If the string does not start with "http": see if it is a
157     * file.
158     * <br> If the file cannot be found, look it up in the classpath.
159     * <br> If the file can be found in the classpath then use the
160     * found file instead of the given URL.
161     * <br>If the file cannot be found in the classpath, then pass the
162     * original given URL to the browser.
163     * <p>If the ptolemy.ptII.browser property is set, then its value
164     * is used as the value of the browser.
165     * <br>To always use Internet Explorer, one might invoke Ptolemy
166     * with:
167     * <pre>
168     * java -classpath $PTII -Dptolemy.ptII.browser=c:\\Program\ Files\\Internet\ Explorer\\iexplore.exe ptolemy.vergil.VergilApplication
169     * </pre>
170     * <p>To always use Firefox:
171     * <pre>
172     * java -classpath $PTII -Dptolemy.ptII.browser=c:\\Program\ Files\\Mozilla\ Firefox\\firefox ptolemy.vergil.VergilApplication
173     * </pre>
174     *
175     * <p>To preserve your browser choice set the ptolemy.ptII.browser
176     * property in <code>$PTII/lib/ptII.properties</code>.  Note that
177     * each time <code>$PTII/bin/configure</code> is run,
178     * <code>$PTII/lib/ptII.properties.in</code> is read and
179     * <code>$PTII/lib/ptII.properties</code> is overwritten, so you
180     * may want to add your changes to
181co     * <code>$PTII/lib/ptII.properties.in</code>.
182     *
183     * @param url The URL to open.
184     *  It is best if the first argument is an absolute URL
185     *  as opposed to a relative URL.
186     * @exception IOException If the web browser could not be located or
187     * does not run
188     */
189    public static void openURL(String url) throws IOException {
190        if (!loadedWithoutErrors) {
191            throw new IOException("Exception in finding browser: "
192                    + errorMessage);
193        }
194
195        if (!url.startsWith("http:") && !url.startsWith("https:")) {
196            // If the url does not start with http:, then look it up
197            // as a regular file and then possibly in the classpath.
198            File urlFile = null;
199
200            try {
201                urlFile = new File(url);
202            } catch (Exception ex) {
203                // Ignored, we try to fix this below.
204                urlFile = null;
205            }
206
207            if (urlFile == null || !urlFile.exists()) {
208                // The file could not be found, so try the search path mambo.
209                // We might be in the Swing Event thread, so
210                // Thread.currentThread().getContextClassLoader()
211                // .getResource(entry) probably will not work.
212                String refClassName = "ptolemy.kernel.util.NamedObj";
213
214                try {
215                    Class refClass = Class.forName(refClassName);
216                    URL entryURL = refClass.getClassLoader().getResource(url);
217
218                    if (entryURL != null && !url.startsWith("jar:")) {
219                        System.out.println("BrowserLauncher: Could not "
220                                + "find '" + url + "', but '" + entryURL
221                                + "' was found.");
222                        url = entryURL.toString();
223                    } else {
224                        if (url.startsWith("jar:")) {
225                            // If the URL begins with jar: then we are
226                            // inside Web Start and we should get the
227                            // resource, write it to a temporary file
228                            // and pass that value to the browser
229                            // Save the jar file as a temporary file
230                            // in the default platform dependent
231                            // directory with the same suffix as that
232                            // of the jar URL
233                            // FIXME: we should probably cache this
234                            // copy somehow.
235                            String old = url;
236                            String temporaryURL = JNLPUtilities
237                                    .saveJarURLInClassPath(url);
238
239                            if (temporaryURL != null) {
240                                url = temporaryURL;
241                            } else {
242                                url = JNLPUtilities.saveJarURLAsTempFile(url,
243                                        "tmp", null, null);
244                                delayExit = true;
245                            }
246
247                            System.out.println("BrowserLauncher: Could not "
248                                    + "find '" + old + "', but jar url'" + url
249                                    + "' was found.");
250                        }
251                    }
252                } catch (ClassNotFoundException ex) {
253                    System.err.println("BrowserLauncher: Internal error, "
254                            + " Could not find " + refClassName);
255                }
256            }
257        }
258
259        if (!StringUtilities.getProperty("ptolemy.ptII.browser").equals("")) {
260            Runtime.getRuntime()
261                    .exec(new String[] {
262                            "\""
263                                    + StringUtilities
264                                            .getProperty("ptolemy.ptII.browser")
265                                    + "\"", url });
266            return;
267        }
268
269        Object browser = locateBrowser();
270
271        if (browser == null) {
272            throw new IOException("Unable to locate browser: " + errorMessage);
273        }
274
275        String args[];
276
277        switch (jvm) {
278        case MRJ_2_0:
279
280            errorMessage = "Command was a call to aeDescConstructor(" + url
281                    + ")";
282
283            Object aeDesc = null;
284
285            try {
286                aeDesc = aeDescConstructor.newInstance(new Object[] { url });
287                putParameter.invoke(browser, new Object[] { keyDirectObject,
288                        aeDesc });
289                sendNoReply.invoke(browser, new Object[] {});
290            } catch (InvocationTargetException ite) {
291                throw new IOException("InvocationTargetException while "
292                        + "creating AEDesc: " + ite.getMessage());
293            } catch (IllegalAccessException iae) {
294                throw new IOException("IllegalAccessException while "
295                        + "building AppleEvent: " + iae.getMessage());
296            } catch (InstantiationException ie) {
297                throw new IOException("InstantiationException while "
298                        + "creating AEDesc: " + ie.getMessage());
299            } finally {
300                // Encourage it to get disposed if it was created
301                aeDesc = null;
302
303                // Ditto
304                browser = null;
305            }
306
307            break;
308
309        case MRJ_2_1:
310            args = new String[] { (String) browser, url };
311            errorMessage = "Command was: " + args[0] + " " + args[1];
312            Runtime.getRuntime().exec(args);
313            break;
314
315        case MRJ_3_0:
316
317            errorMessage = "Command was a call to ICLaunchURL(" + url + ")";
318
319            int[] instance = new int[1];
320            int result = ICStart(instance, 0);
321
322            if (result == 0) {
323                int[] selectionStart = new int[] { 0 };
324                byte[] urlBytes = url.getBytes("UTF-8");
325                int[] selectionEnd = new int[] { urlBytes.length };
326                result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
327                        urlBytes.length, selectionStart, selectionEnd);
328
329                if (result == 0) {
330                    // Ignore the return value; the URL was launched
331                    // successfully regardless of what happens here.
332                    ICStop(instance);
333                } else {
334                    throw new IOException("Unable to launch URL: " + result);
335                }
336            } else {
337                throw new IOException("Unable to create an Internet "
338                        + "Config instance: " + result);
339            }
340
341            break;
342
343        case MRJ_3_1:
344            if (!url.startsWith("file:") && !url.startsWith("http:")
345                    && !url.startsWith("https:")) {
346                // Needed by Web Start file:
347                // ptII/ptolemy/domains/ct/demo/CartPendulum/CartPendulum.jnlp
348                url = new File(url).toURI().toURL().toString();
349            }
350
351            errorMessage = "Command was a call to openURL(" + url + ")";
352
353            try {
354                openURL.invoke(null, new Object[] { url });
355            } catch (InvocationTargetException ite) {
356                throw new IOException("InvocationTargetException while "
357                        + "calling openURL() on " + url + " "
358                        + ite.getMessage());
359            } catch (IllegalAccessException iae) {
360                throw new IOException("IllegalAccessException while "
361                        + "calling openURL() on: " + url + " "
362                        + iae.getMessage());
363            }
364
365            break;
366
367        case WINDOWS_NT:
368        case WINDOWS_9x:
369
370            // Add quotes around the URL to allow ampersands and other special
371            // characters to work.
372            args = new String[] { (String) browser, FIRST_WINDOWS_PARAMETER,
373                    SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER,
374                    '"' + url + '"' };
375            Process process = Runtime.getRuntime().exec(args);
376
377            errorMessage = "Command was: "
378                    + args[0]
379                    + " "
380                    + args[1]
381                    + " "
382                    + args[2]
383                    + " "
384                    + args[3]
385                    + " "
386                    + args[4]
387                    + "\nNote: Under Windows, make sure that the file named by "
388                    + "the url is executable.";
389
390            // The return code returned by process.waitFor()
391            // 0 usually indicates normal execution.
392            int exitCode = 0;
393            // This avoids a memory leak on some versions of Java on Windows.
394            // That's hinted at in
395            // <http://developer.java.sun.com/developer/qow/archive/68/>.
396            try {
397                exitCode = process.waitFor();
398                process.exitValue();
399            } catch (InterruptedException ie) {
400                throw new IOException("InterruptedException while "
401                        + "launching browser: " + ie.getMessage());
402            }
403
404            if (exitCode != 0) {
405                throw new IOException(
406                        "Process exec'd by BrowserLauncher returned "
407                                + exitCode + "." + "\n url was: " + url
408                                + "\n browser was: " + browser + "\n "
409                                + errorMessage);
410            }
411
412            break;
413
414        case OTHER:
415
416            // Assume that we're on Unix and that firefox is installed
417            // First, attempt to open the URL in a currently running
418            // session of Netscape
419            args = new String[] {
420                    (String) browser,
421                    NETSCAPE_REMOTE_PARAMETER,
422                    NETSCAPE_OPEN_PARAMETER_START + url
423                            + NETSCAPE_OPEN_PARAMETER_END };
424            process = Runtime.getRuntime().exec(args);
425
426            errorMessage = "Command was: " + args[0] + " " + args[1] + " "
427                    + args[2];
428
429            try {
430                if (process.waitFor() != 0) { // if Netscape was not open
431                    Runtime.getRuntime().exec(
432                            new String[] { (String) browser, url });
433                }
434            } catch (InterruptedException ie) {
435                throw new IOException("InterruptedException while "
436                        + "launching browser: " + ie.getMessage());
437            }
438
439            break;
440
441        default:
442
443            // This should never occur, but if it does, we'll try
444            // the simplest thing possible
445            Runtime.getRuntime().exec(
446                    new String[] { "\"" + (String) browser + "\"", url });
447            break;
448        }
449
450    }
451
452    /** Set to true if we copied a file out of a jar file so that
453     *  the browser could display it.  The reason we need this flag
454     *  is that the system will delete the temporary file on exit,
455     *  and after openURL() is called, this Java process will exit
456     *  unless we delay.
457     */
458    public static boolean delayExit = false;
459
460    /**
461     * The Java virtual machine that we are running on.  Actually, in
462     * most cases we only care about the operating system, but some
463     * operating systems require us to switch on the VM.
464     */
465    private static int jvm;
466
467    /** The browser for the system */
468    private static Object browser;
469
470    /**
471     * Caches whether any classes, methods, and fields that are not
472     * part of the JDK and need to be dynamically loaded at runtime
473     * loaded successfully.
474     * <p> Note that if this is <code>false</code>,
475     * <code>openURL()</code> will always return an IOException.
476     */
477    private static boolean loadedWithoutErrors;
478
479    /** The com.apple.mrj.MRJFileUtils class */
480    private static Class mrjFileUtilsClass;
481
482    /** The com.apple.mrj.MRJOSType class */
483    private static Class mrjOSTypeClass;
484
485    /** The com.apple.MacOS.AEDesc class */
486    private static Class aeDescClass;
487
488    /** The <init>(int) method of com.apple.MacOS.AETarget */
489    private static Constructor aeTargetConstructor;
490
491    /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
492    private static Constructor appleEventConstructor;
493
494    /** The <init>(String) method of com.apple.MacOS.AEDesc */
495    private static Constructor aeDescConstructor;
496
497    /** The findFolder method of com.apple.mrj.MRJFileUtils */
498    private static Method findFolder;
499
500    /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
501    private static Method getFileCreator;
502
503    /** The getFileType method of com.apple.mrj.MRJFileUtils */
504    private static Method getFileType;
505
506    /** The openURL method of com.apple.mrj.MRJFileUtils */
507    private static Method openURL;
508
509    /** The makeOSType method of com.apple.MacOS.OSUtils */
510    private static Method makeOSType;
511
512    /** The putParameter method of com.apple.MacOS.AppleEvent */
513    private static Method putParameter;
514
515    /** The sendNoReply method of com.apple.MacOS.AppleEvent */
516    private static Method sendNoReply;
517
518    /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
519    private static Object kSystemFolderType;
520
521    /** The keyDirectObject AppleEvent parameter type */
522    private static Integer keyDirectObject;
523
524    /** The kAutoGenerateReturnID AppleEvent code */
525    private static Integer kAutoGenerateReturnID;
526
527    /** The kAnyTransactionID AppleEvent code */
528    private static Integer kAnyTransactionID;
529
530    // The linkage object required for JDirect 3 on Mac OS X.
531    //private static Object linkage;
532
533    /** The framework to reference on Mac OS X */
534
535    //private static final String JDirect_MacOSX =
536    //"/System/Library/Frameworks/Carbon.framework/Frameworks/"
537    //+ "HIToolbox.framework/HIToolbox";
538    /** JVM constant for MRJ 2.0 */
539    private static final int MRJ_2_0 = 0;
540
541    /** JVM constant for MRJ 2.1 or later */
542    private static final int MRJ_2_1 = 1;
543
544    /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
545    private static final int MRJ_3_0 = 3;
546
547    /** JVM constant for MRJ 3.1 */
548    private static final int MRJ_3_1 = 4;
549
550    /** JVM constant for any Windows NT JVM */
551    private static final int WINDOWS_NT = 5;
552
553    /** JVM constant for any Windows 9x JVM */
554    private static final int WINDOWS_9x = 6;
555
556    /** JVM constant for any other platform */
557    private static final int OTHER = -1;
558
559    /**
560     * The file type of the Finder on a Macintosh.  Hardcoding
561     * "Finder" would keep non-U.S. English systems from working
562     * properly.
563     */
564    private static final String FINDER_TYPE = "FNDR";
565
566    /**
567     * The creator code of the Finder on a Macintosh, which is needed
568     * to send AppleEvents to the application.
569     */
570    private static final String FINDER_CREATOR = "MACS";
571
572    /** The name for the AppleEvent type corresponding to a GetURL event. */
573    private static final String GURL_EVENT = "GURL";
574
575    /**
576     * The first parameter that needs to be passed into Runtime.exec()
577     * to open the default web browser on Windows.
578     */
579    private static final String FIRST_WINDOWS_PARAMETER = "/c";
580
581    /** The second parameter for Runtime.exec() on Windows. */
582    private static final String SECOND_WINDOWS_PARAMETER = "start";
583
584    /**
585     * The third parameter for Runtime.exec() on Windows.  This is a "title"
586     * parameter that the command line expects.  Setting this parameter allows
587     * URLs containing spaces to work.
588     */
589    private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
590
591    /**
592     * The shell parameters for Netscape or firefox that opens a given
593     * URL in an already-open copy of Netscape or firefox on many
594     * command-line systems.
595     */
596    private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
597
598    private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
599
600    private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
601
602    /**
603     * The message from any exception thrown throughout the
604     * initialization process.
605     */
606    private static String errorMessage;
607
608    /**
609     * An initialization block that determines the operating system
610     * and loads the necessary runtime data.
611     */
612    static {
613        loadedWithoutErrors = true;
614
615        String osName = System.getProperty("os.name");
616
617        if (osName.startsWith("Mac OS")) {
618            String mrjVersion = System.getProperty("mrj.version");
619            if (mrjVersion == null) {
620                // Java build 1.7.0_04-ea-b11 does not have the mrj.version property set.
621                jvm = MRJ_3_1;
622            } else {
623                String majorMRJVersion = mrjVersion.substring(0, 3);
624
625                try {
626                    double version = Double.valueOf(majorMRJVersion)
627                            .doubleValue();
628
629                    if (version == 2) {
630                        jvm = MRJ_2_0;
631                    } else if (version >= 2.1 && version < 3) {
632                        // Assume that all 2.x versions of MRJ work the
633                        // same.  MRJ 2.1 actually works via
634                        // Runtime.exec() and 2.2 supports that but has an
635                        // openURL() method as well that we currently
636                        // ignore.
637                        jvm = MRJ_2_1;
638                    } else if (version == 3.0) {
639                        jvm = MRJ_3_0;
640                    } else if (version >= 3.1) {
641                        // Assume that all 3.1 and later versions of MRJ
642                        // work the same.
643                        jvm = MRJ_3_1;
644                    } else {
645                        loadedWithoutErrors = false;
646                        errorMessage = "Unsupported MRJ version: " + version;
647                    }
648                } catch (NumberFormatException nfe) {
649                    loadedWithoutErrors = false;
650                    errorMessage = "Invalid MRJ version: " + mrjVersion;
651                }
652            }
653        } else if (osName.startsWith("Windows")) {
654            if (osName.indexOf("9") != -1) {
655                jvm = WINDOWS_9x;
656            } else {
657                jvm = WINDOWS_NT;
658            }
659        } else {
660            jvm = OTHER;
661        }
662
663        if (loadedWithoutErrors) { // if we haven't hit any errors yet
664            loadedWithoutErrors = loadClasses();
665        }
666    }
667
668    /**
669     * This class should be never be instantiated; this just ensures so.
670     */
671    private BrowserLauncher() {
672    }
673
674    /**
675     * Methods required for Mac OS X.  The presence of native methods
676     * does not cause any problems on other platforms.
677     */
678    private native static int ICLaunchURL(int instance, byte[] hint,
679            byte[] data, int len, int[] selectionStart, int[] selectionEnd);
680
681    private native static int ICStart(int[] instance, int signature);
682
683    private native static int ICStop(int[] instance);
684
685    /**
686     * Called by a static initializer to load any classes, fields, and
687     * methods required at runtime to locate the user's web browser.
688     * @return <code>true</code> if all initialization succeeded
689     * <code>false</code> if any portion of the initialization failed
690     */
691    private static boolean loadClasses() {
692        switch (jvm) {
693        case MRJ_2_0:
694
695            try {
696                Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
697                Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
698                Class appleEventClass = Class
699                        .forName("com.apple.MacOS.AppleEvent");
700                Class aeClass = Class.forName("com.apple.MacOS.ae");
701                aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
702
703                aeTargetConstructor = aeTargetClass
704                        .getDeclaredConstructor(new Class[] { int.class });
705                appleEventConstructor = appleEventClass
706                        .getDeclaredConstructor(new Class[] { int.class,
707                                int.class, aeTargetClass, int.class, int.class });
708                aeDescConstructor = aeDescClass
709                        .getDeclaredConstructor(new Class[] { String.class });
710
711                makeOSType = osUtilsClass.getDeclaredMethod("makeOSType",
712                        new Class[] { String.class });
713                putParameter = appleEventClass.getDeclaredMethod(
714                        "putParameter", new Class[] { int.class, aeDescClass });
715                sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply",
716                        new Class[] {});
717
718                Field keyDirectObjectField = aeClass
719                        .getDeclaredField("keyDirectObject");
720                keyDirectObject = (Integer) keyDirectObjectField.get(null);
721
722                Field autoGenerateReturnIDField = appleEventClass
723                        .getDeclaredField("kAutoGenerateReturnID");
724                kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField
725                        .get(null);
726
727                Field anyTransactionIDField = appleEventClass
728                        .getDeclaredField("kAnyTransactionID");
729                kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
730            } catch (ClassNotFoundException cnfe) {
731                errorMessage = cnfe.getMessage();
732                return false;
733            } catch (NoSuchMethodException nsme) {
734                errorMessage = nsme.getMessage();
735                return false;
736            } catch (NoSuchFieldException nsfe) {
737                errorMessage = nsfe.getMessage();
738                return false;
739            } catch (IllegalAccessException iae) {
740                errorMessage = iae.getMessage();
741                return false;
742            }
743
744            break;
745
746        case MRJ_2_1:
747
748            try {
749                mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
750                mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
751
752                Field systemFolderField = mrjFileUtilsClass
753                        .getDeclaredField("kSystemFolderType");
754                kSystemFolderType = systemFolderField.get(null);
755                findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder",
756                        new Class[] { mrjOSTypeClass });
757                getFileCreator = mrjFileUtilsClass.getDeclaredMethod(
758                        "getFileCreator", new Class[] { File.class });
759                getFileType = mrjFileUtilsClass.getDeclaredMethod(
760                        "getFileType", new Class[] { File.class });
761            } catch (ClassNotFoundException cnfe) {
762                errorMessage = cnfe.getMessage();
763                return false;
764            } catch (NoSuchFieldException nsfe) {
765                errorMessage = nsfe.getMessage();
766                return false;
767            } catch (NoSuchMethodException nsme) {
768                errorMessage = nsme.getMessage();
769                return false;
770            } catch (SecurityException se) {
771                errorMessage = se.getMessage();
772                return false;
773            } catch (IllegalAccessException iae) {
774                errorMessage = iae.getMessage();
775                return false;
776            }
777
778            break;
779
780        case MRJ_3_0:
781
782            try {
783                Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
784                Constructor constructor = linker
785                        .getConstructor(new Class[] { Class.class });
786                /*linkage = */constructor
787                        .newInstance(new Object[] { BrowserLauncher.class });
788            } catch (ClassNotFoundException cnfe) {
789                errorMessage = cnfe.getMessage();
790                return false;
791            } catch (NoSuchMethodException nsme) {
792                errorMessage = nsme.getMessage();
793                return false;
794            } catch (InvocationTargetException ite) {
795                errorMessage = ite.getMessage();
796                return false;
797            } catch (InstantiationException ie) {
798                errorMessage = ie.getMessage();
799                return false;
800            } catch (IllegalAccessException iae) {
801                errorMessage = iae.getMessage();
802                return false;
803            }
804
805            break;
806
807        case MRJ_3_1:
808
809            try {
810                mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
811                openURL = mrjFileUtilsClass.getDeclaredMethod("openURL",
812                        new Class[] { String.class });
813            } catch (ClassNotFoundException cnfe) {
814                errorMessage = cnfe.getMessage();
815                return false;
816            } catch (NoSuchMethodException nsme) {
817                errorMessage = nsme.getMessage();
818                return false;
819            }
820
821            break;
822
823        default:
824            break;
825        }
826
827        return true;
828    }
829
830    /**
831     * Attempts to locate the default web browser on the local system.
832     * Caches results so it only locates the browser once for each use
833     * of this class per JVM instance.
834     *
835     * @return The browser for the system.  Note that this may not be
836     * what you would consider to be a standard web browser;
837     * instead, it's the application that gets called to open the
838     * default web browser.  In some cases, this will be a non-String
839     * object that provides the means of calling the default browser.
840     *
841     */
842    private static Object locateBrowser() {
843        if (browser != null) {
844            return browser;
845        }
846
847        switch (jvm) {
848        case MRJ_2_0:
849
850            try {
851                Integer finderCreatorCode = (Integer) makeOSType.invoke(null,
852                        new Object[] { FINDER_CREATOR });
853                Object aeTarget = aeTargetConstructor
854                        .newInstance(new Object[] { finderCreatorCode });
855                Integer gurlType = (Integer) makeOSType.invoke(null,
856                        new Object[] { GURL_EVENT });
857                Object appleEvent = appleEventConstructor
858                        .newInstance(new Object[] { gurlType, gurlType,
859                                aeTarget, kAutoGenerateReturnID,
860                                kAnyTransactionID });
861
862                // Don't set browser = appleEvent because then the
863                // next time we call locateBrowser(), we'll get the
864                // same AppleEvent, to which we'll already have added
865                // the relevant parameter. Instead, regenerate the
866                // AppleEvent every time.  There's probably a way to
867                // do this better; if any has any ideas, please let me
868                // know.
869                return appleEvent;
870            } catch (IllegalAccessException iae) {
871                browser = null;
872                errorMessage = iae.getMessage();
873                return browser;
874            } catch (InstantiationException ie) {
875                browser = null;
876                errorMessage = ie.getMessage();
877                return browser;
878            } catch (InvocationTargetException ite) {
879                browser = null;
880                errorMessage = ite.getMessage();
881                return browser;
882            }
883
884        case MRJ_2_1:
885
886            File systemFolder;
887
888            try {
889                systemFolder = (File) findFolder.invoke(null,
890                        new Object[] { kSystemFolderType });
891            } catch (IllegalArgumentException iare) {
892                browser = null;
893                errorMessage = iare.getMessage();
894                return browser;
895            } catch (IllegalAccessException iae) {
896                browser = null;
897                errorMessage = iae.getMessage();
898                return browser;
899            } catch (InvocationTargetException ite) {
900                browser = null;
901                errorMessage = ite.getTargetException().getClass() + ": "
902                        + ite.getTargetException().getMessage();
903                return browser;
904            }
905
906            String[] systemFolderFiles = systemFolder.list();
907
908            // Avoid a FilenameFilter because
909            // that can't be stopped mid-list
910            for (String systemFolderFile : systemFolderFiles) {
911                try {
912                    File file = new File(systemFolder, systemFolderFile);
913
914                    if (!file.isFile()) {
915                        continue;
916                    }
917
918                    // We're looking for a file with a creator code of
919                    // 'MACS' and a type of 'FNDR'.  Only requiring
920                    // the type results in non-Finder applications
921                    // being picked up on certain Mac OS 9 systems,
922                    // especially German ones, and sending a GURL
923                    // event to those applications results in a logout
924                    // under Multiple Users.
925                    Object fileType = getFileType.invoke(null,
926                            new Object[] { file });
927
928                    if (FINDER_TYPE.equals(fileType.toString())) {
929                        Object fileCreator = getFileCreator.invoke(null,
930                                new Object[] { file });
931
932                        if (FINDER_CREATOR.equals(fileCreator.toString())) {
933                            // Actually the Finder, but that's OK
934                            browser = file.toString();
935                            return browser;
936                        }
937                    }
938                } catch (IllegalArgumentException iare) {
939                    errorMessage = iare.getMessage();
940                    return null;
941                } catch (IllegalAccessException iae) {
942                    browser = null;
943                    errorMessage = iae.getMessage();
944                    return browser;
945                } catch (InvocationTargetException ite) {
946                    browser = null;
947                    errorMessage = ite.getTargetException().getClass() + ": "
948                            + ite.getTargetException().getMessage();
949                    return browser;
950                }
951            }
952
953            browser = null;
954            break;
955
956        case MRJ_3_0:
957        case MRJ_3_1:
958            browser = ""; // Return something non-null
959            break;
960
961        case WINDOWS_NT:
962            browser = "cmd.exe";
963            break;
964
965        case WINDOWS_9x:
966            browser = "command.com";
967            break;
968
969        case OTHER:
970        default:
971            browser = "firefox";
972            break;
973        }
974
975        return browser;
976    }
977}