PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 2ms app.codeStats 0ms

/src/jade/gui/BrowserLauncher.java

https://bitbucket.org/nevo/jade
Java | 478 lines | 290 code | 52 blank | 136 comment | 29 complexity | 6854decf56f4d9f2d9821b2e1ec5f8b8 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
  1. package jade.gui;
  2. //#MIDP_EXCLUDE_FILE
  3. import java.io.File;
  4. import java.io.FileNotFoundException;
  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. * BrowserLauncher is a class that provides one static method, openURL, which opens the default
  12. * web browser for the current user of the system to the given URL. It may support other
  13. * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously
  14. * 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
  17. * that are not part of the standard JDK. What we're trying to do, though, is to take something
  18. * that's frequently desirable but inherently platform-specific -- opening a default browser --
  19. * and allow programmers (you, for example) to do so without worrying about dropping into native
  20. * code or doing anything else similarly evil.
  21. * <p>
  22. * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without
  23. * modification or a need for additional libraries. All classes that are required on certain
  24. * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not
  25. * found, will not cause this to do anything other than returning an error when opening the
  26. * browser.
  27. * <p>
  28. * There are certain system requirements for this class, as it's running through Runtime.exec(),
  29. * which is Java's way of making a native system call. Currently, this requires that a Macintosh
  30. * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that
  31. * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder
  32. * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and
  33. * 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems
  34. * (Windows 95, 98, and NT 4.0, as well as later versions of all). On other systems, this drops
  35. * back from the inherently platform-sensitive concept of a default browser and simply attempts
  36. * to launch Netscape via a shell command.
  37. * <p>
  38. * This code is Copyright 1999 by Eric Albert (ejalbert@cs.stanford.edu) and may be redistributed
  39. * or modified in any form without restrictions as long as the portion of this comment from this
  40. * paragraph through the end of the comment is not removed. The author requests that he be
  41. * notified of any application, applet, or other binary that makes use of this code, but that's
  42. * more out of curiosity than anything and is not required. This software includes no warranty.
  43. * <p>
  44. * Credits:
  45. * <br>Steven Spencer, JavaWorld magazine (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip 66</a>)
  46. * <br>Ron B. Yeh, ZeroG
  47. * <br>Ben Engber, The New York Times
  48. * <br>Paul Teitlebaum and Andrea Cantatore, Datatech Software
  49. *
  50. * @author Eric Albert (<a href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
  51. * @version 1.2 (Released July 28, 1999)
  52. */
  53. public class BrowserLauncher {
  54. /**
  55. * The Java virtual machine that we are running on. Actually, in most cases we only care
  56. * about the operating system, but some operating systems require us to switch on the VM. */
  57. private static int jvm;
  58. /** The browser for the system */
  59. private static Object browser;
  60. /**
  61. * Caches whether any classes, methods, and fields that are not part of the JDK and need to
  62. * be dynamically loaded at runtime loaded successfully.
  63. * <p>
  64. * Note that if this is <code>false</code>, <code>openURL()</code> will always return an
  65. * IOException.
  66. */
  67. private static boolean loadedWithoutErrors;
  68. /** The com.apple.mrj.MRJFileUtils class */
  69. private static Class mrjFileUtilsClass;
  70. /** The com.apple.mrj.MRJOSType class */
  71. private static Class mrjOSTypeClass;
  72. /** The com.apple.MacOS.MacOSError class */
  73. private static Class macOSErrorClass;
  74. /** The com.apple.MacOS.AEDesc class */
  75. private static Class aeDescClass;
  76. /** The <init>(int) method of com.apple.MacOS.AETarget */
  77. private static Constructor aeTargetConstructor;
  78. /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
  79. private static Constructor appleEventConstructor;
  80. /** The <init>(String) method of com.apple.MacOS.AEDesc */
  81. private static Constructor aeDescConstructor;
  82. /** The findFolder method of com.apple.mrj.MRJFileUtils */
  83. private static Method findFolder;
  84. /** The getFileType method of com.apple.mrj.MRJOSType */
  85. private static Method getFileType;
  86. /** The makeOSType method of com.apple.MacOS.OSUtils */
  87. private static Method makeOSType;
  88. /** The putParameter method of com.apple.MacOS.AppleEvent */
  89. private static Method putParameter;
  90. /** The sendNoReply method of com.apple.MacOS.AppleEvent */
  91. private static Method sendNoReply;
  92. /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
  93. private static Object kSystemFolderType;
  94. /** The keyDirectObject AppleEvent parameter type */
  95. private static Integer keyDirectObject;
  96. /** The kAutoGenerateReturnID AppleEvent code */
  97. private static Integer kAutoGenerateReturnID;
  98. /** The kAnyTransactionID AppleEvent code */
  99. private static Integer kAnyTransactionID;
  100. /** JVM constant for MRJ 2.0 */
  101. private static final int MRJ_2_0 = 0;
  102. /** JVM constant for MRJ 2.1 or later */
  103. private static final int MRJ_2_1 = 1;
  104. /** JVM constant for any Windows NT JVM */
  105. private static final int WINDOWS_NT = 2;
  106. /** JVM constant for any Windows 9x JVM */
  107. private static final int WINDOWS_9x = 3;
  108. private static final int WINDOWS_2000 = 4;
  109. /** JVM constant for any other platform */
  110. private static final int OTHER = -1;
  111. /**
  112. * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep non-U.S. English
  113. * systems from working properly.
  114. */
  115. private static final String FINDER_TYPE = "FNDR";
  116. /**
  117. * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the
  118. * application.
  119. */
  120. private static final String FINDER_CREATOR = "MACS";
  121. /** The name for the AppleEvent type corresponding to a GetURL event. */
  122. private static final String GURL_EVENT = "GURL";
  123. /**
  124. * The first parameter that needs to be passed into Runtime.exec() to open the default web
  125. * browser on Windows.
  126. */
  127. private static final String FIRST_WINDOWS_PARAMETER = "/c";
  128. /** The second parameter for Runtime.exec() on Windows. */
  129. private static final String SECOND_WINDOWS_PARAMETER = "start";
  130. /**
  131. * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape
  132. * on many command-line systems.
  133. */
  134. private static final String NETSCAPE_OPEN_PARAMETER_START = " -remote 'openURL(";
  135. private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
  136. /**
  137. * The message from any exception thrown throughout the initialization process.
  138. */
  139. private static String errorMessage;
  140. /**
  141. The URL for the official JADE Home Page.
  142. */
  143. public static String jadeURL ="http://jade.tilab.com/";
  144. /**
  145. * An initialization block that determines the operating system and loads the necessary
  146. * runtime data.
  147. */
  148. static {
  149. loadedWithoutErrors = true;
  150. String osName = System.getProperty("os.name");
  151. if ("Mac OS".equals(osName)) {
  152. String mrjVersion = System.getProperty("mrj.version");
  153. String majorMRJVersion = mrjVersion.substring(0, 3);
  154. try {
  155. double version = Double.valueOf(majorMRJVersion).doubleValue();
  156. if (version == 2) {
  157. jvm = MRJ_2_0;
  158. } else if (version >= 2.1) {
  159. // For the time being, assume that all post-2.0 versions of MRJ work the same
  160. jvm = MRJ_2_1;
  161. } else {
  162. loadedWithoutErrors = false;
  163. errorMessage = "Unsupported MRJ version: " + version;
  164. }
  165. } catch (NumberFormatException nfe) {
  166. loadedWithoutErrors = false;
  167. errorMessage = "Invalid MRJ version: " + mrjVersion;
  168. }
  169. } else if (osName.startsWith("Windows")) {
  170. if (osName.indexOf("9") != -1) {
  171. jvm = WINDOWS_9x;
  172. }
  173. else
  174. if(osName.indexOf("2") != -1){
  175. jvm = WINDOWS_2000;
  176. }
  177. else {
  178. jvm = WINDOWS_NT;
  179. }
  180. }
  181. else {
  182. jvm = OTHER;
  183. }
  184. if (loadedWithoutErrors) { // if we haven't hit any errors yet
  185. loadedWithoutErrors = loadClasses();
  186. }
  187. }
  188. /**
  189. * This class should be never be instantiated; this just ensures so.
  190. */
  191. private BrowserLauncher() { }
  192. /**
  193. * Called by a static initializer to load any classes, fields, and methods required at runtime
  194. * to locate the user's web browser.
  195. * @return <code>true</code> if all intialization succeeded
  196. * <code>false</code> if any portion of the initialization failed
  197. */
  198. private static boolean loadClasses() {
  199. switch (jvm) {
  200. case MRJ_2_0:
  201. try {
  202. Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
  203. macOSErrorClass = Class.forName("com.apple.MacOS.MacOSError");
  204. Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
  205. Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
  206. Class aeClass = Class.forName("com.apple.MacOS.ae");
  207. aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
  208. aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class });
  209. appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class });
  210. aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class });
  211. makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class });
  212. putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass });
  213. sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { });
  214. Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject");
  215. keyDirectObject = (Integer) keyDirectObjectField.get(null);
  216. Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID");
  217. kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null);
  218. Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID");
  219. kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
  220. } catch (ClassNotFoundException cnfe) {
  221. errorMessage = cnfe.getMessage();
  222. return false;
  223. } catch (NoSuchMethodException nsme) {
  224. errorMessage = nsme.getMessage();
  225. return false;
  226. } catch (NoSuchFieldException nsfe) {
  227. errorMessage = nsfe.getMessage();
  228. return false;
  229. } catch (IllegalAccessException iae) {
  230. errorMessage = iae.getMessage();
  231. return false;
  232. }
  233. break;
  234. case MRJ_2_1:
  235. try {
  236. mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
  237. mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
  238. Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType");
  239. kSystemFolderType = systemFolderField.get(null);
  240. findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass });
  241. getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class });
  242. } catch (ClassNotFoundException cnfe) {
  243. errorMessage = cnfe.getMessage();
  244. return false;
  245. } catch (NoSuchFieldException nsfe) {
  246. errorMessage = nsfe.getMessage();
  247. return false;
  248. } catch (NoSuchMethodException nsme) {
  249. errorMessage = nsme.getMessage();
  250. return false;
  251. } catch (SecurityException se) {
  252. errorMessage = se.getMessage();
  253. return false;
  254. } catch (IllegalAccessException iae) {
  255. errorMessage = iae.getMessage();
  256. return false;
  257. }
  258. break;
  259. }
  260. return true;
  261. }
  262. /**
  263. * Attempts to locate the default web browser on the local system. Caches results so it
  264. * only locates the browser once for each use of this class per JVM instance.
  265. * @return The browser for the system. Note that this may not be what you would consider
  266. * to be a standard web browser; instead, it's the application that gets called to
  267. * open the default web browser. In some cases, this will be a non-String object
  268. * that provides the means of calling the default browser.
  269. */
  270. private static Object locateBrowser() {
  271. if (browser != null) {
  272. return browser;
  273. }
  274. switch (jvm) {
  275. case MRJ_2_0:
  276. try {
  277. Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR });
  278. Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode });
  279. Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT });
  280. Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID });
  281. // Don't set browser = appleEvent because then the next time we call
  282. // locateBrowser(), we'll get the same AppleEvent, to which we'll already have
  283. // added the relevant parameter. Instead, regenerate the AppleEvent every time.
  284. // There's probably a way to do this better; if any has any ideas, please let
  285. // me know.
  286. return appleEvent;
  287. } catch (IllegalAccessException iae) {
  288. browser = null;
  289. errorMessage = iae.getMessage();
  290. return browser;
  291. } catch (InstantiationException ie) {
  292. browser = null;
  293. errorMessage = ie.getMessage();
  294. return browser;
  295. } catch (InvocationTargetException ite) {
  296. browser = null;
  297. errorMessage = ite.getMessage();
  298. return browser;
  299. }
  300. case MRJ_2_1:
  301. File systemFolder;
  302. try {
  303. systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType });
  304. } catch (IllegalArgumentException iare) {
  305. browser = null;
  306. errorMessage = iare.getMessage();
  307. return browser;
  308. } catch (IllegalAccessException iae) {
  309. browser = null;
  310. errorMessage = iae.getMessage();
  311. return browser;
  312. } catch (InvocationTargetException ite) {
  313. browser = null;
  314. errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
  315. return browser;
  316. }
  317. String[] systemFolderFiles = systemFolder.list();
  318. // Avoid a FilenameFilter because that can't be stopped mid-list
  319. for(int i = 0; i < systemFolderFiles.length; i++) {
  320. try {
  321. File file = new File(systemFolder, systemFolderFiles[i]);
  322. if (!file.isFile()) {
  323. continue;
  324. }
  325. Object fileType = getFileType.invoke(null, new Object[] { file });
  326. if (FINDER_TYPE.equals(fileType.toString())) {
  327. browser = file.toString(); // Actually the Finder, but that's OK
  328. return browser;
  329. }
  330. } catch (IllegalArgumentException iare) {
  331. browser = browser;
  332. errorMessage = iare.getMessage();
  333. return null;
  334. } catch (IllegalAccessException iae) {
  335. browser = null;
  336. errorMessage = iae.getMessage();
  337. return browser;
  338. } catch (InvocationTargetException ite) {
  339. browser = null;
  340. errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage();
  341. return browser;
  342. }
  343. }
  344. browser = null;
  345. break;
  346. case WINDOWS_NT:
  347. browser = "cmd.exe";
  348. break;
  349. case WINDOWS_9x:
  350. browser = "command.com";
  351. break;
  352. case WINDOWS_2000:
  353. browser ="";
  354. break;
  355. case OTHER:
  356. default:
  357. browser = "netscape";
  358. break;
  359. }
  360. return browser;
  361. }
  362. /**
  363. * Attempts to open the default web browser to the given URL.
  364. * @param url The URL to open
  365. * @throws IOException If the web browser could not be located or does not run
  366. */
  367. public static void openURL(String url) throws IOException {
  368. if (!loadedWithoutErrors) {
  369. throw new IOException("Exception in finding browser: " + errorMessage);
  370. }
  371. Object browser = locateBrowser();
  372. if (browser == null) {
  373. throw new IOException("Unable to locate browser: " + errorMessage);
  374. }
  375. switch (jvm) {
  376. case MRJ_2_0:
  377. Object aeDesc = null;
  378. try {
  379. aeDesc = aeDescConstructor.newInstance(new Object[] { url });
  380. putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc });
  381. sendNoReply.invoke(browser, new Object[] { });
  382. } catch (InvocationTargetException ite) {
  383. throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage());
  384. } catch (IllegalAccessException iae) {
  385. throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage());
  386. } catch (InstantiationException ie) {
  387. throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage());
  388. } finally {
  389. aeDesc = null; // Encourage it to get disposed if it was created
  390. browser = null; // Ditto
  391. }
  392. break;
  393. case MRJ_2_1:
  394. Runtime.getRuntime().exec(new String[] { (String) browser, url } );
  395. break;
  396. case WINDOWS_NT:
  397. case WINDOWS_9x:
  398. Runtime.getRuntime().exec(new String[] { (String)browser, FIRST_WINDOWS_PARAMETER,
  399. SECOND_WINDOWS_PARAMETER, url });
  400. break;
  401. case WINDOWS_2000:
  402. String cmd = "rundll32" + " " + "url.dll" + " " + "FileProtocolHandler"+ " " + url;
  403. Runtime.getRuntime().exec(cmd);
  404. break;
  405. case OTHER:
  406. // Assume that we're on Unix and that Netscape is installed
  407. // First, attempt to open the URL in a currently running session of Netscape
  408. Process process = Runtime.getRuntime().exec((String) browser +
  409. NETSCAPE_OPEN_PARAMETER_START + url +
  410. NETSCAPE_OPEN_PARAMETER_END);
  411. try {
  412. int exitCode = process.waitFor();
  413. if (exitCode != 0) { // if Netscape was not open
  414. Runtime.getRuntime().exec(new String[] { (String) browser, url });
  415. }
  416. } catch (InterruptedException ie) {
  417. throw new IOException("InterruptedException while launching browser: " + ie.getMessage());
  418. }
  419. break;
  420. default:
  421. // This should never occur, but if it does, we'll try the simplest thing possible
  422. Runtime.getRuntime().exec(new String[] { (String) browser, url });
  423. break;
  424. }
  425. }
  426. }