001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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 */
028package edu.wisc.ssec.mcidasv.util;
029
030import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.list;
031
032import java.awt.Desktop;
033import java.io.IOException;
034import java.net.URI;
035import java.net.URISyntaxException;
036import java.util.List;
037
038import javax.swing.JOptionPane;
039
040import ucar.unidata.util.LogUtil;
041
042import edu.wisc.ssec.mcidasv.McIDASV;
043
044/**
045 * A simple utility class for opening a web browser to a given link.
046 */
047public final class WebBrowser {
048
049    /** Probe Unix-like systems for these browsers, in this order. */
050    private static final List<String> unixBrowsers = 
051        list("firefox", "chromium-browser", "google-chrome", "konqueror",
052             "opera", "mozilla", "netscape");
053
054    /** Do not create instances of {@code WebBrowser}. */
055    private WebBrowser() { }
056
057    /**
058     * Attempts to use the system default browser to visit {@code url}. Tries
059     * looking for and executing any browser specified by the IDV property 
060     * {@literal "idv.browser.path"}. 
061     * 
062     * <p>If the property wasn't given or there 
063     * was an error, try the new (as of Java 1.6) way of opening a browser. 
064     * 
065     * <p>If the previous attempts failed (or we're in 1.5), we finally try
066     * some more primitive measures.
067     * 
068     * <p>Note: if you are trying to use this method with a 
069     * {@link javax.swing.JTextPane} you may need to turn off editing via
070     * {@link javax.swing.JTextPane#setEditable(boolean)}.
071     * 
072     * @param url URL to visit.
073     * 
074     * @see #tryUserSpecifiedBrowser(String)
075     * @see #openNewStyle(String)
076     * @see #openOldStyle(String)
077     */
078    public static void browse(final String url) {
079        // if the user has taken the trouble to explicitly provide the path to 
080        // a web browser, we should probably use it. 
081        if (tryUserSpecifiedBrowser(url)) {
082            return;
083        }
084
085        // determine whether or not we can use the 1.6 classes
086        if (canAttemptNewStyle()) {
087            if (openNewStyle(url)) {
088                return;
089            }
090        }
091
092        // if not, use the hacky stuff.
093        openOldStyle(url);
094    }
095
096    /**
097     * Use the functionality within {@link java.awt.Desktop} to try opening
098     * the user's preferred web browser.
099     *
100     * @param url URL to visit.
101     * 
102     * @return Either {@code true} if things look ok, {@code false} if there 
103     * were problems.
104     */
105    private static boolean openNewStyle(final String url) {
106        boolean retVal = false;
107        if (Desktop.isDesktopSupported()) {
108            Desktop desktop = Desktop.getDesktop();
109            if (desktop.isSupported(Desktop.Action.BROWSE)) {
110                try {
111                    desktop.browse(new URI(url));
112                    // well... the assumption is that there was not a problem
113                    retVal = true;
114                } catch (URISyntaxException e) {
115                    LogUtil.logException("Bad syntax in URI: "+url, e);
116                } catch (IOException e) {
117                    LogUtil.logException("Problem accessing URI: "+url, e);
118                }
119            }
120        }
121        return retVal;
122    }
123
124    /**
125     * Uses {@link Runtime#exec(String)} to launch the user's preferred web
126     * browser. This method isn't really recommended unless you're stuck with
127     * Java 1.5.
128     * 
129     * <p>Note that the browsers need to be somewhere in the PATH, as this 
130     * method uses the {@code which} command (also needs to be in the PATH!).
131     * 
132     * @param url URL to visit.
133     */
134    private static void openOldStyle(final String url) {
135        try {
136            if (isWindows()) {
137                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
138            } else if (isMac()) {
139                Runtime.getRuntime().exec("/usr/bin/open "+url);
140            } else {
141                for (String browser : unixBrowsers) {
142                    if (Runtime.getRuntime().exec("which "+browser).waitFor() == 0) {
143                        Runtime.getRuntime().exec(browser+' '+url);
144                        return;
145                    }
146                }
147                throw new IOException("Could not find a web browser to launch (tried "+unixBrowsers+')');
148            }
149        } catch (Exception e) {
150            JOptionPane.showMessageDialog(null, "Problem running web browser:\n" + e.getLocalizedMessage());
151        }
152    }
153
154    /**
155     * Attempts to launch the browser pointed at by 
156     * the {@literal "idv.browser.path"} IDV property, if it has been set.
157     * 
158     * @param url URL to open.
159     * 
160     * @return Either {@code true} if the command-line was executed, {@code false} if
161     * either the command-line wasn't launched or {@literal "idv.browser.path"}
162     * was not set.
163     */
164    private static boolean tryUserSpecifiedBrowser(final String url) {
165        McIDASV mcv = McIDASV.getStaticMcv();
166        boolean retVal = false;
167        if (mcv != null) {
168            String browserPath = mcv.getProperty("idv.browser.path", (String)null);
169            if (browserPath != null && !browserPath.trim().isEmpty()) {
170                try {
171                    Runtime.getRuntime().exec(browserPath+' '+url);
172                    retVal = true;
173                } catch (Exception e) {
174                    LogUtil.logException("Executing browser: "+browserPath, e);
175                }
176            }
177        }
178        return retVal;
179    }
180
181    /**
182     * There's supposedly a bug lurking that can hang the JVM on Linux if
183     * {@code java.net.useSystemProxies} is enabled. Detect whether or not our
184     * configuration may trigger the bug.
185     * 
186     * @return Either {@code true} if everything is ok, {@code false} 
187     * otherwise.
188     */
189    private static boolean canAttemptNewStyle() {
190        // remove this check if JDK's bug 6496491 is fixed or if we can
191        // assume ORBit >= 2.14.2 and gnome-vfs >= 2.16.1
192        return isUnix() && Boolean.valueOf(System.getProperty("java.net.useSystemProxies", "true"));
193    }
194
195    /**
196     * Test for whether or not the current platform is Mac OS X.
197     *
198     * @return Are we shiny, happy OS X users?
199     */
200    private static boolean isMac() {
201        return System.getProperty("os.name", "").startsWith("Mac OS");
202    }
203
204    /**
205     * Test for whether or not the current platform is some form of
206     * {@literal "unix"} (but not OS X!).
207     *
208     * @return Do we perhaps think that beards and suspenders are the height 
209     * of fashion?
210     */
211    private static boolean isUnix() {
212        return !isMac() && !isWindows();
213    }
214
215    /**
216     * Test for whether or not the current platform is Windows.
217     *
218     * @return Are we running Windows??
219     */
220    private static boolean isWindows() {
221        return System.getProperty("os.name", "").startsWith("Windows");
222    }
223
224    public static void main(String[] args) {
225        browse("http://www.haskell.org/"); // sassy!
226    }
227}