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