2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
24 import java.io.IOException;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
31 * BrowserLauncher is a class that provides one static method, openURL, which
32 * opens the default web browser for the current user of the system to the given
33 * URL. It may support other protocols depending on the system -- mailto, ftp,
34 * etc. -- but that has not been rigorously tested and is not guaranteed to
37 * Yes, this is platform-specific code, and yes, it may rely on classes on
38 * certain platforms that are not part of the standard JDK. What we're trying to
39 * do, though, is to take something that's frequently desirable but inherently
40 * platform-specific -- opening a default browser -- and allow programmers (you,
41 * for example) to do so without worrying about dropping into native code or
42 * doing anything else similarly evil.
44 * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant
45 * systems without modification or a need for additional libraries. All classes
46 * that are required on certain platforms to allow this to run are dynamically
47 * loaded at runtime via reflection and, if not found, will not cause this to do
48 * anything other than returning an error when opening the browser.
50 * There are certain system requirements for this class, as it's running through
51 * Runtime.exec(), which is Java's way of making a native system call.
52 * Currently, this requires that a Macintosh have a Finder which supports the
53 * GURL event, which is true for Mac OS 8.0 and 8.1 systems that have the
54 * Internet Scripting AppleScript dictionary installed in the Scripting
55 * Additions folder in the Extensions folder (which is installed by default as
56 * far as I know under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later
57 * systems. On Windows, it only runs under Win32 systems (Windows 95, 98, and NT
58 * 4.0, as well as later versions of all). On other systems, this drops back
59 * from the inherently platform-sensitive concept of a default browser and
60 * simply attempts to launch Netscape via a shell command.
62 * This code is Copyright 1999-2001 by Eric Albert (ejalbert\@cs.stanford.edu)
63 * and may be redistributed or modified in any form without restrictions as long
64 * as the portion of this comment from this paragraph through the end of the
65 * comment is not removed. The author requests that he be notified of any
66 * application, applet, or other binary that makes use of this code, but that's
67 * more out of curiosity than anything and is not required. This software
68 * includes no warranty. The author is not repsonsible for any loss of data or
69 * functionality or any adverse or unexpected effects of using this software.
72 * Steven Spencer, JavaWorld magazine
73 * (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java
75 * Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea
76 * Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk
78 * @author Eric Albert (<a href=
79 * "mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
80 * @version 1.4b1 (Released June 20, 2001)
82 public class BrowserLauncher
85 * The Java virtual machine that we are running on. Actually, in most cases we
86 * only care about the operating system, but some operating systems require us
87 * to switch on the VM.
89 private static int jvm;
91 /** The browser for the system */
92 private static Object browser;
95 * Caches whether any classes, methods, and fields that are not part of the
96 * JDK and need to be dynamically loaded at runtime loaded successfully.
98 * Note that if this is <code>false</code>, <code>openURL()</code> will always
99 * return an IOException.
101 private static boolean loadedWithoutErrors;
103 /** The com.apple.mrj.MRJFileUtils class */
104 private static Class mrjFileUtilsClass;
106 /** The com.apple.mrj.MRJOSType class */
107 private static Class mrjOSTypeClass;
109 /** The com.apple.MacOS.AEDesc class */
110 private static Class aeDescClass;
112 /** The <init>(int) method of com.apple.MacOS.AETarget */
113 private static Constructor aeTargetConstructor;
115 /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
116 private static Constructor appleEventConstructor;
118 /** The <init>(String) method of com.apple.MacOS.AEDesc */
119 private static Constructor aeDescConstructor;
121 /** The findFolder method of com.apple.mrj.MRJFileUtils */
122 private static Method findFolder;
124 /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
125 private static Method getFileCreator;
127 /** The getFileType method of com.apple.mrj.MRJFileUtils */
128 private static Method getFileType;
130 /** The openURL method of com.apple.mrj.MRJFileUtils */
131 private static Method openURL;
133 /** The makeOSType method of com.apple.MacOS.OSUtils */
134 private static Method makeOSType;
136 /** The putParameter method of com.apple.MacOS.AppleEvent */
137 private static Method putParameter;
139 /** The sendNoReply method of com.apple.MacOS.AppleEvent */
140 private static Method sendNoReply;
142 /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
143 private static Object kSystemFolderType;
145 /** The keyDirectObject AppleEvent parameter type */
146 private static Integer keyDirectObject;
148 /** The kAutoGenerateReturnID AppleEvent code */
149 private static Integer kAutoGenerateReturnID;
151 /** The kAnyTransactionID AppleEvent code */
152 private static Integer kAnyTransactionID;
154 /** The linkage object required for JDirect 3 on Mac OS X. */
155 private static Object linkage;
157 /** The framework to reference on Mac OS X */
158 private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
160 /** JVM constant for MRJ 2.0 */
161 private static final int MRJ_2_0 = 0;
163 /** JVM constant for MRJ 2.1 or later */
164 private static final int MRJ_2_1 = 1;
166 /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
167 private static final int MRJ_3_0 = 3;
169 /** JVM constant for MRJ 3.1 */
170 private static final int MRJ_3_1 = 4;
172 /** JVM constant for any Windows NT JVM */
173 private static final int WINDOWS_NT = 5;
175 /** JVM constant for any Windows 9x JVM */
176 private static final int WINDOWS_9x = 6;
178 /** JVM constant for any other platform */
179 private static final int OTHER = -1;
182 * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep
183 * non-U.S. English systems from working properly.
185 private static final String FINDER_TYPE = "FNDR";
188 * The creator code of the Finder on a Macintosh, which is needed to send
189 * AppleEvents to the application.
191 private static final String FINDER_CREATOR = "MACS";
193 /** The name for the AppleEvent type corresponding to a GetURL event. */
194 private static final String GURL_EVENT = "GURL";
197 * The first parameter that needs to be passed into Runtime.exec() to open the
198 * default web browser on Windows.
200 private static final String FIRST_WINDOWS_PARAMETER = "/c";
202 /** The second parameter for Runtime.exec() on Windows. */
203 private static final String SECOND_WINDOWS_PARAMETER = "start";
206 * The third parameter for Runtime.exec() on Windows. This is a "title"
207 * parameter that the command line expects. Setting this parameter allows URLs
208 * containing spaces to work.
210 private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
213 * The shell parameters for Netscape that opens a given URL in an already-open
214 * copy of Netscape on many command-line systems.
216 private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
218 private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL(";
220 private static final String NETSCAPE_OPEN_NEW_WINDOW = ", new-window";
222 private static final String NETSCAPE_OPEN_PARAMETER_END = ")";
225 * The message from any exception thrown throughout the initialization
228 private static String errorMessage;
231 * An initialization block that determines the operating system and loads the
232 * necessary runtime data.
237 loadedWithoutErrors = true;
239 if (!Platform.isJS()) {
246 String osName = System.getProperty("os.name");
248 if (osName.startsWith("Mac OS"))
250 String mrjVersion = System.getProperty("mrj.version");
251 String majorMRJVersion;
252 if (mrjVersion == null)
254 // must be on some later build with mrj support
255 majorMRJVersion = "3.1";
259 majorMRJVersion = mrjVersion.substring(0, 3);
264 double version = Double.valueOf(majorMRJVersion).doubleValue();
270 else if ((version >= 2.1) && (version < 3))
272 // Assume that all 2.x versions of MRJ work the same. MRJ 2.1 actually
273 // works via Runtime.exec() and 2.2 supports that but has an openURL()
275 // as well that we currently ignore.
278 else if (version == 3.0)
282 else if (version >= 3.1)
284 // Assume that all 3.1 and later versions of MRJ work the same.
289 loadedWithoutErrors = false;
290 errorMessage = "Unsupported MRJ version: " + version;
292 } catch (NumberFormatException nfe)
294 loadedWithoutErrors = false;
295 errorMessage = "Invalid MRJ version: " + mrjVersion;
298 else if (osName.startsWith("Windows"))
300 if (osName.indexOf("9") != -1)
314 if (loadedWithoutErrors)
315 { // if we haven't hit any errors yet
316 loadedWithoutErrors = loadClasses();
322 * This class should be never be instantiated; this just ensures so.
324 private BrowserLauncher()
329 * Called by a static initializer to load any classes, fields, and methods
330 * required at runtime to locate the user's web browser.
332 * @return <code>true</code> if all intialization succeeded <code>false</code>
333 * if any portion of the initialization failed
335 private static boolean loadClasses()
338 if (!Platform.isJS())
346 Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
347 Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
348 Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent");
349 Class aeClass = Class.forName("com.apple.MacOS.ae");
350 aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
352 aeTargetConstructor = aeTargetClass
353 .getDeclaredConstructor(new Class[]
355 appleEventConstructor = appleEventClass
356 .getDeclaredConstructor(new Class[]
357 { int.class, int.class, aeTargetClass, int.class,
359 aeDescConstructor = aeDescClass
360 .getDeclaredConstructor(new Class[]
363 makeOSType = osUtilsClass.getDeclaredMethod("makeOSType",
366 putParameter = appleEventClass.getDeclaredMethod("putParameter",
368 { int.class, aeDescClass });
369 sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply",
372 Field keyDirectObjectField = aeClass
373 .getDeclaredField("keyDirectObject");
374 keyDirectObject = (Integer) keyDirectObjectField.get(null);
376 Field autoGenerateReturnIDField = appleEventClass
377 .getDeclaredField("kAutoGenerateReturnID");
378 kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField
381 Field anyTransactionIDField = appleEventClass
382 .getDeclaredField("kAnyTransactionID");
383 kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
384 } catch (ClassNotFoundException cnfe)
386 errorMessage = cnfe.getMessage();
389 } catch (NoSuchMethodException nsme)
391 errorMessage = nsme.getMessage();
394 } catch (NoSuchFieldException nsfe)
396 errorMessage = nsfe.getMessage();
399 } catch (IllegalAccessException iae)
401 errorMessage = iae.getMessage();
412 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
413 mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
415 Field systemFolderField = mrjFileUtilsClass
416 .getDeclaredField("kSystemFolderType");
417 kSystemFolderType = systemFolderField.get(null);
418 findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder",
421 getFileCreator = mrjFileUtilsClass
422 .getDeclaredMethod("getFileCreator", new Class[]
424 getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType",
427 } catch (ClassNotFoundException cnfe)
429 errorMessage = cnfe.getMessage();
432 } catch (NoSuchFieldException nsfe)
434 errorMessage = nsfe.getMessage();
437 } catch (NoSuchMethodException nsme)
439 errorMessage = nsme.getMessage();
442 } catch (SecurityException se)
444 errorMessage = se.getMessage();
447 } catch (IllegalAccessException iae)
449 errorMessage = iae.getMessage();
460 Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
461 Constructor constructor = linker
462 .getConstructor(new Class[]
464 linkage = constructor
465 .newInstance(new Object[]
466 { BrowserLauncher.class });
467 } catch (ClassNotFoundException cnfe)
469 errorMessage = cnfe.getMessage();
472 } catch (NoSuchMethodException nsme)
474 errorMessage = nsme.getMessage();
477 } catch (InvocationTargetException ite)
479 errorMessage = ite.getMessage();
482 } catch (InstantiationException ie)
484 errorMessage = ie.getMessage();
487 } catch (IllegalAccessException iae)
489 errorMessage = iae.getMessage();
500 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
501 openURL = mrjFileUtilsClass.getDeclaredMethod("openURL",
504 } catch (ClassNotFoundException cnfe)
506 errorMessage = cnfe.getMessage();
509 } catch (NoSuchMethodException nsme)
511 errorMessage = nsme.getMessage();
527 * Attempts to locate the default web browser on the local system. s results
528 * so it only locates the browser once for each use of this class per JVM
531 * @return The browser for the system. Note that this may not be what you
532 * would consider to be a standard web browser; instead, it's the
533 * application that gets called to open the default web browser. In
534 * some cases, this will be a non-String object that provides the
535 * means of calling the default browser.
537 private static Object locateBrowser()
557 Integer finderCreatorCode = (Integer) makeOSType.invoke(null,
560 Object aeTarget = aeTargetConstructor
561 .newInstance(new Object[]
562 { finderCreatorCode });
563 Integer gurlType = (Integer) makeOSType.invoke(null,
566 Object appleEvent = appleEventConstructor
567 .newInstance(new Object[]
568 { gurlType, gurlType, aeTarget, kAutoGenerateReturnID,
569 kAnyTransactionID });
571 // Don't set browser = appleEvent because then the next time we call
572 // locateBrowser(), we'll get the same AppleEvent, to which we'll
574 // added the relevant parameter. Instead, regenerate the AppleEvent
576 // There's probably a way to do this better; if any has any ideas,
580 } catch (IllegalAccessException iae)
583 errorMessage = iae.getMessage();
586 } catch (InstantiationException ie)
589 errorMessage = ie.getMessage();
592 } catch (InvocationTargetException ite)
595 errorMessage = ite.getMessage();
606 systemFolder = (File) findFolder.invoke(null,
608 { kSystemFolderType });
609 } catch (IllegalArgumentException iare)
612 errorMessage = iare.getMessage();
615 } catch (IllegalAccessException iae)
618 errorMessage = iae.getMessage();
621 } catch (InvocationTargetException ite)
624 errorMessage = ite.getTargetException().getClass() + ": "
625 + ite.getTargetException().getMessage();
630 String[] systemFolderFiles = systemFolder.list();
632 // Avoid a FilenameFilter because that can't be stopped mid-list
633 for (int i = 0; i < systemFolderFiles.length; i++)
637 File file = new File(systemFolder, systemFolderFiles[i]);
644 // We're looking for a file with a creator code of 'MACS' and
645 // a type of 'FNDR'. Only requiring the type results in non-Finder
646 // applications being picked up on certain Mac OS 9 systems,
647 // especially German ones, and sending a GURL event to those
648 // applications results in a logout under Multiple Users.
649 Object fileType = getFileType.invoke(null, new Object[] { file });
651 if (FINDER_TYPE.equals(fileType.toString()))
653 Object fileCreator = getFileCreator.invoke(null,
657 if (FINDER_CREATOR.equals(fileCreator.toString()))
659 browser = file.toString(); // Actually the Finder, but that's OK
664 } catch (IllegalArgumentException iare)
666 errorMessage = iare.getMessage();
669 } catch (IllegalAccessException iae)
672 errorMessage = iae.getMessage();
675 } catch (InvocationTargetException ite)
678 errorMessage = ite.getTargetException().getClass() + ": "
679 + ite.getTargetException().getMessage();
691 browser = ""; // Return something non-null
701 browser = "command.com";
707 browser = jalview.bin.Cache.getDefault("DEFAULT_BROWSER", "firefox");
719 * used to ensure that browser is up-to-date after a configuration change
720 * (Unix DEFAULT_BROWSER property change).
722 public static void resetBrowser()
728 * Attempts to open the default web browser to the given URL.
732 * @throws IOException
733 * If the web browser could not be located or does not run
735 public static void openURL(String url) throws IOException
738 if (Platform.openURL(url))
748 if (!loadedWithoutErrors)
750 throw new IOException(MessageManager
751 .formatMessage("exception.browser_not_found", new String[]
755 Object browser = locateBrowser();
759 throw new IOException(MessageManager.formatMessage(
760 "exception.browser_unable_to_locate", new String[]
768 Object aeDesc = null;
772 aeDesc = aeDescConstructor.newInstance(new Object[] { url });
773 putParameter.invoke(browser,
775 { keyDirectObject, aeDesc });
776 sendNoReply.invoke(browser, new Object[] {});
777 } catch (InvocationTargetException ite)
779 throw new IOException(MessageManager.formatMessage(
780 "exception.invocation_target_exception_creating_aedesc",
782 { ite.getMessage() }));
783 } catch (IllegalAccessException iae)
785 throw new IOException(MessageManager.formatMessage(
786 "exception.illegal_access_building_apple_evt", new String[]
787 { iae.getMessage() }));
788 } catch (InstantiationException ie)
790 throw new IOException(MessageManager.formatMessage(
791 "exception.illegal_access_building_apple_evt", new String[]
792 { ie.getMessage() }));
795 aeDesc = null; // Encourage it to get disposed if it was created
796 browser = null; // Ditto
802 Runtime.getRuntime().exec(new String[] { (String) browser, url });
808 int[] instance = new int[1];
809 int result = ICStart(instance, 0);
813 int[] selectionStart = new int[] { 0 };
814 byte[] urlBytes = url.getBytes();
815 int[] selectionEnd = new int[] { urlBytes.length };
816 result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
817 urlBytes.length, selectionStart, selectionEnd);
821 // Ignore the return value; the URL was launched successfully
822 // regardless of what happens here.
827 throw new IOException(MessageManager.formatMessage(
828 "exception.unable_to_launch_url", new String[]
829 { Integer.valueOf(result).toString() }));
834 throw new IOException(MessageManager.formatMessage(
835 "exception.unable_to_create_internet_config", new String[]
836 { Integer.valueOf(result).toString() }));
845 openURL.invoke(null, new Object[] { url });
846 } catch (InvocationTargetException ite)
848 throw new IOException(MessageManager.formatMessage(
849 "exception.invocation_target_calling_url", new String[]
850 { ite.getMessage() }));
851 } catch (IllegalAccessException iae)
853 throw new IOException(MessageManager.formatMessage(
854 "exception.illegal_access_calling_url", new String[]
855 { iae.getMessage() }));
863 // Add quotes around the URL to allow ampersands and other special
864 // characters to work.
865 Process process = Runtime.getRuntime()
867 { (String) browser, FIRST_WINDOWS_PARAMETER,
868 SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER,
871 // This avoids a memory leak on some versions of Java on Windows.
872 // That's hinted at in
873 // <http://developer.java.sun.com/developer/qow/archive/68/>.
878 } catch (InterruptedException ie)
880 throw new IOException(MessageManager.formatMessage(
881 "exception.interrupted_launching_browser", new String[]
882 { ie.getMessage() }));
889 // Assume that we're on Unix and that Netscape (actually Firefox) is
891 // First, attempt to open the URL in a currently running session of
896 * System.out.println("Executing : "+browser+" "+
897 * NETSCAPE_REMOTE_PARAMETER+" "+ NETSCAPE_OPEN_PARAMETER_START + url +
898 * NETSCAPE_OPEN_NEW_WINDOW + NETSCAPE_OPEN_PARAMETER_END);
900 process = Runtime.getRuntime()
902 { (String) browser, NETSCAPE_REMOTE_PARAMETER,
904 NETSCAPE_OPEN_PARAMETER_START + url
905 + NETSCAPE_OPEN_NEW_WINDOW
906 + NETSCAPE_OPEN_PARAMETER_END });
910 int exitCode = process.waitFor();
913 { // if Netscape was not open
914 Runtime.getRuntime().exec(new String[] { (String) browser, url });
916 } catch (InterruptedException ie)
918 throw new IOException(MessageManager.formatMessage(
919 "exception.interrupted_launching_browser", new String[]
920 { ie.getMessage() }));
927 // This should never occur, but if it does, we'll try the simplest thing
929 Runtime.getRuntime().exec(new String[] { (String) browser, url });
938 * Methods required for Mac OS X. The presence of native methods does not
939 * cause any problems on other platforms.
941 private native static int ICStart(int[] instance, int signature);
943 private native static int ICStop(int[] instance);
945 private native static int ICLaunchURL(int instance, byte[] hint,
946 byte[] data, int len, int[] selectionStart, int[] selectionEnd);