1
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
203
204
205 jvm = MRJ_2_1;
206 } else if ( version == 3.0 ) {
207 jvm = MRJ_3_0;
208 } else if ( version >= 3.1 ) {
209
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 ) {
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
422
423
424
425
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
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
469
470
471
472
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();
483 return browser;
484 }
485 }
486 } catch ( IllegalArgumentException iare ) {
487
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 = "";
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;
561 browser = null;
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
585
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
613
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
621
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
633
634
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 ) {
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
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 }