View Javadoc

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