001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.springframework.richclient.util;
017    
018    import java.io.File;
019    import java.io.IOException;
020    import java.lang.reflect.Constructor;
021    import java.lang.reflect.Field;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.net.URL;
025    
026    import javax.swing.SwingUtilities;
027    
028    /**
029     * BrowserLauncher is a class that provides one static method, openURL, which
030     * opens the default web browser for the current user of the system to the given
031     * URL. It may support other protocols depending on the system -- mailto, ftp,
032     * etc. -- but that has not been rigorously tested and is not guaranteed to
033     * work.
034     * <p>
035     * Yes, this is platform-specific code, and yes, it may rely on classes on
036     * certain platforms that are not part of the standard JDK. What we're trying to
037     * do, though, is to take something that's frequently desirable but inherently
038     * platform-specific -- opening a default browser -- and allow programmers (you,
039     * for example) to do so without worrying about dropping into native code or
040     * doing anything else similarly evil.
041     * <p>
042     * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant
043     * systems without modification or a need for additional libraries. All classes
044     * that are required on certain platforms to allow this to run are dynamically
045     * loaded at runtime via reflection and, if not found, will not cause this to do
046     * anything other than returning an error when opening the browser.
047     * <p>
048     * There are certain system requirements for this class, as it's running through
049     * Runtime.exec(), which is Java's way of making a native system call.
050     * Currently, this requires that a Macintosh have a Finder which supports the
051     * GURL event, which is true for Mac OS 8.0 and 8.1 systems that have the
052     * Internet Scripting AppleScript dictionary installed in the Scripting
053     * Additions folder in the Extensions folder (which is installed by default as
054     * far as I know under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later
055     * systems. On Windows, it only runs under Win32 systems (Windows 95, 98, and NT
056     * 4.0, as well as later versions of all). On other systems, this drops back
057     * from the inherently platform-sensitive concept of a default browser and
058     * simply attempts to launch firefox via a shell command.
059     * <p>
060     * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu)
061     * and may be redistributed or modified in any form without restrictions as long
062     * as the portion of this comment from this paragraph through the end of the
063     * comment is not removed. The author requests that he be notified of any
064     * application, applet, or other binary that makes use of this code, but that's
065     * more out of curiosity than anything and is not required. This software
066     * includes no warranty. The author is not repsonsible for any loss of data or
067     * functionality or any adverse or unexpected effects of using this software.
068     * <p>
069     * Credits: <br>
070     * Steven Spencer, JavaWorld magazine ( <a
071     * href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip
072     * 66 </a>) <br>
073     * Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea
074     * Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
075     *
076     * @author Eric Albert ( <a
077     * href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu </a>)
078     * @version 1.4b1 (Released June 20, 2001)
079     *
080     * @deprecated use JDIC (https://jdic.dev.java.net/) or the Desktop class in
081     * java 1.6 instead
082     */
083    public class BrowserLauncher {
084    
085            /**
086             * The Java virtual machine that we are running on. Actually, in most cases
087             * we only care about the operating system, but some operating systems
088             * require us to switch on the VM.
089             */
090            private static int jvm;
091    
092            /** The browser for the system */
093            private static Object browser;
094    
095            /**
096             * Caches whether any classes, methods, and fields that are not part of the
097             * JDK and need to be dynamically loaded at runtime loaded successfully.
098             * <p>
099             * Note that if this is <code>false</code>,<code>openURL()</code> will
100             * always return an IOException.
101             */
102            private static boolean loadedWithoutErrors;
103    
104            /** The com.apple.mrj.MRJFileUtils class */
105            private static Class mrjFileUtilsClass;
106    
107            /** The com.apple.mrj.MRJOSType class */
108            private static Class mrjOSTypeClass;
109    
110            /** The com.apple.MacOS.AEDesc class */
111            private static Class aeDescClass;
112    
113            /** The <init>(int) method of com.apple.MacOS.AETarget */
114            private static Constructor aeTargetConstructor;
115    
116            /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
117            private static Constructor appleEventConstructor;
118    
119            /** The <init>(String) method of com.apple.MacOS.AEDesc */
120            private static Constructor aeDescConstructor;
121    
122            /** The findFolder method of com.apple.mrj.MRJFileUtils */
123            private static Method findFolder;
124    
125            /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
126            private static Method getFileCreator;
127    
128            /** The getFileType method of com.apple.mrj.MRJFileUtils */
129            private static Method getFileType;
130    
131            /** The openURL method of com.apple.mrj.MRJFileUtils */
132            private static Method openURL;
133    
134            /** The makeOSType method of com.apple.MacOS.OSUtils */
135            private static Method makeOSType;
136    
137            /** The putParameter method of com.apple.MacOS.AppleEvent */
138            private static Method putParameter;
139    
140            /** The sendNoReply method of com.apple.MacOS.AppleEvent */
141            private static Method sendNoReply;
142    
143            /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
144            private static Object kSystemFolderType;
145    
146            /** The keyDirectObject AppleEvent parameter type */
147            private static Integer keyDirectObject;
148    
149            /** The kAutoGenerateReturnID AppleEvent code */
150            private static Integer kAutoGenerateReturnID;
151    
152            /** The kAnyTransactionID AppleEvent code */
153            private static Integer kAnyTransactionID;
154    
155            /** JVM constant for MRJ 2.0 */
156            private static final int MRJ_2_0 = 0;
157    
158            /** JVM constant for MRJ 2.1 or later */
159            private static final int MRJ_2_1 = 1;
160    
161            /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
162            private static final int MRJ_3_0 = 3;
163    
164            /** JVM constant for MRJ 3.1 */
165            private static final int MRJ_3_1 = 4;
166    
167            /** JVM constant for any Windows NT JVM */
168            private static final int WINDOWS_NT = 5;
169    
170            /** JVM constant for any Windows 9x JVM */
171            private static final int WINDOWS_9x = 6;
172    
173            /** JVM constant for any other platform */
174            private static final int OTHER = -1;
175    
176            /**
177             * The file type of the Finder on a Macintosh. Hardcoding "Finder" would
178             * keep non-U.S. English systems from working properly.
179             */
180            private static final String FINDER_TYPE = "FNDR";
181    
182            /**
183             * The creator code of the Finder on a Macintosh, which is needed to send
184             * AppleEvents to the application.
185             */
186            private static final String FINDER_CREATOR = "MACS";
187    
188            /** The name for the AppleEvent type corresponding to a GetURL event. */
189            private static final String GURL_EVENT = "GURL";
190    
191            /**
192             * The first parameter that needs to be passed into Runtime.exec() to open
193             * the default web browser on Windows.
194             */
195            private static final String FIRST_WINDOWS_PARAMETER = "/c";
196    
197            /** The second parameter for Runtime.exec() on Windows. */
198            private static final String SECOND_WINDOWS_PARAMETER = "start";
199    
200            /**
201             * The third parameter for Runtime.exec() on Windows. This is a "title"
202             * parameter that the command line expects. Setting this parameter allows
203             * URLs containing spaces to work.
204             */
205            private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
206    
207            /**
208             * The message from any exception thrown throughout the initialization
209             * process.
210             */
211            private static String errorMessage;
212    
213            /**
214             * An initialization block that determines the operating system and loads
215             * the necessary runtime data.
216             */
217            static {
218                    loadedWithoutErrors = true;
219                    String osName = System.getProperty("os.name");
220                    if (osName.startsWith("Mac OS")) {
221                            String mrjVersion = System.getProperty("mrj.version");
222                            String majorMRJVersion = mrjVersion.substring(0, 3);
223                            try {
224                                    double version = Double.valueOf(majorMRJVersion).doubleValue();
225                                    if (version == 2) {
226                                            jvm = MRJ_2_0;
227                                    }
228                                    else if (version >= 2.1 && version < 3) {
229                                            // Assume that all 2.x versions of MRJ work the same. MRJ
230                                            // 2.1 actually works via Runtime.exec() and 2.2 supports
231                                            // that but has an openURL() method as well that we
232                                            // currently ignore.
233                                            jvm = MRJ_2_1;
234                                    }
235                                    else if (version == 3.0) {
236                                            jvm = MRJ_3_0;
237                                    }
238                                    else if (version >= 3.1) {
239                                            // Assume that all 3.1 and later versions of MRJ work the
240                                            // same.
241                                            jvm = MRJ_3_1;
242                                    }
243                                    else {
244                                            loadedWithoutErrors = false;
245                                            errorMessage = "Unsupported MRJ version: " + version;
246                                    }
247                            }
248                            catch (NumberFormatException nfe) {
249                                    loadedWithoutErrors = false;
250                                    errorMessage = "Invalid MRJ version: " + mrjVersion;
251                            }
252                    }
253                    else if (osName.startsWith("Windows")) {
254                            if (osName.indexOf("9") != -1) {
255                                    jvm = WINDOWS_9x;
256                            }
257                            else {
258                                    jvm = WINDOWS_NT;
259                            }
260                    }
261                    else {
262                            jvm = OTHER;
263                    }
264    
265                    if (loadedWithoutErrors) {// if we haven't hit any errors yet
266                            loadedWithoutErrors = loadClasses();
267                    }
268            }
269    
270            /**
271             * This class should be never be instantiated; this just ensures so.
272             */
273            private BrowserLauncher() {
274            }
275    
276            /**
277             * Called by a static initializer to load any classes, fields, and methods
278             * required at runtime to locate the user's web browser.
279             *
280             * @return <code>true</code> if all intialization succeeded
281             * <code>false</code> if any portion of the initialization failed
282             */
283            private static boolean loadClasses() {
284                    switch (jvm) {
285                    case MRJ_2_0:
286                            try {
287                                    Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
288                                    Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
289                                    Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
290                                    Class aeClass = Class.forName("com.apple.MacOS.ae");
291                                    aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
292    
293                                    aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class[] { int.class });
294                                    appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class,
295                                                    aeTargetClass, int.class, int.class });
296                                    aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
297    
298                                    makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class[] { String.class });
299                                    putParameter = appleEventClass
300                                                    .getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
301                                    sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] {});
302    
303                                    Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
304                                    keyDirectObject = (Integer) keyDirectObjectField.get(null);
305                                    Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
306                                    kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
307                                    Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
308                                    kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
309                            }
310                            catch (ClassNotFoundException cnfe) {
311                                    errorMessage = cnfe.getMessage();
312                                    return false;
313                            }
314                            catch (NoSuchMethodException nsme) {
315                                    errorMessage = nsme.getMessage();
316                                    return false;
317                            }
318                            catch (NoSuchFieldException nsfe) {
319                                    errorMessage = nsfe.getMessage();
320                                    return false;
321                            }
322                            catch (IllegalAccessException iae) {
323                                    errorMessage = iae.getMessage();
324                                    return false;
325                            }
326                            break;
327                    case MRJ_2_1:
328                            try {
329                                    mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
330                                    mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
331                                    Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
332                                    kSystemFolderType = systemFolderField.get(null);
333                                    findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
334                                    getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class });
335                                    getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
336                            }
337                            catch (ClassNotFoundException cnfe) {
338                                    errorMessage = cnfe.getMessage();
339                                    return false;
340                            }
341                            catch (NoSuchFieldException nsfe) {
342                                    errorMessage = nsfe.getMessage();
343                                    return false;
344                            }
345                            catch (NoSuchMethodException nsme) {
346                                    errorMessage = nsme.getMessage();
347                                    return false;
348                            }
349                            catch (SecurityException se) {
350                                    errorMessage = se.getMessage();
351                                    return false;
352                            }
353                            catch (IllegalAccessException iae) {
354                                    errorMessage = iae.getMessage();
355                                    return false;
356                            }
357                            break;
358                    case MRJ_3_0:
359                            try {
360                                    Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
361                                    Constructor constructor = linker.getConstructor(new Class[] { Class.class });
362                                    constructor.newInstance(new Object[] { BrowserLauncher.class });
363                            }
364                            catch (ClassNotFoundException cnfe) {
365                                    errorMessage = cnfe.getMessage();
366                                    return false;
367                            }
368                            catch (NoSuchMethodException nsme) {
369                                    errorMessage = nsme.getMessage();
370                                    return false;
371                            }
372                            catch (InvocationTargetException ite) {
373                                    errorMessage = ite.getMessage();
374                                    return false;
375                            }
376                            catch (InstantiationException ie) {
377                                    errorMessage = ie.getMessage();
378                                    return false;
379                            }
380                            catch (IllegalAccessException iae) {
381                                    errorMessage = iae.getMessage();
382                                    return false;
383                            }
384                            break;
385                    case MRJ_3_1:
386                            try {
387                                    mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
388                                    openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class });
389                            }
390                            catch (ClassNotFoundException cnfe) {
391                                    errorMessage = cnfe.getMessage();
392                                    return false;
393                            }
394                            catch (NoSuchMethodException nsme) {
395                                    errorMessage = nsme.getMessage();
396                                    return false;
397                            }
398                            break;
399                    default:
400                            break;
401                    }
402                    return true;
403            }
404    
405            /**
406             * Attempts to locate the default web browser on the local system. Caches
407             * results so it only locates the browser once for each use of this class
408             * per JVM instance.
409             *
410             * @return The browser for the system. Note that this may not be what you
411             * would consider to be a standard web browser; instead, it's the
412             * application that gets called to open the default web browser. In some
413             * cases, this will be a non-String object that provides the means of
414             * calling the default browser.
415             */
416            private static Object locateBrowser() {
417                    if (browser != null) {
418                            return browser;
419                    }
420                    switch (jvm) {
421                    case MRJ_2_0:
422                            try {
423                                    Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
424                                    Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
425                                    Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
426                                    Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget,
427                                                    kAutoGenerateReturnID, kAnyTransactionID });
428                                    // Don't set browser = appleEvent because then the next time we
429                                    // call locateBrowser(), we'll get the same AppleEvent, to which
430                                    // we'll already have added the relevant parameter. Instead,
431                                    // regenerate the AppleEvent every time.
432                                    // There's probably a way to do this better; if any has any
433                                    // ideas, please let me know.
434                                    return appleEvent;
435                            }
436                            catch (IllegalAccessException iae) {
437                                    browser = null;
438                                    errorMessage = iae.getMessage();
439                                    return browser;
440                            }
441                            catch (InstantiationException ie) {
442                                    browser = null;
443                                    errorMessage = ie.getMessage();
444                                    return browser;
445                            }
446                            catch (InvocationTargetException ite) {
447                                    browser = null;
448                                    errorMessage = ite.getMessage();
449                                    return browser;
450                            }
451                    case MRJ_2_1:
452                            File systemFolder;
453                            try {
454                                    systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
455                            }
456                            catch (IllegalArgumentException iare) {
457                                    browser = null;
458                                    errorMessage = iare.getMessage();
459                                    return browser;
460                            }
461                            catch (IllegalAccessException iae) {
462                                    browser = null;
463                                    errorMessage = iae.getMessage();
464                                    return browser;
465                            }
466                            catch (InvocationTargetException ite) {
467                                    browser = null;
468                                    errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
469                                    return browser;
470                            }
471                            String[] systemFolderFiles = systemFolder.list();
472                            // Avoid a FilenameFilter because that can't be stopped mid-list
473                            for (int i = 0; i < systemFolderFiles.length; i++) {
474                                    try {
475                                            File file = new File(systemFolder, systemFolderFiles[i]);
476                                            if (!file.isFile()) {
477                                                    continue;
478                                            }
479                                            // We're looking for a file with a creator code of 'MACS'
480                                            // and a type of 'FNDR'. Only requiring the type results in
481                                            // non-Finder applications being picked up on certain Mac OS
482                                            // 9 systems, especially German ones, and sending a GURL
483                                            // event to those applications results in a logout under
484                                            // Multiple Users.
485                                            Object fileType = getFileType.invoke(null, new Object[] { file });
486                                            if (FINDER_TYPE.equals(fileType.toString())) {
487                                                    Object fileCreator = getFileCreator.invoke(null, new Object[] { file });
488                                                    if (FINDER_CREATOR.equals(fileCreator.toString())) {
489                                                            browser = file.toString();// Actually the Finder,
490                                                            // but that's OK
491                                                            return browser;
492                                                    }
493                                            }
494                                    }
495                                    catch (IllegalArgumentException iare) {
496                                            errorMessage = iare.getMessage();
497                                            return null;
498                                    }
499                                    catch (IllegalAccessException iae) {
500                                            browser = null;
501                                            errorMessage = iae.getMessage();
502                                            return browser;
503                                    }
504                                    catch (InvocationTargetException ite) {
505                                            browser = null;
506                                            errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
507                                            return browser;
508                                    }
509                            }
510                            browser = null;
511                            break;
512                    case MRJ_3_0:
513                    case MRJ_3_1:
514                            browser = "";// Return something non-null
515                            break;
516                    case WINDOWS_NT:
517                            browser = "cmd.exe";
518                            break;
519                    case WINDOWS_9x:
520                            browser = "command.com";
521                            break;
522                    case OTHER:
523                    default:
524                            browser = "firefox";
525                            break;
526                    }
527                    return browser;
528            }
529    
530            /**
531             * Attempts to open the default web browser to the given URL.
532             *
533             * @param url The URL to open
534             * @throws RuntimeException If the web browser could not be located or does
535             * not run
536             */
537            public static void openURL(final URL url) {
538                    Runnable opener = new Runnable() {
539                            public void run() {
540                                    try {
541                                            String urlString = url.toExternalForm();
542    
543                                            if (!loadedWithoutErrors) {
544                                                    throw new IOException("Exception in finding browser: " + errorMessage);
545                                            }
546                                            Object browser = locateBrowser();
547                                            if (browser == null) {
548                                                    throw new IOException("Unable to locate browser: " + errorMessage);
549                                            }
550    
551                                            switch (jvm) {
552                                            case MRJ_2_0:
553                                                    Object aeDesc = null;
554                                                    try {
555                                                            aeDesc = aeDescConstructor.newInstance(new Object[] { urlString });
556                                                            putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
557                                                            sendNoReply.invoke(browser, new Object[] {});
558                                                    }
559                                                    catch (InvocationTargetException ite) {
560                                                            throw new IOException("InvocationTargetException while creating AEDesc: "
561                                                                            + ite.getMessage());
562                                                    }
563                                                    catch (IllegalAccessException iae) {
564                                                            throw new IOException("IllegalAccessException while building AppleEvent: "
565                                                                            + iae.getMessage());
566                                                    }
567                                                    catch (InstantiationException ie) {
568                                                            throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
569                                                    }
570                                                    finally {
571                                                            // Encourage it to get disposed if it was created
572                                                            aeDesc = null;
573                                                            browser = null;// Ditto
574                                                    }
575                                                    break;
576                                            case MRJ_2_1:
577                                                    Runtime.getRuntime().exec(new String[] { (String) browser, urlString });
578                                                    break;
579                                            case MRJ_3_0:
580                                                    int[] instance = new int[1];
581                                                    int result = ICStart(instance, 0);
582                                                    if (result == 0) {
583                                                            int[] selectionStart = new int[] { 0 };
584                                                            byte[] urlBytes = urlString.getBytes();
585                                                            int[] selectionEnd = new int[] { urlBytes.length };
586                                                            result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, urlBytes.length,
587                                                                            selectionStart, selectionEnd);
588                                                            if (result == 0) {
589                                                                    // Ignore the return value; the URL was launched
590                                                                    // successfully
591                                                                    // regardless of what happens here.
592                                                                    ICStop(instance);
593                                                            }
594                                                            else {
595                                                                    throw new IOException("Unable to launch URL: " + result);
596                                                            }
597                                                    }
598                                                    else {
599                                                            throw new IOException("Unable to create an Internet Config instance: " + result);
600                                                    }
601                                                    break;
602                                            case MRJ_3_1:
603                                                    try {
604                                                            openURL.invoke(null, new Object[] { urlString });
605                                                    }
606                                                    catch (InvocationTargetException ite) {
607                                                            throw new IOException("InvocationTargetException while calling openURL: "
608                                                                            + ite.getMessage());
609                                                    }
610                                                    catch (IllegalAccessException iae) {
611                                                            throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage());
612                                                    }
613                                                    break;
614                                            case WINDOWS_NT:
615                                            case WINDOWS_9x:
616                                                    // Add quotes around the URL to allow ampersands and
617                                                    // other
618                                                    // special
619                                                    // characters to work.
620                                                    Process process = Runtime.getRuntime().exec(
621                                                                    new String[] { (String) browser, FIRST_WINDOWS_PARAMETER, SECOND_WINDOWS_PARAMETER,
622                                                                                    THIRD_WINDOWS_PARAMETER, '"' + urlString + '"' });
623                                                    // This avoids a memory leak on some versions of Java on
624                                                    // Windows.
625                                                    // That's hinted at in
626                                                    // <http://developer.java.sun.com/developer/qow/archive/68/>.
627                                                    try {
628                                                            process.waitFor();
629                                                            process.exitValue();
630                                                    }
631                                                    catch (InterruptedException ie) {
632                                                            throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
633                                                    }
634                                                    break;
635                                            case OTHER:
636                                                    // Assume that we're on Unix and that firefox is
637                                                    // installed
638                                                    process = Runtime.getRuntime().exec(new String[] { (String) browser, urlString });
639                                                    try {
640                                                            process.waitFor();
641                                                            process.exitValue();
642                                                    }
643                                                    catch (InterruptedException ie) {
644                                                            throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
645                                                    }
646                                                    break;
647                                            default:
648                                                    // This should never occur, but if it does, we'll try
649                                                    // the
650                                                    // simplest thing possible
651                                                    Runtime.getRuntime().exec(new String[] { (String) browser, urlString });
652                                                    break;
653                                            }
654                                    }
655                                    catch (IOException e) {
656                                            throw new RuntimeException(e.toString());
657                                    }
658    
659                            }
660                    };
661                    if (SwingUtilities.isEventDispatchThread()) {
662                            new Thread(opener).start();
663                    }
664                    else {
665                            opener.run();
666                    }
667            }
668    
669            /**
670             * Methods required for Mac OS X. The presence of native methods does not
671             * cause any problems on other platforms.
672             */
673            private native static int ICStart(int[] instance, int signature);
674    
675            private native static int ICStop(int[] instance);
676    
677            private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, int[] selectionStart,
678                            int[] selectionEnd);
679    
680            public static void main(String[] args) throws Exception {
681                    BrowserLauncher.openURL(new URL("http://www.google.com"));
682            }
683    }