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    }