View Javadoc

1   //package edu.stanford.ejalbert;
2   package net.wotonomy.web.util;
3   
4   import java.io.BufferedReader;
5   import java.io.InputStreamReader;
6   import java.io.File;
7   import java.io.IOException;
8   import java.lang.reflect.Constructor;
9   import java.lang.reflect.Field;
10  import java.lang.reflect.InvocationTargetException;
11  import java.lang.reflect.Method;
12  
13  /*** <p> BrowserLauncher is a class that provides one static method,
14   * openURL, which opens the default web browser for the current user
15   * of the system to the given URL.  It may support other protocols
16   * depending on the system -- mailto, ftp, etc. -- but that has not
17   * been rigorously tested and is not guaranteed to work. </p>
18   *
19   * <p> Yes, this is platform-specific code, and yes, it may rely on
20   * classes on certain platforms that are not part of the standard JDK.
21   * What we're trying to do, though, is to take something that's
22   * frequently desirable but inherently platform-specific -- opening a
23   * default browser -- and allow programmers (you, for example) to do
24   * so without worrying about dropping into native code or doing
25   * anything else similarly evil. </p>
26   *
27   * <p> Anyway, this code is completely in Java and will run on all JDK
28   * 1.1-compliant systems without modification or a need for additional
29   * libraries.  All classes that are required on certain platforms to
30   * allow this to run are dynamically loaded at runtime via reflection
31   * and, if not found, will not cause this to do anything other than
32   * returning an error when opening the browser. </p>
33   *
34   * <p> There are certain system requirements for this class, as it's
35   * running through Runtime.exec(), which is Java's way of making a
36   * native system call.  Currently, this requires that a Macintosh have
37   * a Finder which supports the GURL event, which is true for Mac OS
38   * 8.0 and 8.1 systems that have the Internet Scripting AppleScript
39   * dictionary installed in the Scripting Additions folder in the
40   * Extensions folder (which is installed by default as far as I know
41   * under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later
42   * systems.  On Windows, it only runs under Win32 systems (Windows 95,
43   * 98, and NT 4.0, as well as later versions of all).  On other
44   * systems, this drops back from the inherently platform-sensitive
45   * concept of a default browser and simply attempts to launch Netscape
46   * via a shell command. </p>
47   *
48   * <p> This code is Copyright 1999-2001 by Eric Albert
49   * (ejalbert@cs.stanford.edu) and may be redistributed or modified in
50   * any form without restrictions as long as the portion of this
51   * comment from this paragraph through the end of the comment is not
52   * removed.  The author requests that he be notified of any
53   * application, applet, or other binary that makes use of this code,
54   * but that's more out of curiosity than anything and is not required.
55   * This software includes no warranty.  The author is not repsonsible
56   * for any loss of data or functionality or any adverse or unexpected
57   * effects of using this software. </p>
58   *
59   * Credits: <br> Steven Spencer, JavaWorld magazine (<a
60   * href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java
61   * Tip 66</a>) <br> Thanks also to Ron B. Yeh, Eric Shapiro, Ben
62   * Engber, Paul Teitlebaum, Andrea Cantatore, Larry Barowski, Trevor
63   * Bedzek, Frank Miedrich, and Ron Rabakukk
64   *
65   *
66   * original author Eric Albert (<a
67   * href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
68   * author's version 1.4b1 (Released June 20, 2001)
69   * @author Copyright 2001, Intersect Software Corporation
70   * @version $Revision: 905 $ $Date: 2006-02-19 01:44:03 +0000 (Sun, 19 Feb 2006) $
71   */
72  
73  public class BrowserLauncher {
74      /*** The Java virtual machine that we are running on.  Actually, in
75       * most cases we only care about the operating system, but some
76       * operating systems require us to switch on the VM. */
77      private static int jvm;
78  
79      /*** The browser for the system */
80      private static Object browser;
81  
82      /*** Caches whether any classes, methods, and fields that are not
83       * part of the JDK and need to be dynamically loaded at runtime
84       * loaded successfully.  <p> Note that if this is
85       * <code>false</code>, <code>openURL()</code> will always return
86       * an IOException.  */
87      private static boolean loadedWithoutErrors;
88  
89      /*** The com.apple.mrj.MRJFileUtils class */
90      private static Class mrjFileUtilsClass;
91  
92      /*** The com.apple.mrj.MRJOSType class */
93      private static Class mrjOSTypeClass;
94  
95      /*** The com.apple.MacOS.AEDesc class */
96      private static Class aeDescClass;
97  
98      /*** The <init>(int) method of com.apple.MacOS.AETarget */
99      private static Constructor aeTargetConstructor;
100 
101     /*** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
102     private static Constructor appleEventConstructor;
103 
104     /*** The <init>(String) method of com.apple.MacOS.AEDesc */
105     private static Constructor aeDescConstructor;
106 
107     /*** The findFolder method of com.apple.mrj.MRJFileUtils */
108     private static Method findFolder;
109 
110     /*** The getFileCreator method of com.apple.mrj.MRJFileUtils */
111     private static Method getFileCreator;
112 
113     /*** The getFileType method of com.apple.mrj.MRJFileUtils */
114     private static Method getFileType;
115 
116     /*** The openURL method of com.apple.mrj.MRJFileUtils */
117     private static Method openURL;
118 
119     /*** The makeOSType method of com.apple.MacOS.OSUtils */
120     private static Method makeOSType;
121 
122     /*** The putParameter method of com.apple.MacOS.AppleEvent */
123     private static Method putParameter;
124 
125     /*** The sendNoReply method of com.apple.MacOS.AppleEvent */
126     private static Method sendNoReply;
127 
128     /*** Actually an MRJOSType pointing to the System Folder on a Macintosh */
129     private static Object kSystemFolderType;
130 
131     /*** The keyDirectObject AppleEvent parameter type */
132     private static Integer keyDirectObject;
133 
134     /*** The kAutoGenerateReturnID AppleEvent code */
135     private static Integer kAutoGenerateReturnID;
136 
137     /*** The kAnyTransactionID AppleEvent code */
138     private static Integer kAnyTransactionID;
139 
140     /*** The linkage object required for JDirect 3 on Mac OS X. */
141     private static Object linkage;
142 
143     /*** The framework to reference on Mac OS X */
144     private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
145 
146     /*** JVM constant for MRJ 2.0 */
147     private static final int MRJ_2_0 = 0;
148 
149     /*** JVM constant for MRJ 2.1 or later */
150     private static final int MRJ_2_1 = 1;
151 
152     /*** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
153     private static final int MRJ_3_0 = 3;
154 
155     /*** JVM constant for MRJ 3.1 */
156     private static final int MRJ_3_1 = 4;
157 
158     /*** JVM constant for any Windows NT JVM */
159     private static final int WINDOWS_NT = 5;
160 
161     /*** JVM constant for any Windows 9x JVM */
162     private static final int WINDOWS_9x = 6;
163 
164     /*** JVM constant for any other platform */
165     private static final int OTHER = -1;
166 
167     /*** The file type of the Finder on a Macintosh.  Hardcoding
168      * "Finder" would keep non-U.S. English systems from working
169      * properly.  */
170     private static final String FINDER_TYPE = "FNDR";
171 
172     /*** The creator code of the Finder on a Macintosh, which is needed
173      * to send AppleEvents to the application.  */
174     private static final String FINDER_CREATOR = "MACS";
175 
176     /*** The name for the AppleEvent type corresponding to a GetURL event. */
177     private static final String GURL_EVENT = "GURL";
178 
179     /*** The first parameter that needs to be passed into
180      * Runtime.exec() to open the default web browser on Windows.  */
181     private static final String FIRST_WINDOWS_PARAMETER = "/c";
182 
183     /*** The second parameter for Runtime.exec() on Windows. */
184     private static final String SECOND_WINDOWS_PARAMETER = "start";
185 
186     /*** The third parameter for Runtime.exec() on Windows.  This is a
187      * "title" parameter that the command line expects.  Setting this
188      * parameter allows URLs containing spaces to work.  */
189     private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
190 
191     /*** The shell parameters for Netscape that opens a given URL in an
192      * already-open copy of Netscape on many command-line systems.  */
193     private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
194     //private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
195     //private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
196     private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL(";
197     private static final String NETSCAPE_OPEN_PARAMETER_END = ")";
198 
199     /*** The message from any exception thrown throughout the
200      * initialization process.  */
201     private static String errorMessage;
202 
203     /*** An initialization block that determines the operating system
204      * and loads the necessary runtime data.  */
205     static {
206         loadedWithoutErrors = true;
207         String osName = System.getProperty("os.name");
208         if (osName.startsWith("Mac OS")) {
209             String mrjVersion = System.getProperty("mrj.version");
210             String majorMRJVersion = mrjVersion.substring(0, 3);
211             try {
212                 double version = Double.valueOf(majorMRJVersion).doubleValue();
213                 if (version == 2) {
214                     jvm = MRJ_2_0;
215                 } else if (version >= 2.1 && version < 3) {
216                     // Assume that all 2.x versions of MRJ work the
217                     // same.  MRJ 2.1 actually works via
218                     // Runtime.exec() and 2.2 supports that but has an
219                     // openURL() method as well that we currently
220                     // ignore.
221                     jvm = MRJ_2_1;
222                 } else if (version == 3.0) {
223                     jvm = MRJ_3_0;
224                 } else if (version >= 3.1) {
225                     // Assume that all 3.1 and later versions of MRJ
226                     // work the same.
227                     jvm = MRJ_3_1;
228                 } else {
229                     loadedWithoutErrors = false;
230                     errorMessage = "Unsupported MRJ version: " + version;
231                 }
232             } catch (NumberFormatException nfe) {
233                 loadedWithoutErrors = false;
234                 errorMessage = "Invalid MRJ version: " + mrjVersion;
235             }
236         } else if (osName.startsWith("Windows")) {
237             if (osName.indexOf("9") != -1) {
238                 jvm = WINDOWS_9x;
239             } else {
240                 jvm = WINDOWS_NT;
241             }
242         } else {
243             jvm = OTHER;
244         }
245 
246         if (loadedWithoutErrors) {	// if we haven't hit any errors yet
247             loadedWithoutErrors = loadClasses();
248         }
249     }
250 
251     /*** This class should be never be instantiated; this just ensures so. */
252     private BrowserLauncher() { }
253 
254     /*** Called by a static initializer to load any classes, fields,
255      * and methods required at runtime to locate the user's web
256      * browser.
257      * @return <code>true</code> if all intialization succeeded
258      * <code>false</code> if any portion of the initialization failed */
259     private static boolean loadClasses() {
260         switch (jvm) {
261         case MRJ_2_0:
262             try {
263                 Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
264                 Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
265                 Class appleEventClass = Class.forName
266                     ("com.apple.MacOS.AppleEvent");
267                 Class aeClass = Class.forName("com.apple.MacOS.ae");
268                 aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
269 
270                 aeTargetConstructor = aeTargetClass.getDeclaredConstructor
271                     (new Class [] { int.class });
272                 appleEventConstructor = appleEventClass.getDeclaredConstructor
273                     (new Class[] { int.class, int.class, aeTargetClass,
274                                    int.class, int.class });
275                 aeDescConstructor = aeDescClass.getDeclaredConstructor
276                     (new Class[] { String.class });
277 
278                 makeOSType = osUtilsClass.getDeclaredMethod
279                     ("makeOSType", new Class [] { String.class });
280                 putParameter = appleEventClass.getDeclaredMethod
281                     ("putParameter", new Class[] { int.class, aeDescClass });
282                 sendNoReply = appleEventClass.getDeclaredMethod
283                     ("sendNoReply", new Class[] { });
284 
285                 Field keyDirectObjectField = aeClass.getDeclaredField
286                     ("keyDirectObject");
287                 keyDirectObject = (Integer) keyDirectObjectField.get(null);
288                 Field autoGenerateReturnIDField = appleEventClass
289                     .getDeclaredField("kAutoGenerateReturnID");
290                 kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField
291                     .get(null);
292                 Field anyTransactionIDField = appleEventClass.getDeclaredField
293                     ("kAnyTransactionID");
294                 kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
295             } catch (ClassNotFoundException cnfe) {
296                 errorMessage = cnfe.getMessage();
297                 return false;
298             } catch (NoSuchMethodException nsme) {
299                 errorMessage = nsme.getMessage();
300                 return false;
301             } catch (NoSuchFieldException nsfe) {
302                 errorMessage = nsfe.getMessage();
303                 return false;
304             } catch (IllegalAccessException iae) {
305                 errorMessage = iae.getMessage();
306                 return false;
307             }
308             break;
309         case MRJ_2_1:
310             try {
311                 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
312                 mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
313                 Field systemFolderField = mrjFileUtilsClass.getDeclaredField
314                     ("kSystemFolderType");
315                 kSystemFolderType = systemFolderField.get(null);
316                 findFolder = mrjFileUtilsClass.getDeclaredMethod
317                     ("findFolder", new Class[] { mrjOSTypeClass });
318                 getFileCreator = mrjFileUtilsClass.getDeclaredMethod
319                     ("getFileCreator", new Class[] { File.class });
320                 getFileType = mrjFileUtilsClass.getDeclaredMethod
321                     ("getFileType", new Class[] { File.class });
322             } catch (ClassNotFoundException cnfe) {
323                 errorMessage = cnfe.getMessage();
324                 return false;
325             } catch (NoSuchFieldException nsfe) {
326                 errorMessage = nsfe.getMessage();
327                 return false;
328             } catch (NoSuchMethodException nsme) {
329                 errorMessage = nsme.getMessage();
330                 return false;
331             } catch (SecurityException se) {
332                 errorMessage = se.getMessage();
333                 return false;
334             } catch (IllegalAccessException iae) {
335                 errorMessage = iae.getMessage();
336                 return false;
337             }
338             break;
339         case MRJ_3_0:
340             try {
341                 Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
342                 Constructor constructor = linker.getConstructor
343                     (new Class[]{ Class.class });
344                 linkage = constructor.newInstance(new Object[]
345                     { BrowserLauncher.class });
346             } catch (ClassNotFoundException cnfe) {
347                 errorMessage = cnfe.getMessage();
348                 return false;
349             } catch (NoSuchMethodException nsme) {
350                 errorMessage = nsme.getMessage();
351                 return false;
352             } catch (InvocationTargetException ite) {
353                 errorMessage = ite.getMessage();
354                 return false;
355             } catch (InstantiationException ie) {
356                 errorMessage = ie.getMessage();
357                 return false;
358             } catch (IllegalAccessException iae) {
359                 errorMessage = iae.getMessage();
360                 return false;
361             }
362             break;
363         case MRJ_3_1:
364             try {
365                 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
366                 openURL = mrjFileUtilsClass.getDeclaredMethod
367                     ("openURL", new Class[] { String.class });
368             } catch (ClassNotFoundException cnfe) {
369                 errorMessage = cnfe.getMessage();
370                 return false;
371             } catch (NoSuchMethodException nsme) {
372                 errorMessage = nsme.getMessage();
373                 return false;
374             }
375             break;
376         default:
377             break;
378         }
379         return true;
380     }
381 
382     /*** Attempts to locate the default web browser on the local
383      * system.  Caches results so it only locates the browser once *
384      * for each use of this class per JVM instance.
385      * @return The browser for the system.  Note that this may not be
386      * what you would consider to be a standard web browser; instead,
387      * it's the application that gets called to open the default web
388      * browser.  In some cases, this will be a non-String object that
389      * provides the means of calling the default browser. */
390     private static Object locateBrowser() {
391         if (browser != null) {
392             return browser;
393         }
394         switch (jvm) {
395         case MRJ_2_0:
396             try {
397                 Integer finderCreatorCode = (Integer) makeOSType.invoke
398                     (null, new Object[] { FINDER_CREATOR });
399                 Object aeTarget = aeTargetConstructor.newInstance
400                     (new Object[] { finderCreatorCode });
401                 Integer gurlType = (Integer) makeOSType.invoke
402                     (null, new Object[] { GURL_EVENT });
403                 Object appleEvent = appleEventConstructor.newInstance
404                     (new Object[] { gurlType, gurlType, aeTarget,
405                                     kAutoGenerateReturnID, kAnyTransactionID });
406                 // Don't set browser = appleEvent because then the
407                 // next time we call locateBrowser(), we'll get the
408                 // same AppleEvent, to which we'll already have added
409                 // the relevant parameter. Instead, regenerate the
410                 // AppleEvent every time.  There's probably a way to
411                 // do this better; if any has any ideas, please let me
412                 // know.
413                 return appleEvent;
414             } catch (IllegalAccessException iae) {
415                 browser = null;
416                 errorMessage = iae.getMessage();
417                 return browser;
418             } catch (InstantiationException ie) {
419                 browser = null;
420                 errorMessage = ie.getMessage();
421                 return browser;
422             } catch (InvocationTargetException ite) {
423                 browser = null;
424                 errorMessage = ite.getMessage();
425                 return browser;
426             }
427         case MRJ_2_1:
428             File systemFolder;
429             try {
430                 systemFolder = (File) findFolder.invoke(null, new Object[]
431                     { kSystemFolderType });
432             } catch (IllegalArgumentException iare) {
433                 browser = null;
434                 errorMessage = iare.getMessage();
435                 return browser;
436             } catch (IllegalAccessException iae) {
437                 browser = null;
438                 errorMessage = iae.getMessage();
439                 return browser;
440             } catch (InvocationTargetException ite) {
441                 browser = null;
442                 errorMessage = ite.getTargetException().getClass() + ": " +
443                     ite.getTargetException().getMessage();
444                 return browser;
445             }
446             String[] systemFolderFiles = systemFolder.list();
447             // Avoid a FilenameFilter because that can't be stopped mid-list
448             for(int i = 0; i < systemFolderFiles.length; i++) {
449                 try {
450                     File file = new File(systemFolder, systemFolderFiles[i]);
451                     if (!file.isFile()) {
452                         continue;
453                     }
454                     // We're looking for a file with a creator code of
455                     // 'MACS' and a type of 'FNDR'.  Only requiring
456                     // the type results in non-Finder applications
457                     // being picked up on certain Mac OS 9 systems,
458                     // especially German ones, and sending a GURL
459                     // event to those applications results in a logout
460                     // under Multiple Users.
461                     Object fileType = getFileType.invoke
462                         (null, new Object[] { file });
463                     if (FINDER_TYPE.equals(fileType.toString())) {
464                         Object fileCreator = getFileCreator.invoke
465                             (null, new Object[] { file });
466                         if (FINDER_CREATOR.equals(fileCreator.toString())) {
467                             browser = file.toString(); // Actually the
468                             // Finder, but that's OK
469                             return browser;
470                         }
471                     }
472                 } catch (IllegalArgumentException iare) {
473                     //WTF? browser = browser;
474                     errorMessage = iare.getMessage();
475                     return null;
476                 } catch (IllegalAccessException iae) {
477                     browser = null;
478                     errorMessage = iae.getMessage();
479                     return browser;
480                 } catch (InvocationTargetException ite) {
481                     browser = null;
482                     errorMessage = ite.getTargetException().getClass() + ": "
483                         + ite.getTargetException().getMessage();
484                     return browser;
485                 }
486             }
487             browser = null;
488             break;
489         case MRJ_3_0:
490         case MRJ_3_1:
491             browser = "";	// Return something non-null
492             break;
493         case WINDOWS_NT:
494             browser = "cmd.exe";
495             break;
496         case WINDOWS_9x:
497             browser = "command.com";
498             break;
499         case OTHER:
500         default:
501             browser = "netscape";
502             break;
503         }
504         return browser;
505     }
506 
507     /*** Attempts to open the default web browser to the given URL.
508      * @param url The URL to open
509      * @throws IOException If the web browser could not be located or
510      * does not run */
511     public static void openURL(String url) throws IOException {
512         if (!loadedWithoutErrors) {
513             throw new IOException("Exception in finding browser: "
514                                   + errorMessage);
515         }
516         Object browser = locateBrowser();
517         if (browser == null) {
518             throw new IOException("Unable to locate browser: " + errorMessage);
519         }
520 
521         switch (jvm) {
522         case MRJ_2_0:
523             Object aeDesc = null;
524             try {
525                 aeDesc = aeDescConstructor.newInstance(new Object[] { url });
526                 putParameter.invoke(browser, new Object[]
527                     { keyDirectObject, aeDesc });
528                 sendNoReply.invoke(browser, new Object[] { });
529             } catch (InvocationTargetException ite) {
530                 throw new IOException("InvocationTargetException while creating"
531                                       +" AEDesc: " + ite.getMessage());
532             } catch (IllegalAccessException iae) {
533                 throw new IOException("IllegalAccessException while building "
534                                       + "AppleEvent: " + iae.getMessage());
535             } catch (InstantiationException ie) {
536                 throw new IOException("InstantiationException while creating "
537                                       + "AEDesc: " + ie.getMessage());
538             } finally {
539                 aeDesc = null; // Encourage it to get disposed if it
540                 // was created
541                 browser = null;	// Ditto
542             }
543             break;
544         case MRJ_2_1:
545             Runtime.getRuntime().exec(new String[] { (String) browser, url } );
546             break;
547         case MRJ_3_0:
548             int[] instance = new int[1];
549             int result = ICStart(instance, 0);
550             if (result == 0) {
551                 int[] selectionStart = new int[] { 0 };
552                 byte[] urlBytes = url.getBytes();
553                 int[] selectionEnd = new int[] { urlBytes.length };
554                 result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
555                 urlBytes.length, selectionStart,
556                 selectionEnd);
557                 if (result == 0) {
558                     // Ignore the return value; the URL was launched
559                     // successfully regardless of what happens here.
560                     ICStop(instance);
561                 } else {
562                     throw new IOException("Unable to launch URL: " + result);
563                 }
564             } else {
565                 throw new IOException("Unable to create an Internet Config "
566                                       + "instance: " + result);
567             }
568             break;
569         case MRJ_3_1:
570             try {
571                 openURL.invoke(null, new Object[] { url });
572             } catch (InvocationTargetException ite) {
573                 throw new IOException("InvocationTargetException while calling "
574                                       + "openURL: " + ite.getMessage());
575             } catch (IllegalAccessException iae) {
576                 throw new IOException("IllegalAccessException while calling "
577                                       + "openURL: " + iae.getMessage());
578             }
579             break;
580         case WINDOWS_NT:
581             // Add quotes around the URL to allow ampersands and other special
582             // characters to work.
583             Process process = Runtime.getRuntime().exec(new String[]
584                 { (String) browser, FIRST_WINDOWS_PARAMETER,
585                   SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER,
586                   '"' + url + '"' });
587             // This avoids a memory leak on some versions of Java on
588             // Windows.  That's hinted at in
589             // <http://developer.java.sun.com/developer/qow/archive/68/>.
590             try {
591                 process.waitFor();
592                 process.exitValue();
593             } catch (InterruptedException ie) {
594                 throw new IOException("InterruptedException while launching "
595                                       + "browser: " + ie.getMessage());
596             }
597             break;
598         case WINDOWS_9x:
599             // Add quotes around the URL to allow ampersands and other special
600             // characters to work.
601             // Note: windows 98 doesn't expect the THIRD_WINDOWS_PARAMETER for
602             // its title.
603             process = Runtime.getRuntime().exec(new String[]
604                 { (String) browser, FIRST_WINDOWS_PARAMETER,
605                   SECOND_WINDOWS_PARAMETER, '"' + url + '"' });
606             // This avoids a memory leak on some versions of Java on
607             // Windows.  That's hinted at in
608             // <http://developer.java.sun.com/developer/qow/archive/68/>.
609             try {
610                 process.waitFor();
611                 process.exitValue();
612             } catch (InterruptedException ie) {
613                 throw new IOException("InterruptedException while launching "
614                                       + "browser: " + ie.getMessage());
615             }
616             break;
617         case OTHER:
618             // Assume that we're on Unix and that Netscape is installed
619 
620             // First, attempt to open the URL in a currently running
621             // session of Netscape
622             process = Runtime.getRuntime().exec(new String[]
623                 {(String)browser, NETSCAPE_REMOTE_PARAMETER,
624                  NETSCAPE_OPEN_PARAMETER_START + url +
625                  NETSCAPE_OPEN_PARAMETER_END });
626             try {
627                 int exitCode = process.waitFor();
628                 if (exitCode != 0) {	// if the command had an error
629                     Runtime.getRuntime().exec(new String[]
630                         { (String) browser, url } );
631                 } else if(process.getErrorStream() != null) {
632                     // Netscape may not be open, so the command may not have an
633                     // error, it just wouldn't have a process to attach to...
634                     BufferedReader reader = new BufferedReader
635                         (new InputStreamReader(process.getErrorStream()));
636                     String errorStr = reader.readLine();
637 
638                     if (  errorStr != null ) {
639                         // Command failed, start up the browser
640                         process = Runtime.getRuntime().exec(new String[] {
641                             (String) browser, url });
642                     }
643                 }
644             } catch (InterruptedException ie) {
645                 throw new IOException("InterruptedException while launching "
646                                       + "browser: " + ie.getMessage());
647             }
648             break;
649         default:
650             // This should never occur, but if it does, we'll try the
651             // simplest thing possible
652             Runtime.getRuntime().exec(new String[] { (String) browser, url });
653             break;
654         }
655     }
656 
657     /*** Methods required for Mac OS X.  The presence of native methods
658      * does not cause any problems on other platforms.  */
659     private native static int ICStart(int[] instance, int signature);
660     private native static int ICStop(int[] instance);
661     private native static int ICLaunchURL
662         (int instance, byte[] hint, byte[] data, int len, int[] selectionStart,
663          int[] selectionEnd);
664 }
665