001/*
002 * $Id: WebBrowser.java,v 1.7 2011/03/24 16:06:35 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
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 */
030package edu.wisc.ssec.mcidasv.util;
031
032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.list;
033
034import java.io.IOException;
035import java.lang.reflect.Method;
036import java.net.URI;
037import java.util.List;
038
039import javax.swing.JOptionPane;
040
041import ucar.unidata.util.LogUtil;
042
043import edu.wisc.ssec.mcidasv.McIDASV;
044
045public 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, new Object[] {browseConst});
113                if (b.booleanValue()) {
114                    final Method browse = desktop.getMethod("browse", new Class[]{ URI.class });
115                    browse.invoke(desktopInstance, new Object[] { 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}