001 /*
002 * $Id: WebBrowser.java,v 1.9 2012/02/19 17:35:52 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030 package edu.wisc.ssec.mcidasv.util;
031
032 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.list;
033
034 import java.io.IOException;
035 import java.lang.reflect.Method;
036 import java.net.URI;
037 import java.util.List;
038
039 import javax.swing.JOptionPane;
040
041 import ucar.unidata.util.LogUtil;
042
043 import edu.wisc.ssec.mcidasv.McIDASV;
044
045 public final class WebBrowser {
046
047 /** Probe Unix-like systems for these browsers, in this order. */
048 private static final List<String> unixBrowsers =
049 list("firefox", "konqueror", "opera", "mozilla", "netscape");
050
051 /** None shall instantiate WebBrowser!! */
052 private WebBrowser() { }
053
054 /**
055 * Attempts to use the system default browser to visit {@code url}. Tries
056 * looking for and executing any browser specified by the IDV property
057 * {@literal "idv.browser.path"}.
058 *
059 * <p>If the property wasn't given or there
060 * was an error, try the new (as of Java 1.6) way of opening a browser.
061 *
062 * <p>If the previous attempts failed (or we're in 1.5), we finally try
063 * some more primitive measures.
064 *
065 * <p>Note: if you are trying to use this method with a
066 * {@link javax.swing.JTextPane} you may need to turn off editing via
067 * {@link javax.swing.JTextPane#setEditable(boolean)}.
068 *
069 * @param url URL to visit.
070 *
071 * @see #tryUserSpecifiedBrowser(String)
072 * @see #openNewStyle(String)
073 * @see #openOldStyle(String)
074 */
075 public static void browse(final String url) {
076 // if the user has taken the trouble to explicitly provide the path to
077 // a web browser, we should probably use it.
078 if (tryUserSpecifiedBrowser(url))
079 return;
080
081 // determine whether or not we can use the 1.6 classes
082 if (canAttemptNewStyle())
083 if (openNewStyle(url))
084 return;
085
086 // if not, use the hacky stuff.
087 openOldStyle(url);
088 }
089
090 /**
091 * Uses the new functionality in {@link java.awt.Desktop} to try opening
092 * the browser. Because McIDAS-V does not yet require Java 1.6, and
093 * {@code Desktop} was introduced in 1.6, we have to jump through some
094 * reflection hoops.
095 *
096 * @param url URL to visit.
097 *
098 * @return Either {@code true} if things look ok, {@code false} if there
099 * were problems.
100 */
101 private static boolean openNewStyle(final String url) {
102 boolean retVal = true;
103 try {
104 Class<?> desktop = Class.forName("java.awt.Desktop");
105 Method isDesktopSupported = desktop.getMethod("isDesktopSupported", (Class<?>[])null);
106 Boolean b = (Boolean)isDesktopSupported.invoke(null, (Object[])null);
107 if (b.booleanValue()) {
108 final Object desktopInstance = desktop.getMethod("getDesktop", (Class<?>[])null).invoke(null, (Object[])null);
109 Class<?> desktopAction = Class.forName("java.awt.Desktop$Action");
110 Method isSupported = desktop.getMethod("isSupported", new Class[] { desktopAction });
111 Object browseConst = desktopAction.getField("BROWSE").get(null);
112 b = (Boolean)isSupported.invoke(desktopInstance, browseConst);
113 if (b.booleanValue()) {
114 final Method browse = desktop.getMethod("browse", new Class[]{ URI.class });
115 browse.invoke(desktopInstance, new URI(url));
116 retVal = true;
117 } else {
118 retVal = false;
119 }
120 } else {
121 retVal = false;
122 }
123 } catch (ClassNotFoundException e) {
124 // JDK 5, ignore
125 retVal = false;
126 } catch (Exception e) {
127 retVal = false;
128 }
129 return retVal;
130 }
131
132 /**
133 * Uses {@link Runtime#exec(String)} to launch the user's preferred web
134 * browser. This method isn't really recommended unless you're stuck with
135 * Java 1.5.
136 *
137 * <p>Note that the browsers need to be somewhere in the PATH, as this
138 * method uses the {@code which} command (also needs to be in the PATH!).
139 *
140 * @param url URL to visit.
141 */
142 private static void openOldStyle(final String url) {
143 try {
144 if (isWindows()) {
145 Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
146 } else if (isMac()) {
147 Runtime.getRuntime().exec("/usr/bin/open "+url);
148 } else {
149 for (String browser : unixBrowsers) {
150 if (Runtime.getRuntime().exec("which "+browser).waitFor() == 0) {
151 Runtime.getRuntime().exec(browser+' '+url);
152 return;
153 }
154 }
155 throw new IOException("Could not find a web browser to launch (tried "+unixBrowsers+')');
156 }
157 } catch (Exception e) {
158 JOptionPane.showMessageDialog(null, "Problem running web browser:\n" + e.getLocalizedMessage());
159 }
160 }
161
162 /**
163 * Attempts to launch the browser pointed at by
164 * the {@literal "idv.browser.path"} IDV property, if it has been set.
165 *
166 * @param url URL to open.
167 *
168 * @return Either {@code true} if the command-line was executed, {@code false} if
169 * either the command-line wasn't launched or {@literal "idv.browser.path"}
170 * was not set.
171 */
172 private static boolean tryUserSpecifiedBrowser(final String url) {
173 McIDASV mcv = McIDASV.getStaticMcv();
174 if (mcv != null) {
175 String browserPath = mcv.getProperty("idv.browser.path", (String)null);
176 if (browserPath != null && browserPath.trim().length() > 0) {
177 try {
178 Runtime.getRuntime().exec(browserPath+' '+url);
179 return true;
180 } catch (Exception e) {
181 LogUtil.logException("Executing browser: "+browserPath, e);
182 }
183 }
184 }
185 return false;
186 }
187
188 /**
189 * There's supposedly a bug lurking that can hang the JVM on Linux if
190 * {@code java.net.useSystemProxies} is enabled. Detect whether or not our
191 * configuration may trigger the bug.
192 *
193 * @return Either {@code true} if everything is ok, {@code false}
194 * otherwise.
195 */
196 private static boolean canAttemptNewStyle() {
197 if (Boolean.getBoolean("java.net.useSystemProxies") && isUnix()) {
198 // remove this check if JDK's bug 6496491 is fixed or if we can
199 // assume ORBit >= 2.14.2 and gnome-vfs >= 2.16.1
200 return false;
201 }
202 return true;
203 }
204
205 /**
206 * @return Are we shiny, happy OS X users?
207 */
208 private static boolean isMac() {
209 return System.getProperty("os.name", "").startsWith("Mac OS");
210 }
211
212 /**
213 * @return Do we perhaps think that beards and suspenders are the height
214 * of fashion?
215 */
216 private static boolean isUnix() {
217 return !isMac() && !isWindows();
218 }
219
220 /**
221 * @return Are we running Windows??
222 */
223 private static boolean isWindows() {
224 return System.getProperty("os.name", "").startsWith("Windows");
225 }
226
227 public static void main(String[] args) {
228 browse("http://www.haskell.org/"); // sassy!
229 }
230 }