Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

BrowserLauncher.java

00001 package edu.virtualschool.jwaa.swing;
00002 
00003 import java.io.File;
00004 import java.io.IOException;
00005 import java.lang.reflect.Constructor;
00006 import java.lang.reflect.Field;
00007 import java.lang.reflect.InvocationTargetException;
00008 import java.lang.reflect.Method;
00009 
00054 public class BrowserLauncher 
00055 {
00056 
00060   private static int jvm;
00061 
00063   private static Object browser;
00064 
00072   private static boolean loadedWithoutErrors;
00073 
00075   private static Class mrjFileUtilsClass;
00076 
00078   private static Class mrjOSTypeClass;
00079 
00081   private static Class aeDescClass;
00082   
00084   private static Constructor aeTargetConstructor;
00085   
00087   private static Constructor appleEventConstructor;
00088   
00090   private static Constructor aeDescConstructor;
00091   
00093   private static Method findFolder;
00094 
00096   private static Method getFileCreator;
00097   
00099   private static Method getFileType;
00100   
00102   private static Method openURL;
00103   
00105   private static Method makeOSType;
00106   
00108   private static Method putParameter;
00109   
00111   private static Method sendNoReply;
00112   
00114   private static Object kSystemFolderType;
00115 
00117   private static Integer keyDirectObject;
00118 
00120   private static Integer kAutoGenerateReturnID;
00121   
00123   private static Integer kAnyTransactionID;
00124 
00126   private static Object linkage;
00127   
00129 //  private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
00130 
00132   private static final int MRJ_2_0 = 0;
00133   
00135   private static final int MRJ_2_1 = 1;
00136 
00138   private static final int MRJ_3_0 = 3;
00139   
00141   private static final int MRJ_3_1 = 4;
00142 
00144   private static final int WINDOWS_NT = 5;
00145   
00147   private static final int WINDOWS_9x = 6;
00148 
00150   private static final int OTHER = -1;
00151 
00156   private static final String FINDER_TYPE = "FNDR";
00157 
00162   private static final String FINDER_CREATOR = "MACS";
00163 
00165   private static final String GURL_EVENT = "GURL";
00166 
00171     private static final String FIRST_WINDOWS_PARAMETER = "/c";
00172     
00174     private static final String SECOND_WINDOWS_PARAMETER = "start";
00175     
00181     private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
00182   
00187   private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
00188   private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
00189   private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
00190   
00194   private static String errorMessage;
00195 
00200   static {
00201     loadedWithoutErrors = true;
00202     String osName = System.getProperty("os.name");
00203     if (osName.startsWith("Mac OS")) {
00204       String mrjVersion = System.getProperty("mrj.version");
00205       String majorMRJVersion = mrjVersion.substring(0, 3);
00206       try {
00207         double version = Double.valueOf(majorMRJVersion).doubleValue();
00208         if (version == 2) {
00209           jvm = MRJ_2_0;
00210         } else if (version >= 2.1 && version < 3) {
00211           // Assume that all 2.x versions of MRJ work the same.  MRJ 2.1 actually
00212           // works via Runtime.exec() and 2.2 supports that but has an openURL() method
00213           // as well that we currently ignore.
00214           jvm = MRJ_2_1;
00215         } else if (version == 3.0) {
00216           jvm = MRJ_3_0;
00217         } else if (version >= 3.1) {
00218           // Assume that all 3.1 and later versions of MRJ work the same.
00219           jvm = MRJ_3_1;
00220         } else {
00221           loadedWithoutErrors = false;
00222           errorMessage = "Unsupported MRJ version: " + version;
00223         }
00224       } catch (NumberFormatException nfe) {
00225         loadedWithoutErrors = false;
00226         errorMessage = "Invalid MRJ version: " + mrjVersion;
00227       }
00228     } else if (osName.startsWith("Windows")) {
00229       if (osName.indexOf("9") != -1) {
00230         jvm = WINDOWS_9x;
00231       } else {
00232         jvm = WINDOWS_NT;
00233       }
00234     } else {
00235       jvm = OTHER;
00236     }
00237     
00238     if (loadedWithoutErrors) {  // if we haven't hit any errors yet
00239       loadedWithoutErrors = loadClasses();
00240     }
00241   }
00242 
00246   private BrowserLauncher() { }
00247   
00254   private static boolean loadClasses() {
00255     switch (jvm) {
00256       case MRJ_2_0:
00257         try {
00258           Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
00259           Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
00260           Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
00261           Class aeClass = Class.forName("com.apple.MacOS.ae");
00262           aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
00263 
00264           aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class });
00265           appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class });
00266           aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
00267 
00268           makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class });
00269           putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
00270           sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { });
00271 
00272           Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
00273           keyDirectObject = (Integer) keyDirectObjectField.get(null);
00274           Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
00275           kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
00276           Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
00277           kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
00278         } catch (ClassNotFoundException cnfe) {
00279           errorMessage = cnfe.getMessage();
00280           return false;
00281         } catch (NoSuchMethodException nsme) {
00282           errorMessage = nsme.getMessage();
00283           return false;
00284         } catch (NoSuchFieldException nsfe) {
00285           errorMessage = nsfe.getMessage();
00286           return false;
00287         } catch (IllegalAccessException iae) {
00288           errorMessage = iae.getMessage();
00289           return false;
00290         }
00291         break;
00292       case MRJ_2_1:
00293         try {
00294           mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
00295           mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
00296           Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
00297           kSystemFolderType = systemFolderField.get(null);
00298           findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
00299           getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class });
00300           getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
00301         } catch (ClassNotFoundException cnfe) {
00302           errorMessage = cnfe.getMessage();
00303           return false;
00304         } catch (NoSuchFieldException nsfe) {
00305           errorMessage = nsfe.getMessage();
00306           return false;
00307         } catch (NoSuchMethodException nsme) {
00308           errorMessage = nsme.getMessage();
00309           return false;
00310         } catch (SecurityException se) {
00311           errorMessage = se.getMessage();
00312           return false;
00313         } catch (IllegalAccessException iae) {
00314           errorMessage = iae.getMessage();
00315           return false;
00316         }
00317         break;
00318       case MRJ_3_0:
00319           try {
00320           Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
00321           Constructor constructor = linker.getConstructor(new Class[]{ Class.class });
00322           linkage = constructor.newInstance(new Object[] { BrowserLauncher.class });
00323         } catch (ClassNotFoundException cnfe) {
00324           errorMessage = cnfe.getMessage();
00325           return false;
00326         } catch (NoSuchMethodException nsme) {
00327           errorMessage = nsme.getMessage();
00328           return false;
00329         } catch (InvocationTargetException ite) {
00330           errorMessage = ite.getMessage();
00331           return false;
00332         } catch (InstantiationException ie) {
00333           errorMessage = ie.getMessage();
00334           return false;
00335         } catch (IllegalAccessException iae) {
00336           errorMessage = iae.getMessage();
00337           return false;
00338         }
00339         break;
00340       case MRJ_3_1:
00341         try {
00342           mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
00343           openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class });
00344         } catch (ClassNotFoundException cnfe) {
00345           errorMessage = cnfe.getMessage();
00346           return false;
00347         } catch (NoSuchMethodException nsme) {
00348           errorMessage = nsme.getMessage();
00349           return false;
00350         }
00351         break;
00352       default:
00353           break;
00354     }
00355     return true;
00356   }
00357 
00366   private static Object locateBrowser() {
00367     if (browser != null) {
00368       return browser;
00369     }
00370     switch (jvm) {
00371       case MRJ_2_0:
00372         try {
00373           Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
00374           Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
00375           Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
00376           Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID });
00377           // Don't set browser = appleEvent because then the next time we call
00378           // locateBrowser(), we'll get the same AppleEvent, to which we'll already have
00379           // added the relevant parameter. Instead, regenerate the AppleEvent every time.
00380           // There's probably a way to do this better; if any has any ideas, please let
00381           // me know.
00382           return appleEvent;
00383         } catch (IllegalAccessException iae) {
00384           browser = null;
00385           errorMessage = iae.getMessage();
00386           return browser;
00387         } catch (InstantiationException ie) {
00388           browser = null;
00389           errorMessage = ie.getMessage();
00390           return browser;
00391         } catch (InvocationTargetException ite) {
00392           browser = null;
00393           errorMessage = ite.getMessage();
00394           return browser;
00395         }
00396       case MRJ_2_1:
00397         File systemFolder;
00398         try {
00399           systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
00400         } catch (IllegalArgumentException iare) {
00401           browser = null;
00402           errorMessage = iare.getMessage();
00403           return browser;
00404         } catch (IllegalAccessException iae) {
00405           browser = null;
00406           errorMessage = iae.getMessage();
00407           return browser;
00408         } catch (InvocationTargetException ite) {
00409           browser = null;
00410           errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
00411           return browser;
00412         }
00413         String[] systemFolderFiles = systemFolder.list();
00414         // Avoid a FilenameFilter because that can't be stopped mid-list
00415         for(int i = 0; i < systemFolderFiles.length; i++) {
00416           try {
00417             File file = new File(systemFolder, systemFolderFiles[i]);
00418             if (!file.isFile()) {
00419               continue;
00420             }
00421             // We're looking for a file with a creator code of 'MACS' and
00422             // a type of 'FNDR'.  Only requiring the type results in non-Finder
00423             // applications being picked up on certain Mac OS 9 systems,
00424             // especially German ones, and sending a GURL event to those
00425             // applications results in a logout under Multiple Users.
00426             Object fileType = getFileType.invoke(null, new Object[] { file });
00427             if (FINDER_TYPE.equals(fileType.toString())) {
00428               Object fileCreator = getFileCreator.invoke(null, new Object[] { file });
00429               if (FINDER_CREATOR.equals(fileCreator.toString())) {
00430                 browser = file.toString();  // Actually the Finder, but that's OK
00431                 return browser;
00432               }
00433             }
00434           } catch (IllegalArgumentException iare) {
00435             errorMessage = iare.getMessage();
00436             return null;
00437           } catch (IllegalAccessException iae) {
00438             browser = null;
00439             errorMessage = iae.getMessage();
00440             return browser;
00441           } catch (InvocationTargetException ite) {
00442             browser = null;
00443             errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
00444             return browser;
00445           }
00446         }
00447         browser = null;
00448         break;
00449       case MRJ_3_0:
00450       case MRJ_3_1:
00451         browser = ""; // Return something non-null
00452         break;
00453       case WINDOWS_NT:
00454         browser = "cmd.exe";
00455         break;
00456       case WINDOWS_9x:
00457         browser = "command.com";
00458         break;
00459       case OTHER:
00460       default:
00461         browser = "netscape";
00462         break;
00463     }
00464     return browser;
00465   }
00466 
00472   public static void openURL(String url) throws IOException {
00473     if (!loadedWithoutErrors) {
00474       throw new IOException("Exception in finding browser: " + errorMessage);
00475     }
00476     Object browser = locateBrowser();
00477     if (browser == null) {
00478       throw new IOException("Unable to locate browser: " + errorMessage);
00479     }
00480     
00481     switch (jvm) {
00482       case MRJ_2_0:
00483         Object aeDesc = null;
00484         try {
00485           aeDesc = aeDescConstructor.newInstance(new Object[] { url });
00486           putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
00487           sendNoReply.invoke(browser, new Object[] { });
00488         } catch (InvocationTargetException ite) {
00489           throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage());
00490         } catch (IllegalAccessException iae) {
00491           throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage());
00492         } catch (InstantiationException ie) {
00493           throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
00494         } finally {
00495           aeDesc = null;  // Encourage it to get disposed if it was created
00496           browser = null; // Ditto
00497         }
00498         break;
00499       case MRJ_2_1:
00500         Runtime.getRuntime().exec(new String[] { (String) browser, url } );
00501         break;
00502       case MRJ_3_0:
00503         int[] instance = new int[1];
00504         int result = ICStart(instance, 0);
00505         if (result == 0) {
00506           int[] selectionStart = new int[] { 0 };
00507           byte[] urlBytes = url.getBytes();
00508           int[] selectionEnd = new int[] { urlBytes.length };
00509           result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
00510                       urlBytes.length, selectionStart,
00511                       selectionEnd);
00512           if (result == 0) {
00513             // Ignore the return value; the URL was launched successfully
00514             // regardless of what happens here.
00515             ICStop(instance);
00516           } else {
00517             throw new IOException("Unable to launch URL: " + result);
00518           }
00519         } else {
00520           throw new IOException("Unable to create an Internet Config instance: " + result);
00521         }
00522         break;
00523       case MRJ_3_1:
00524         try {
00525           openURL.invoke(null, new Object[] { url });
00526         } catch (InvocationTargetException ite) {
00527           throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage());
00528         } catch (IllegalAccessException iae) {
00529           throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage());
00530         }
00531         break;
00532         case WINDOWS_NT:
00533         case WINDOWS_9x:
00534           // Add quotes around the URL to allow ampersands and other special
00535           // characters to work.
00536         Process process = Runtime.getRuntime().exec(new String[] { (String) browser,
00537                                 FIRST_WINDOWS_PARAMETER,
00538                                 SECOND_WINDOWS_PARAMETER,
00539                                 THIRD_WINDOWS_PARAMETER,
00540                                 '"' + url + '"' });
00541         // This avoids a memory leak on some versions of Java on Windows.
00542         // That's hinted at in <http://developer.java.sun.com/developer/qow/archive/68/>.
00543         try {
00544           process.waitFor();
00545           process.exitValue();
00546         } catch (InterruptedException ie) {
00547           throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
00548         }
00549         break;
00550       case OTHER:
00551         // Assume that we're on Unix and that Netscape is installed
00552         
00553         // First, attempt to open the URL in a currently running session of Netscape
00554         process = Runtime.getRuntime().exec(new String[] { (String) browser,
00555                           NETSCAPE_REMOTE_PARAMETER,
00556                           NETSCAPE_OPEN_PARAMETER_START +
00557                           url +
00558                           NETSCAPE_OPEN_PARAMETER_END });
00559         try {
00560           int exitCode = process.waitFor();
00561           if (exitCode != 0) {  // if Netscape was not open
00562             Runtime.getRuntime().exec(new String[] { (String) browser, url });
00563           }
00564         } catch (InterruptedException ie) {
00565           throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
00566         }
00567         break;
00568       default:
00569         // This should never occur, but if it does, we'll try the simplest thing possible
00570         Runtime.getRuntime().exec(new String[] { (String) browser, url });
00571         break;
00572     }
00573   }
00574 
00579   private native static int ICStart(int[] instance, int signature);
00580   private native static int ICStop(int[] instance);
00581   private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len,
00582                       int[] selectionStart, int[] selectionEnd);
00583 }