PageRenderTime 57ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/edu/stanford/ejalbert/BrowserLauncher.java

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