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    }