JAL-1432 updated copyright notices
[jalview.git] / src / jalview / util / BrowserLauncher.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.util;
20
21 import java.io.*;
22 import java.lang.reflect.*;
23
24 /**
25  * BrowserLauncher is a class that provides one static method, openURL, which
26  * opens the default web browser for the current user of the system to the given
27  * URL. It may support other protocols depending on the system -- mailto, ftp,
28  * etc. -- but that has not been rigorously tested and is not guaranteed to
29  * work.
30  * <p>
31  * Yes, this is platform-specific code, and yes, it may rely on classes on
32  * certain platforms that are not part of the standard JDK. What we're trying to
33  * do, though, is to take something that's frequently desirable but inherently
34  * platform-specific -- opening a default browser -- and allow programmers (you,
35  * for example) to do so without worrying about dropping into native code or
36  * doing anything else similarly evil.
37  * <p>
38  * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant
39  * systems without modification or a need for additional libraries. All classes
40  * that are required on certain platforms to allow this to run are dynamically
41  * loaded at runtime via reflection and, if not found, will not cause this to do
42  * anything other than returning an error when opening the browser.
43  * <p>
44  * There are certain system requirements for this class, as it's running through
45  * Runtime.exec(), which is Java's way of making a native system call.
46  * Currently, this requires that a Macintosh have a Finder which supports the
47  * GURL event, which is true for Mac OS 8.0 and 8.1 systems that have the
48  * Internet Scripting AppleScript dictionary installed in the Scripting
49  * Additions folder in the Extensions folder (which is installed by default as
50  * far as I know under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later
51  * systems. On Windows, it only runs under Win32 systems (Windows 95, 98, and NT
52  * 4.0, as well as later versions of all). On other systems, this drops back
53  * from the inherently platform-sensitive concept of a default browser and
54  * simply attempts to launch Netscape via a shell command.
55  * <p>
56  * This code is Copyright 1999-2001 by Eric Albert (ejalbert\@cs.stanford.edu)
57  * and may be redistributed or modified in any form without restrictions as long
58  * as the portion of this comment from this paragraph through the end of the
59  * comment is not removed. The author requests that he be notified of any
60  * application, applet, or other binary that makes use of this code, but that's
61  * more out of curiosity than anything and is not required. This software
62  * includes no warranty. The author is not repsonsible for any loss of data or
63  * functionality or any adverse or unexpected effects of using this software.
64  * <p>
65  * Credits: <br>
66  * Steven Spencer, JavaWorld magazine (<a
67  * href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip
68  * 66</a>) <br>
69  * Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea
70  * Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
71  * 
72  * @author Eric Albert (<a
73  *         href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
74  * @version 1.4b1 (Released June 20, 2001)
75  */
76 public class BrowserLauncher
77 {
78   /**
79    * The Java virtual machine that we are running on. Actually, in most cases we
80    * only care about the operating system, but some operating systems require us
81    * to switch on the VM.
82    */
83   private static int jvm;
84
85   /** The browser for the system */
86   private static Object browser;
87
88   /**
89    * Caches whether any classes, methods, and fields that are not part of the
90    * JDK and need to be dynamically loaded at runtime loaded successfully.
91    * <p>
92    * Note that if this is <code>false</code>, <code>openURL()</code> will always
93    * return an IOException.
94    */
95   private static boolean loadedWithoutErrors;
96
97   /** The com.apple.mrj.MRJFileUtils class */
98   private static Class mrjFileUtilsClass;
99
100   /** The com.apple.mrj.MRJOSType class */
101   private static Class mrjOSTypeClass;
102
103   /** The com.apple.MacOS.AEDesc class */
104   private static Class aeDescClass;
105
106   /** The &lt;init&gt;(int) method of com.apple.MacOS.AETarget */
107   private static Constructor aeTargetConstructor;
108
109   /** The &lt;init&gt;(int, int, int) method of com.apple.MacOS.AppleEvent */
110   private static Constructor appleEventConstructor;
111
112   /** The &lt;init&gt;(String) method of com.apple.MacOS.AEDesc */
113   private static Constructor aeDescConstructor;
114
115   /** The findFolder method of com.apple.mrj.MRJFileUtils */
116   private static Method findFolder;
117
118   /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
119   private static Method getFileCreator;
120
121   /** The getFileType method of com.apple.mrj.MRJFileUtils */
122   private static Method getFileType;
123
124   /** The openURL method of com.apple.mrj.MRJFileUtils */
125   private static Method openURL;
126
127   /** The makeOSType method of com.apple.MacOS.OSUtils */
128   private static Method makeOSType;
129
130   /** The putParameter method of com.apple.MacOS.AppleEvent */
131   private static Method putParameter;
132
133   /** The sendNoReply method of com.apple.MacOS.AppleEvent */
134   private static Method sendNoReply;
135
136   /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
137   private static Object kSystemFolderType;
138
139   /** The keyDirectObject AppleEvent parameter type */
140   private static Integer keyDirectObject;
141
142   /** The kAutoGenerateReturnID AppleEvent code */
143   private static Integer kAutoGenerateReturnID;
144
145   /** The kAnyTransactionID AppleEvent code */
146   private static Integer kAnyTransactionID;
147
148   /** The linkage object required for JDirect 3 on Mac OS X. */
149   private static Object linkage;
150
151   /** The framework to reference on Mac OS X */
152   private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
153
154   /** JVM constant for MRJ 2.0 */
155   private static final int MRJ_2_0 = 0;
156
157   /** JVM constant for MRJ 2.1 or later */
158   private static final int MRJ_2_1 = 1;
159
160   /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
161   private static final int MRJ_3_0 = 3;
162
163   /** JVM constant for MRJ 3.1 */
164   private static final int MRJ_3_1 = 4;
165
166   /** JVM constant for any Windows NT JVM */
167   private static final int WINDOWS_NT = 5;
168
169   /** JVM constant for any Windows 9x JVM */
170   private static final int WINDOWS_9x = 6;
171
172   /** JVM constant for any other platform */
173   private static final int OTHER = -1;
174
175   /**
176    * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep
177    * non-U.S. English systems from working properly.
178    */
179   private static final String FINDER_TYPE = "FNDR";
180
181   /**
182    * The creator code of the Finder on a Macintosh, which is needed to send
183    * AppleEvents to the application.
184    */
185   private static final String FINDER_CREATOR = "MACS";
186
187   /** The name for the AppleEvent type corresponding to a GetURL event. */
188   private static final String GURL_EVENT = "GURL";
189
190   /**
191    * The first parameter that needs to be passed into Runtime.exec() to open the
192    * default web browser on Windows.
193    */
194   private static final String FIRST_WINDOWS_PARAMETER = "/c";
195
196   /** The second parameter for Runtime.exec() on Windows. */
197   private static final String SECOND_WINDOWS_PARAMETER = "start";
198
199   /**
200    * The third parameter for Runtime.exec() on Windows. This is a "title"
201    * parameter that the command line expects. Setting this parameter allows URLs
202    * containing spaces to work.
203    */
204   private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
205
206   /**
207    * The shell parameters for Netscape that opens a given URL in an already-open
208    * copy of Netscape on many command-line systems.
209    */
210   private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
211
212   private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL(";
213
214   private static final String NETSCAPE_OPEN_NEW_WINDOW = ", new-window";
215
216   private static final String NETSCAPE_OPEN_PARAMETER_END = ")";
217
218   /**
219    * The message from any exception thrown throughout the initialization
220    * process.
221    */
222   private static String errorMessage;
223
224   /**
225    * An initialization block that determines the operating system and loads the
226    * necessary runtime data.
227    */
228   static
229   {
230     loadedWithoutErrors = true;
231
232     String osName = System.getProperty("os.name");
233
234     if (osName.startsWith("Mac OS"))
235     {
236       String mrjVersion = System.getProperty("mrj.version");
237       String majorMRJVersion;
238       if (mrjVersion == null)
239       {
240         // must be on some later build with mrj support
241         majorMRJVersion = "3.1";
242       }
243       else
244       {
245         majorMRJVersion = mrjVersion.substring(0, 3);
246       }
247
248       try
249       {
250         double version = Double.valueOf(majorMRJVersion).doubleValue();
251
252         if (version == 2)
253         {
254           jvm = MRJ_2_0;
255         }
256         else if ((version >= 2.1) && (version < 3))
257         {
258           // Assume that all 2.x versions of MRJ work the same. MRJ 2.1 actually
259           // works via Runtime.exec() and 2.2 supports that but has an openURL()
260           // method
261           // as well that we currently ignore.
262           jvm = MRJ_2_1;
263         }
264         else if (version == 3.0)
265         {
266           jvm = MRJ_3_0;
267         }
268         else if (version >= 3.1)
269         {
270           // Assume that all 3.1 and later versions of MRJ work the same.
271           jvm = MRJ_3_1;
272         }
273         else
274         {
275           loadedWithoutErrors = false;
276           errorMessage = "Unsupported MRJ version: " + version;
277         }
278       } catch (NumberFormatException nfe)
279       {
280         loadedWithoutErrors = false;
281         errorMessage = "Invalid MRJ version: " + mrjVersion;
282       }
283     }
284     else if (osName.startsWith("Windows"))
285     {
286       if (osName.indexOf("9") != -1)
287       {
288         jvm = WINDOWS_9x;
289       }
290       else
291       {
292         jvm = WINDOWS_NT;
293       }
294     }
295     else
296     {
297       jvm = OTHER;
298     }
299
300     if (loadedWithoutErrors)
301     { // if we haven't hit any errors yet
302       loadedWithoutErrors = loadClasses();
303     }
304   }
305
306   /**
307    * This class should be never be instantiated; this just ensures so.
308    */
309   private BrowserLauncher()
310   {
311   }
312
313   /**
314    * Called by a static initializer to load any classes, fields, and methods
315    * required at runtime to locate the user's web browser.
316    * 
317    * @return <code>true</code> if all intialization succeeded <code>false</code>
318    *         if any portion of the initialization failed
319    */
320   private static boolean loadClasses()
321   {
322     switch (jvm)
323     {
324     case MRJ_2_0:
325
326       try
327       {
328         Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
329         Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
330         Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
331         Class aeClass = Class.forName("com.apple.MacOS.ae");
332         aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
333
334         aeTargetConstructor = aeTargetClass
335                 .getDeclaredConstructor(new Class[]
336                 { int.class });
337         appleEventConstructor = appleEventClass
338                 .getDeclaredConstructor(new Class[]
339                 { int.class, int.class, aeTargetClass, int.class, int.class });
340         aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[]
341         { String.class });
342
343         makeOSType = osUtilsClass.getDeclaredMethod("makeOSType",
344                 new Class[]
345                 { String.class });
346         putParameter = appleEventClass.getDeclaredMethod("putParameter",
347                 new Class[]
348                 { int.class, aeDescClass });
349         sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply",
350                 new Class[]
351                 {});
352
353         Field keyDirectObjectField = aeClass
354                 .getDeclaredField("keyDirectObject");
355         keyDirectObject = (Integer) keyDirectObjectField.get(null);
356
357         Field autoGenerateReturnIDField = appleEventClass
358                 .getDeclaredField("kAutoGenerateReturnID");
359         kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField
360                 .get(null);
361
362         Field anyTransactionIDField = appleEventClass
363                 .getDeclaredField("kAnyTransactionID");
364         kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
365       } catch (ClassNotFoundException cnfe)
366       {
367         errorMessage = cnfe.getMessage();
368
369         return false;
370       } catch (NoSuchMethodException nsme)
371       {
372         errorMessage = nsme.getMessage();
373
374         return false;
375       } catch (NoSuchFieldException nsfe)
376       {
377         errorMessage = nsfe.getMessage();
378
379         return false;
380       } catch (IllegalAccessException iae)
381       {
382         errorMessage = iae.getMessage();
383
384         return false;
385       }
386
387       break;
388
389     case MRJ_2_1:
390
391       try
392       {
393         mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
394         mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
395
396         Field systemFolderField = mrjFileUtilsClass
397                 .getDeclaredField("kSystemFolderType");
398         kSystemFolderType = systemFolderField.get(null);
399         findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder",
400                 new Class[]
401                 { mrjOSTypeClass });
402         getFileCreator = mrjFileUtilsClass.getDeclaredMethod(
403                 "getFileCreator", new Class[]
404                 { File.class });
405         getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType",
406                 new Class[]
407                 { File.class });
408       } catch (ClassNotFoundException cnfe)
409       {
410         errorMessage = cnfe.getMessage();
411
412         return false;
413       } catch (NoSuchFieldException nsfe)
414       {
415         errorMessage = nsfe.getMessage();
416
417         return false;
418       } catch (NoSuchMethodException nsme)
419       {
420         errorMessage = nsme.getMessage();
421
422         return false;
423       } catch (SecurityException se)
424       {
425         errorMessage = se.getMessage();
426
427         return false;
428       } catch (IllegalAccessException iae)
429       {
430         errorMessage = iae.getMessage();
431
432         return false;
433       }
434
435       break;
436
437     case MRJ_3_0:
438
439       try
440       {
441         Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
442         Constructor constructor = linker.getConstructor(new Class[]
443         { Class.class });
444         linkage = constructor.newInstance(new Object[]
445         { BrowserLauncher.class });
446       } catch (ClassNotFoundException cnfe)
447       {
448         errorMessage = cnfe.getMessage();
449
450         return false;
451       } catch (NoSuchMethodException nsme)
452       {
453         errorMessage = nsme.getMessage();
454
455         return false;
456       } catch (InvocationTargetException ite)
457       {
458         errorMessage = ite.getMessage();
459
460         return false;
461       } catch (InstantiationException ie)
462       {
463         errorMessage = ie.getMessage();
464
465         return false;
466       } catch (IllegalAccessException iae)
467       {
468         errorMessage = iae.getMessage();
469
470         return false;
471       }
472
473       break;
474
475     case MRJ_3_1:
476
477       try
478       {
479         mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
480         openURL = mrjFileUtilsClass.getDeclaredMethod("openURL",
481                 new Class[]
482                 { String.class });
483       } catch (ClassNotFoundException cnfe)
484       {
485         errorMessage = cnfe.getMessage();
486
487         return false;
488       } catch (NoSuchMethodException nsme)
489       {
490         errorMessage = nsme.getMessage();
491
492         return false;
493       }
494
495       break;
496
497     default:
498       break;
499     }
500
501     return true;
502   }
503
504   /**
505    * Attempts to locate the default web browser on the local system. s results
506    * so it only locates the browser once for each use of this class per JVM
507    * instance.
508    * 
509    * @return The browser for the system. Note that this may not be what you
510    *         would consider to be a standard web browser; instead, it's the
511    *         application that gets called to open the default web browser. In
512    *         some cases, this will be a non-String object that provides the
513    *         means of calling the default browser.
514    */
515   private static Object locateBrowser()
516   {
517     if (browser != null)
518     {
519       return browser;
520     }
521
522     switch (jvm)
523     {
524     case MRJ_2_0:
525
526       try
527       {
528         Integer finderCreatorCode = (Integer) makeOSType.invoke(null,
529                 new Object[]
530                 { FINDER_CREATOR });
531         Object aeTarget = aeTargetConstructor.newInstance(new Object[]
532         { finderCreatorCode });
533         Integer gurlType = (Integer) makeOSType.invoke(null, new Object[]
534         { GURL_EVENT });
535         Object appleEvent = appleEventConstructor.newInstance(new Object[]
536         { gurlType, gurlType, aeTarget, kAutoGenerateReturnID,
537             kAnyTransactionID });
538
539         // Don't set browser = appleEvent because then the next time we call
540         // locateBrowser(), we'll get the same AppleEvent, to which we'll
541         // already have
542         // added the relevant parameter. Instead, regenerate the AppleEvent
543         // every time.
544         // There's probably a way to do this better; if any has any ideas,
545         // please let
546         // me know.
547         return appleEvent;
548       } catch (IllegalAccessException iae)
549       {
550         browser = null;
551         errorMessage = iae.getMessage();
552
553         return browser;
554       } catch (InstantiationException ie)
555       {
556         browser = null;
557         errorMessage = ie.getMessage();
558
559         return browser;
560       } catch (InvocationTargetException ite)
561       {
562         browser = null;
563         errorMessage = ite.getMessage();
564
565         return browser;
566       }
567
568     case MRJ_2_1:
569
570       File systemFolder;
571
572       try
573       {
574         systemFolder = (File) findFolder.invoke(null, new Object[]
575         { kSystemFolderType });
576       } catch (IllegalArgumentException iare)
577       {
578         browser = null;
579         errorMessage = iare.getMessage();
580
581         return browser;
582       } catch (IllegalAccessException iae)
583       {
584         browser = null;
585         errorMessage = iae.getMessage();
586
587         return browser;
588       } catch (InvocationTargetException ite)
589       {
590         browser = null;
591         errorMessage = ite.getTargetException().getClass() + ": "
592                 + ite.getTargetException().getMessage();
593
594         return browser;
595       }
596
597       String[] systemFolderFiles = systemFolder.list();
598
599       // Avoid a FilenameFilter because that can't be stopped mid-list
600       for (int i = 0; i < systemFolderFiles.length; i++)
601       {
602         try
603         {
604           File file = new File(systemFolder, systemFolderFiles[i]);
605
606           if (!file.isFile())
607           {
608             continue;
609           }
610
611           // We're looking for a file with a creator code of 'MACS' and
612           // a type of 'FNDR'. Only requiring the type results in non-Finder
613           // applications being picked up on certain Mac OS 9 systems,
614           // especially German ones, and sending a GURL event to those
615           // applications results in a logout under Multiple Users.
616           Object fileType = getFileType.invoke(null, new Object[]
617           { file });
618
619           if (FINDER_TYPE.equals(fileType.toString()))
620           {
621             Object fileCreator = getFileCreator.invoke(null, new Object[]
622             { file });
623
624             if (FINDER_CREATOR.equals(fileCreator.toString()))
625             {
626               browser = file.toString(); // Actually the Finder, but that's OK
627
628               return browser;
629             }
630           }
631         } catch (IllegalArgumentException iare)
632         {
633           errorMessage = iare.getMessage();
634
635           return null;
636         } catch (IllegalAccessException iae)
637         {
638           browser = null;
639           errorMessage = iae.getMessage();
640
641           return browser;
642         } catch (InvocationTargetException ite)
643         {
644           browser = null;
645           errorMessage = ite.getTargetException().getClass() + ": "
646                   + ite.getTargetException().getMessage();
647
648           return browser;
649         }
650       }
651
652       browser = null;
653
654       break;
655
656     case MRJ_3_0:
657     case MRJ_3_1:
658       browser = ""; // Return something non-null
659
660       break;
661
662     case WINDOWS_NT:
663       browser = "cmd.exe";
664
665       break;
666
667     case WINDOWS_9x:
668       browser = "command.com";
669
670       break;
671
672     case OTHER:
673     default:
674       browser = jalview.bin.Cache.getDefault("DEFAULT_BROWSER", "firefox");
675
676       break;
677     }
678
679     return browser;
680   }
681
682   /**
683    * used to ensure that browser is up-to-date after a configuration change
684    * (Unix DEFAULT_BROWSER property change).
685    */
686   public static void resetBrowser()
687   {
688     browser = null;
689   }
690
691   /**
692    * Attempts to open the default web browser to the given URL.
693    * 
694    * @param url
695    *          The URL to open
696    * @throws IOException
697    *           If the web browser could not be located or does not run
698    */
699   public static void openURL(String url) throws IOException
700   {
701     if (!loadedWithoutErrors)
702     {
703       throw new IOException("Exception in finding browser: " + errorMessage);
704     }
705
706     Object browser = locateBrowser();
707
708     if (browser == null)
709     {
710       throw new IOException("Unable to locate browser: " + errorMessage);
711     }
712
713     switch (jvm)
714     {
715     case MRJ_2_0:
716
717       Object aeDesc = null;
718
719       try
720       {
721         aeDesc = aeDescConstructor.newInstance(new Object[]
722         { url });
723         putParameter.invoke(browser, new Object[]
724         { keyDirectObject, aeDesc });
725         sendNoReply.invoke(browser, new Object[]
726         {});
727       } catch (InvocationTargetException ite)
728       {
729         throw new IOException(
730                 "InvocationTargetException while creating AEDesc: "
731                         + ite.getMessage());
732       } catch (IllegalAccessException iae)
733       {
734         throw new IOException(
735                 "IllegalAccessException while building AppleEvent: "
736                         + iae.getMessage());
737       } catch (InstantiationException ie)
738       {
739         throw new IOException(
740                 "InstantiationException while creating AEDesc: "
741                         + ie.getMessage());
742       } finally
743       {
744         aeDesc = null; // Encourage it to get disposed if it was created
745         browser = null; // Ditto
746       }
747
748       break;
749
750     case MRJ_2_1:
751       Runtime.getRuntime().exec(new String[]
752       { (String) browser, url });
753
754       break;
755
756     case MRJ_3_0:
757
758       int[] instance = new int[1];
759       int result = ICStart(instance, 0);
760
761       if (result == 0)
762       {
763         int[] selectionStart = new int[]
764         { 0 };
765         byte[] urlBytes = url.getBytes();
766         int[] selectionEnd = new int[]
767         { urlBytes.length };
768         result = ICLaunchURL(instance[0], new byte[]
769         { 0 }, urlBytes, urlBytes.length, selectionStart, selectionEnd);
770
771         if (result == 0)
772         {
773           // Ignore the return value; the URL was launched successfully
774           // regardless of what happens here.
775           ICStop(instance);
776         }
777         else
778         {
779           throw new IOException("Unable to launch URL: " + result);
780         }
781       }
782       else
783       {
784         throw new IOException(
785                 "Unable to create an Internet Config instance: " + result);
786       }
787
788       break;
789
790     case MRJ_3_1:
791
792       try
793       {
794         openURL.invoke(null, new Object[]
795         { url });
796       } catch (InvocationTargetException ite)
797       {
798         throw new IOException(
799                 "InvocationTargetException while calling openURL: "
800                         + ite.getMessage());
801       } catch (IllegalAccessException iae)
802       {
803         throw new IOException(
804                 "IllegalAccessException while calling openURL: "
805                         + iae.getMessage());
806       }
807
808       break;
809
810     case WINDOWS_NT:
811     case WINDOWS_9x:
812
813       // Add quotes around the URL to allow ampersands and other special
814       // characters to work.
815       Process process = Runtime.getRuntime().exec(
816               new String[]
817               { (String) browser, FIRST_WINDOWS_PARAMETER,
818                   SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER,
819                   '"' + url + '"' });
820
821       // This avoids a memory leak on some versions of Java on Windows.
822       // That's hinted at in
823       // <http://developer.java.sun.com/developer/qow/archive/68/>.
824       try
825       {
826         process.waitFor();
827         process.exitValue();
828       } catch (InterruptedException ie)
829       {
830         throw new IOException(
831                 "InterruptedException while launching browser: "
832                         + ie.getMessage());
833       }
834
835       break;
836
837     case OTHER:
838
839       // Assume that we're on Unix and that Netscape (actually Firefox) is
840       // installed
841       // First, attempt to open the URL in a currently running session of
842       // Netscape
843       // JBPNote log debug
844
845       /*
846        * System.out.println("Executing : "+browser+" "+
847        * NETSCAPE_REMOTE_PARAMETER+" "+ NETSCAPE_OPEN_PARAMETER_START + url +
848        * NETSCAPE_OPEN_NEW_WINDOW + NETSCAPE_OPEN_PARAMETER_END);
849        */
850       process = Runtime.getRuntime().exec(
851               new String[]
852               {
853                   (String) browser,
854                   NETSCAPE_REMOTE_PARAMETER,
855
856                   NETSCAPE_OPEN_PARAMETER_START + url
857                           + NETSCAPE_OPEN_NEW_WINDOW
858                           + NETSCAPE_OPEN_PARAMETER_END });
859
860       try
861       {
862         int exitCode = process.waitFor();
863
864         if (exitCode != 0)
865         { // if Netscape was not open
866           Runtime.getRuntime().exec(new String[]
867           { (String) browser, url });
868         }
869       } catch (InterruptedException ie)
870       {
871         throw new IOException(
872                 "InterruptedException while launching browser: "
873                         + ie.getMessage());
874       }
875
876       break;
877
878     default:
879
880       // This should never occur, but if it does, we'll try the simplest thing
881       // possible
882       Runtime.getRuntime().exec(new String[]
883       { (String) browser, url });
884
885       break;
886     }
887   }
888
889   /**
890    * Methods required for Mac OS X. The presence of native methods does not
891    * cause any problems on other platforms.
892    */
893   private native static int ICStart(int[] instance, int signature);
894
895   private native static int ICStop(int[] instance);
896
897   private native static int ICLaunchURL(int instance, byte[] hint,
898           byte[] data, int len, int[] selectionStart, int[] selectionEnd);
899 }