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