/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.util;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Surrogate for the 1.6 java.awt.Desktop "browse(url)" and "open(file)" methods. Provides platform-dependent fallback methods
* where the Desktop class is unavailable.
*
* Copyright (c) 2009 David C A Croft.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author David Croft (http://www.davidc.net/)
* @version $Id: DesktopUtil.java 262 2009-04-22 13:47:21Z david $
*/
public class DesktopUtil {
private static final Logger log = Logger.getLogger(DesktopUtil.class.getName());
private static final String OS_MACOS = "Mac OS";
private static final String OS_WINDOWS = "Windows";
private static final String[] UNIX_BROWSE_CMDS = {
"www-browser", // debian update-alternatives target
"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape", "w3m", "lynx"};
private static final String[] UNIX_OPEN_CMDS = {
"run-mailcap", // many Unixes, run registered program from /etc/mailcap
// Fall back to assuming it's a text file.
"pager", // debian update-alternatives target
"less", "more"};
private DesktopUtil()
{
}
/**
* Launches the given URL and throws an exception if it couldn't be launched.
*
* @param url the URL to open
* @throws IOException if a browser couldn't be found or if the URL failed to launch
*/
public static void browse(final URL url) throws IOException
{
// Try Java 1.6 Desktop class if supported
if (browseDesktop(url)) return;
final String osName = System.getProperty("os.name");
log.finer("Launching " + url + " for OS " + osName);
if (osName.startsWith(OS_MACOS)) {
browseMac(url);
}
else if (osName.startsWith(OS_WINDOWS)) {
browseWindows(url);
}
else {
//assume Unix or Linux
browseUnix(url);
}
}
/**
* Launches the given URL and shows a dialog to the user if a browser couldn't be found or if the URL failed to launch.
*
* @param url the URL to open
* @param parentComponent The parent Component over which the error dialog should be shown
*/
public static void browseAndWarn(final URL url, final Component parentComponent)
{
try {
browse(url);
}
catch (final IOException e) {
log.log(Level.SEVERE, "Unable to browse to " + url, e);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog(parentComponent, "Couldn't open a web browser:\n" + e.getLocalizedMessage(),
"Unable to launch web browser", JOptionPane.ERROR_MESSAGE);
}
});
}
}
/**
* Launches the given URL and shows a dialog to the user if a browser couldn't be found or if the URL failed to launch.
* This version takes a String and handles the warning of malformed URLs where needed.
*
* @param url the URL to open
* @param parentComponent The parent Component over which the error dialog should be shown
*/
public static void browseAndWarn(final String url, final Component parentComponent)
{
try {
browse(new URL(url));
}
catch (final IOException e) {
log.log(Level.SEVERE, "Unable to browse to " + url, e);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog(parentComponent, "Couldn't open a web browser:\n" + e.getLocalizedMessage(),
"Unable to launch web browser", JOptionPane.ERROR_MESSAGE);
}
});
}
}
/**
* Opens the given File in the system default viewer application.
*
* @param file the File to open
* @throws IOException if an application couldn't be found or if the File failed to launch
*/
public static void open(final File file) throws IOException
{
// Try Java 1.6 Desktop class if supported
if (openDesktop(file)) return;
final String osName = System.getProperty("os.name");
log.finer("Opening " + file + " for OS " + osName);
if (osName.startsWith(OS_MACOS)) {
openMac(file);
}
else if (osName.startsWith(OS_WINDOWS)) {
openWindows(file);
}
else {
//assume Unix or Linux
openUnix(file);
}
}
/**
* Launches the given URL and shows a dialog to the user if a browser couldn't be found or if the URL failed to launch.
*
* @param file the File to open
* @param parentComponent The parent Component over which the error dialog should be shown
*/
public static void openAndWarn(final File file, final Component parentComponent)
{
try {
open(file);
}
catch (final IOException e) {
log.log(Level.SEVERE, "Unable to open " + file, e);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog(parentComponent, "Couldn't open " + file + ":\n" + e.getLocalizedMessage(),
"Unable to open file", JOptionPane.ERROR_MESSAGE);
}
});
}
}
/**
* Attempt to use java.awt.Desktop by reflection. Does not link directly to Desktop class so that this class can still
* be loaded in JRE < 1.6.
*
* @param url the URL to launch
* @return true if launch successful, false if we should fall back to other methods
* @throws IOException if Desktop was found, but the browse() call failed.
*/
private static boolean browseDesktop(final URL url) throws IOException
{
final Class<?> desktopClass = getDesktopClass();
if (desktopClass == null) return false;
final Object desktopInstance = getDesktopInstance(desktopClass);
if (desktopInstance == null) return false;
log.finer("Launching " + url + " using Desktop.browse()");
try {
final Method browseMethod = desktopClass.getDeclaredMethod("browse", URI.class);
browseMethod.invoke(desktopInstance, new URI(url.toExternalForm()));
return true;
}
catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
else {
log.log(Level.FINE, "Exception in Desktop operation", e);
return false;
}
}
catch (Exception e) {
log.log(Level.FINE, "Exception in Desktop operation", e);
return false;
}
}
/**
* Uses url.dll to browse to a URL under Windows.
*
* @param url the URL to launch
* @throws IOException if the launch failed
*/
private static void browseWindows(final URL url) throws IOException
{
log.finer("Windows invoking rundll32");
Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url.toString()});
}
/**
* Attempts to locate a browser from a predefined list under Unix.
*
* @param url the URL to launch
* @throws IOException if the launch failed
*/
private static void browseUnix(final URL url) throws IOException
{
for (final String cmd : UNIX_BROWSE_CMDS) {
log.finest("Unix looking for " + cmd);
if (unixCommandExists(cmd)) {
log.finer("Unix found " + cmd);
Runtime.getRuntime().exec(new String[]{cmd, url.toString()});
return;
}
}
throw new IOException("Could not find a suitable web browser");
}
/**
* Attempt to use com.apple.eio.FileManager by reflection.
*
* @param url the URL to launch
* @throws IOException if the launch failed
*/
private static void browseMac(final URL url) throws IOException
{
try {
final Class<?> fileMgr = getAppleFileManagerClass();
final Method openURL = fileMgr.getDeclaredMethod("openURL", String.class);
log.finer("Mac invoking");
openURL.invoke(null, url.toString());
}
catch (Exception e) {
log.log(Level.WARNING, "Couldn't launch Mac URL", e);
throw new IOException("Could not launch Mac URL: " + e.getLocalizedMessage());
}
}
static public boolean isSupported() {
final Class<?> desktopClass = getDesktopClass();
if (desktopClass == null) return false;
final Object desktopInstance = getDesktopInstance(desktopClass);
if (desktopInstance == null) return false;
return true;
}
/**
* Attempt to use java.awt.Desktop by reflection. Does not link directly to Desktop class so that this class can still
* be loaded in JRE < 1.6.
*
* @param file the File to open
* @return true if open successful, false if we should fall back to other methods
* @throws IOException if Desktop was found, but the open() call failed.
*/
private static boolean openDesktop(final File file) throws IOException
{
final Class<?> desktopClass = getDesktopClass();
if (desktopClass == null) return false;
final Object desktopInstance = getDesktopInstance(desktopClass);
if (desktopInstance == null) return false;
log.finer("Opening " + file + " using Desktop.open()");
try {
final Method browseMethod = desktopClass.getDeclaredMethod("open", File.class);
browseMethod.invoke(desktopInstance, file);
return true;
}
catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
else if (e.getCause() instanceof IllegalArgumentException) {
throw new FileNotFoundException(e.getCause().getLocalizedMessage());
}
else {
log.log(Level.FINE, "Exception in Desktop operation", e);
return false;
}
}
catch (Exception e) {
log.log(Level.FINE, "Exception in Desktop operation", e);
return false;
}
}
/**
* Uses shell32.dll to open a file under Windows.
*
* @param file the File to open
* @throws IOException if the open failed
*/
private static void openWindows(final File file) throws IOException
{
log.finer("Windows invoking rundll32");
Runtime.getRuntime().exec(new String[]{"rundll32", "shell32.dll,ShellExec_RunDLL", file.getAbsolutePath()});
}
/**
* Attempt to use com.apple.eio.FileManager by reflection.
*
* @param file the File to open
* @throws IOException if the open failed
*/
@SuppressWarnings("deprecation")
private static void openMac(final File file) throws IOException
{
// we use openURL() on the file's URL form since openURL supports file:// protocol
browseMac(file.getAbsoluteFile().toURL());
}
/**
* Attempts to locate a viewer from a predefined list under Unix.
*
* @param file the File to open
* @throws IOException if the open failed
*/
private static void openUnix(final File file) throws IOException
{
for (final String cmd : UNIX_OPEN_CMDS) {
log.finest("Unix looking for " + cmd);
if (unixCommandExists(cmd)) {
log.finer("Unix found " + cmd);
Runtime.getRuntime().exec(new String[]{cmd, file.getAbsolutePath()});
return;
}
}
throw new IOException("Could not find a suitable viewer");
}
/**
* Find the Desktop class if it exists in this JRE.
*
* @return the Desktop class object, or null if it could not be found
*/
private static Class<?> getDesktopClass()
{
// NB The following String is intentionally not inlined to prevent ProGuard trying to locate the unknown class.
final String desktopClassName = "java.awt.Desktop";
try {
return Class.forName(desktopClassName);
}
catch (ClassNotFoundException e) {
log.fine("Desktop class not found");
return null;
}
}
/**
* Gets a Desktop class instance if supported. We check isDesktopSupported() but for convenience we don't bother to
* check isSupported(method); instead the caller handles any UnsupportedOperationExceptions.
*
* @param desktopClass the Desktop Class object
* @return the Desktop instance, or null if it is not supported
*/
public static Object getDesktopInstance(final Class<?> desktopClass)
{
try {
final Method isDesktopSupportedMethod = desktopClass.getDeclaredMethod("isDesktopSupported");
log.finest("invoking isDesktopSupported");
final boolean isDesktopSupported = (Boolean) isDesktopSupportedMethod.invoke(null);
if (!isDesktopSupported) {
log.finer("isDesktopSupported: no");
return null;
}
final Method getDesktopMethod = desktopClass.getDeclaredMethod("getDesktop");
return getDesktopMethod.invoke(null);
}
catch (Exception e) {
log.log(Level.FINE, "Exception in Desktop operation", e);
return null;
}
}
/**
* Finds the com.apple.eio.FileManager class on a Mac.
*
* @return the FileManager instance
* @throws ClassNotFoundException if FileManager was not found
*/
private static Class<?> getAppleFileManagerClass() throws ClassNotFoundException
{
log.finest("Mac looking for com.apple.eio.FileManager");
// NB The following String is intentionally not inlined to prevent ProGuard trying to locate the unknown class.
final String appleClass = "com.apple.eio.FileManager";
return Class.forName(appleClass);
}
/**
* Checks whether a given executable exists, by means of the "which" command.
*
* @param cmd the executable to locate
* @return true if the executable was found
* @throws IOException if Runtime.exec() throws an IOException
*/
private static boolean unixCommandExists(final String cmd) throws IOException
{
final Process whichProcess = Runtime.getRuntime().exec(new String[]{"which", cmd});
boolean finished = false;
do {
try {
whichProcess.waitFor();
finished = true;
}
catch (InterruptedException e) {
log.log(Level.WARNING, "Interrupted waiting for which to complete", e);
}
} while (!finished);
return whichProcess.exitValue() == 0;
}
}