net.sourceforge.vaticanfetcher.util.Util.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.vaticanfetcher.util.Util.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2011 Tran Nam Quang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Tran Nam Quang - initial API and implementation
 *******************************************************************************/
/**
 * @author Tran Nam Quang
 */

package net.sourceforge.vaticanfetcher.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sourceforge.vaticanfetcher.util.annotations.MutableCopy;
import net.sourceforge.vaticanfetcher.util.annotations.NotNull;
import net.sourceforge.vaticanfetcher.util.annotations.Nullable;
import net.sourceforge.vaticanfetcher.util.annotations.ThreadSafe;
import net.sourceforge.vaticanfetcher.util.gui.Col;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;

import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.WString;

public final class Util {

    /* TODO pre-release: consider structuring the methods in this class by putting them into public static inner classes. */

    /** Whether the platform is Windows. */
    public static final boolean IS_WINDOWS;

    /** Whether the platform is Linux. */
    public static final boolean IS_LINUX;

    /** Whether the platform is Linux KDE. */
    public static final boolean IS_LINUX_KDE;

    /** Whether the operating system is Ubuntu and has the Unity desktop. */
    public static final boolean IS_UBUNTU_UNITY;

    /** Whether the platform is Mac OS X. */
    public static final boolean IS_MAC_OS_X;

    public static final boolean IS_64_BIT_JVM;

    /** The system's temporary directory. Does not contain backward slashes. */
    public static final File TEMP_DIR = new File(System.getProperty("java.io.tmpdir"));

    /** The current directory. Does not contain backward slashes. */
    public static final String USER_DIR_PATH = toForwardSlashes(System.getProperty("user.dir"));

    /** The current directory. */
    public static final File USER_DIR = new File(USER_DIR_PATH);

    /** The user's home directory. Does not contain backward slashes. */
    public static final String USER_HOME_PATH = System.getProperty("user.home");

    /** Line separator character ('\r\n' on Windows, '\n' on Linux). */
    public static final String LS = System.getProperty("line.separator");

    /** File separator character. On Windows, this is '\', and on Linux, it's '/'. */
    public static final String FS = System.getProperty("file.separator");

    /** Default minimum value for the width of a button. */
    public static final int BTW = 75;

    static {
        String osName = System.getProperty("os.name").toLowerCase();
        IS_WINDOWS = osName.contains("windows");
        IS_LINUX = osName.contains("linux");
        IS_UBUNTU_UNITY = isUbuntuUnity(IS_LINUX);
        IS_LINUX_KDE = IS_LINUX && System.getenv("KDE_FULL_SESSION") != null;
        IS_MAC_OS_X = osName.equals("mac os x");

        String arch = System.getProperty("sun.arch.data.model");
        if (arch == null)
            arch = System.getProperty("os.arch").toLowerCase();
        IS_64_BIT_JVM = arch.contains("64");
    }

    private Util() {
    }

    private static boolean isUbuntuUnity(boolean isLinux) {
        if (!isLinux)
            return false;
        try {
            String output = getProcessOutput("lsb_release -irs").trim();
            String[] lines = output.split("\n");
            if (lines.length != 2)
                return false;
            if (!lines[0].trim().toLowerCase().equals("ubuntu"))
                return false;

            // See: http://askubuntu.com/questions/70296/is-there-an-environment-variable-that-is-set-for-unity
            if (lines[1].trim().equals("11.04"))
                return "gnome".equals(System.getenv("DESKTOP_SESSION"))
                        && "gnome".equals(System.getenv("GDMSESSION"));
            return "Unity".equals(System.getenv("XDG_CURRENT_DESKTOP"));
        } catch (IOException e) {
            return false;
        }
    }

    @NotNull
    private static String getProcessOutput(@NotNull String command) throws IOException {
        Process p = Runtime.getRuntime().exec(command);
        BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
        StringBuilder sb = new StringBuilder();
        boolean firstLine = true;
        while (true) {
            String line = in.readLine();
            if (line == null)
                break;
            if (firstLine)
                firstLine = false;
            else
                sb.append(Util.LS);
            sb.append(line);
        }
        return sb.toString();
    }

    /**
     * Splits the given string into an integer array. Any characters other than digits and the 'minus' are treated as separators.
     * <p>
     * If the string cannot be parsed, the given array of default values is returned. If the string contains numbers that are greater than
     * {@code Integer.MAX_VALUE} or less than {@code Integer.MIN_VALUE}, those numbers will be clamped.
     */
    public static int[] toIntArray(String str, int[] defaultValues) {
        if (str.trim().equals(""))
            return new int[0];
        String[] rawValues = str.split("[^-\\d]+");
        int[] array = new int[rawValues.length];
        for (int i = 0; i < rawValues.length; i++) {
            try {
                array[i] = Integer.parseInt(rawValues[i]);
            } catch (NumberFormatException e) {
                if (rawValues[i].matches("\\d{10,}"))
                    array[i] = Integer.MAX_VALUE;
                else if (rawValues[i].matches("-\\d{10,}"))
                    array[i] = Integer.MIN_VALUE;
                else
                    return defaultValues;
            }
        }
        return array;
    }

    /**
     * Returns the given integer string as an {@code int} value. Leading and trailing whitespaces are ignored. 
     * If the string cannot be parsed, the given default value is returned. If the string is a number, but greater 
     * than {@code Integer.MAX_VALUE} or less than {@code Integer.MIN_VALUE}, a clamped value is returned.
     */
    public static int toInt(String value, int defaultValue) {
        value = value.trim();
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            if (value.matches("\\d{10,}"))
                return Integer.MAX_VALUE;
            else if (value.matches("-\\d{10,}"))
                return Integer.MIN_VALUE;
        }
        return defaultValue;
    }

    /**
     * Encodes the given collection of strings into a single string, using the specified separator. 
     * The resulting string is a concatenation of the elements of the collection, which are separated 
     * by the given separator and where occurrences of the separator and backslashes are escaped appropriately.
     *
     * @see Util#decodeStrings(String, char)
     */
    @NotNull
    public static String encodeStrings(@NotNull String sep, @NotNull Collection<String> parts) {
        Util.checkNotNull(sep, parts);
        if (parts.isEmpty())
            return "";
        StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        for (String part : parts) {
            if (!isFirst)
                sb.append(sep);
            sb.append(part.replace("\\", "\\\\").replace(sep, "\\" + sep));
            isFirst = false;
        }
        return sb.toString();
    }

    /**
     * Decodes the given string into a list of strings, using the specified separator. This method 
     * basically splits the given string at those occurrences of the separator that aren't escaped with a backslash.
     * <p>
     * Special case: If the given string is an empty or a blank string, an empty list is returned.
     *
     * @see Util#encodeStrings(String, char)
     */
    @MutableCopy
    @NotNull
    public static List<String> decodeStrings(char sep, @NotNull String str) {
        Util.checkNotNull(str);
        if (str.trim().isEmpty())
            return new ArrayList<String>(0);
        boolean precedingBackslash = false;
        List<String> parts = new ArrayList<String>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == sep && !precedingBackslash) {
                parts.add(sb.toString());
                sb.delete(0, sb.length());
            } else if (c != '\\' || precedingBackslash)
                sb.append(c);
            if (c == '\\')
                precedingBackslash = !precedingBackslash;
            else
                precedingBackslash = false;
        }
        parts.add(sb.toString());
        return parts;
    }

    /** Shortens the given string if its length exceeds a fixed limit. */
    @NotNull
    public static String truncate(@NotNull String str) {
        if (str.length() > 32)
            return str.substring(0, 32) + "..."; //$NON-NLS-1$
        return str;
    }

    /**
     * Removes any leading whitespace from the input string and returns the resulting string.
     *
     * @throws IllegalArgumentException
     *             if the input string is null.
     * @see String#trim()
     */
    @NotNull
    public static String trimLeft(@NotNull String input) {
        if (input == null)
            throw new IllegalArgumentException();
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            if (!Character.isWhitespace(c))
                return input.substring(i);
        }
        return "";
    }

    /**
     * Removes any trailing whitespace from the input string and returns the resulting string.
     *
     * @throws IllegalArgumentException
     *             if the input string is null.
     * @see String#trim()
     */
    @NotNull
    public static String trimRight(@NotNull String input) {
        final int len = input.length();
        for (int i = len - 1; i >= 0; i--) {
            char c = input.charAt(i);
            if (!Character.isWhitespace(c))
                return input.substring(0, i + 1);
        }
        return "";
    }

    public static <T> boolean equals(@NotNull Collection<T> col, @NotNull T[] a) {
        Util.checkNotNull(col, a);
        if (col.size() != a.length)
            return false;
        int i = 0;
        for (T e1 : col) {
            if (!e1.equals(a[i]))
                return false;
            i++;
        }
        return true;
    }

    public static String ensureLinuxLineSep(@NotNull String input) {
        return input.replace("\r\n", "\n");
    }

    public static String ensureWindowsLineSep(@NotNull String input) {
        // Two replace passes are needed to avoid converting "\r\n" to "\r\r\n".
        return input.replace("\r\n", "\n").replace("\n", "\r\n");
    }

    /**
     * Centers the given shell relative to its parent shell and sets the shell's width and height. 
     * If there is no parent shell, the given shell is centered relative to the screen.
     */
    public static void setCenteredBounds(@NotNull Shell shell, int width, int height) {
        shell.setSize(width, height);
        Composite parent = shell.getParent();
        Rectangle parentBounds = null;
        if (parent == null || !parent.isVisible())
            parentBounds = shell.getMonitor().getBounds();
        else
            parentBounds = parent.getBounds();
        int shellPosX = (parentBounds.width - width) / 2;
        int shellPosY = (parentBounds.height - height) / 2;
        if (parent != null) {
            shellPosX += parentBounds.x;
            shellPosY += parentBounds.y;
        }
        shell.setLocation(shellPosX, shellPosY);
    }

    /**
     * Packs the given shell and then centers it relative to its parent shell.
     * If there is no parent shell, the given shell is centered relative to the screen.
     */
    public static void setCenteredBounds(@NotNull Shell shell) {
        shell.pack();
        Point shellSize = shell.getSize();
        Composite parent = shell.getParent();
        Rectangle parentBounds = null;
        if (parent == null || !parent.isVisible())
            parentBounds = shell.getMonitor().getBounds();
        else
            parentBounds = parent.getBounds();
        int shellPosX = (parentBounds.width - shellSize.x) / 2;
        int shellPosY = (parentBounds.height - shellSize.y) / 2;
        if (parent != null) {
            shellPosX += parentBounds.x;
            shellPosY += parentBounds.y;
        }
        shell.setLocation(shellPosX, shellPosY);
    }

    /** Packs the given shell and then centers it relative to the given control. */
    public static void setCenteredBounds(@NotNull Shell shell, @NotNull Control control) {
        shell.pack();
        Point shellSize = shell.getSize();
        Composite parent = control.getParent();
        Rectangle bounds = control.getBounds();
        bounds = control.getDisplay().map(parent, null, bounds);
        int x = bounds.x + (bounds.width - shellSize.x) / 2;
        int y = bounds.y + (bounds.height - shellSize.y) / 2;
        shell.setLocation(x, y);
    }

    /**
     * Centers the given shell relative to its parent shell and sets the shell's minimum width and height. 
     * The actual width and height may be greater to provide enough space for the shell's children. 
     * If the given shell has no parent shell, it is centered relative to the screen.
     */
    public static void setCenteredMinBounds(@NotNull Shell shell, int minWidth, int minHeight) {
        Point prefSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        int width = Math.max(prefSize.x, minWidth);
        int height = Math.max(prefSize.y, minHeight);
        setCenteredBounds(shell, width, height);
    }

    @NotNull
    public static Button[] maybeSwapButtons(@NotNull Button b1, @NotNull Button b2) {
        boolean leftAlign = b1.getDisplay().getDismissalAlignment() == SWT.LEFT;
        return new Button[] { leftAlign ? b1 : b2, leftAlign ? b2 : b1 };
    }

    /**
     * Returns whether the first bit mask contains the second bit mask.
     * <p>
     * Example: {@code contains(SWT.CTRL | SWT.ALT, SWT.CTRL) == true}
     */
    public static boolean contains(int bit1, int bit2) {
        return (bit1 & bit2) == bit2;
    }

    /** Creates and returns a {@link org.eclipse.swt.layout.FillLayout FillLayout} with the given margin. */
    public static FillLayout createFillLayout(int margin) {
        FillLayout layout = new FillLayout();
        layout.marginWidth = layout.marginHeight = margin;
        return layout;
    }

    /** Creates and returns a {@link org.eclipse.swt.layout.GridLayout GridLayout} with the given arguments. */
    public static GridLayout createGridLayout(int numColumns, boolean makeColumsEqualWidth, int margin,
            int spacing) {
        GridLayout layout = new GridLayout(numColumns, makeColumsEqualWidth);
        layout.marginWidth = layout.marginHeight = margin;
        layout.horizontalSpacing = layout.verticalSpacing = spacing;
        return layout;
    }

    /** Creates and returns a {@link org.eclipse.swt.layout.FormLayout FormLayout} with the given margin. */
    public static FormLayout createFormLayout(int margin) {
        FormLayout layout = new FormLayout();
        layout.marginWidth = layout.marginHeight = margin;
        return layout;
    }

    @NotNull
    public static Text createLabeledGridText(@NotNull Composite parent, @NotNull String labelText) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(labelText);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        Text text = new Text(parent, SWT.BORDER | SWT.SINGLE);
        text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        return text;
    }

    @NotNull
    public static StyledText createLabeledGridStyledText(@NotNull Composite parent, @NotNull String labelText) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(labelText);
        label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        StyledText text = new StyledText(parent, SWT.BORDER | SWT.SINGLE);
        text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        return text;
    }

    @NotNull
    public static Button createCheckButton(@NotNull Composite parent, @NotNull String label) {
        Button bt = new Button(parent, SWT.CHECK);
        bt.setText(label);
        return bt;
    }

    @NotNull
    public static Button createPushButton(@NotNull Composite parent, @NotNull String label,
            @NotNull SelectionListener listener) {
        Button bt = new Button(parent, SWT.PUSH);
        bt.setText(label);
        bt.addSelectionListener(listener);
        return bt;
    }

    @NotNull
    public static Button createPushButton(@NotNull Composite parent, @Nullable Image image,
            @Nullable String toolTip, @NotNull SelectionListener listener) {
        Button bt = new Button(parent, SWT.PUSH);
        bt.setImage(image);
        if (toolTip != null)
            bt.setToolTipText(toolTip);
        bt.addSelectionListener(listener);
        return bt;
    }

    /**
     * Returns a suitable text foreground color for the given background color.
     * The returned color is either black or white, depending on the perceived luminance of the given background color.
     */
    @NotNull
    public static Color getTextForeground(@NotNull Color background) {
        int r = background.getRed();
        int g = background.getGreen();
        int b = background.getBlue();
        double a = 1 - (0.299 * r + 0.587 * g + 0.114 * b) / 255;
        return a < 0.5 ? Col.BLACK.get() : Col.WHITE.get();
    }

    /**
     * Splits the given file path at any path separators, i.e. forward or backward slashes. Example:
     *
     * <pre>
     * /path/to/file/ -> '', 'path', 'to', 'file'
     * </pre>
     *
     * Note that a leading path separator will produce an empty string at the
     * beginning of the returned list, while a (single) trailing path separator won't.
     */
    @MutableCopy
    @NotNull
    public static List<String> splitPath(@NotNull String path) {
        List<String> parts = new ArrayList<String>();
        int lastStart = 0;
        for (int i = 0; i < path.length(); i++) {
            char c = path.charAt(i);
            if (c == '/' || c == '\\') {
                parts.add(path.substring(lastStart, i));
                lastStart = i + 1;
            }
        }
        if (lastStart < path.length())
            parts.add(path.substring(lastStart));
        return parts;
    }

    /**
     * A {@link com.google.common.base.CharMatcher CharMatcher} that matches
     * forward and backward slashes. See the {@code CharMatcher} Javadocs for more.
     */
    public static final CharMatcher fileSepMatcher = CharMatcher.anyOf("/\\").precomputed();

    /**
     * Creates a file path by joining the given parts. All leading and trailing forward and backward 
     * slashes are stripped from the parts, except for the first part, where only the trailing slashes 
     * are stripped. All backward slashes are replaced by forward slashes.
     * <p>
     * Special case: If only two path parts are given and one of them is empty,
     * the other path part is returned, without any additional file separators.
     */
    @NotNull
    public static String joinPath(@NotNull String first, @NotNull String second, @NotNull String... more) {
        if (more.length == 0) {
            if (first.isEmpty())
                return second;
            if (second.isEmpty())
                return first;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(fileSepMatcher.trimTrailingFrom(first));
        sb.append('/');
        sb.append(fileSepMatcher.trimFrom(second));
        for (int i = 0; i < more.length; i++) {
            sb.append('/');
            sb.append(fileSepMatcher.trimFrom(more[i]));
        }
        return toForwardSlashes(sb.toString());
    }

    /** Same as {@link #joinPath(String...)}, but reads the parts from an <tt>Iterable</tt>. */
    @NotNull
    public static String joinPath(@NotNull Iterable<?> parts) {
        Iterator<?> it = parts.iterator();
        if (!it.hasNext())
            return "";
        StringBuilder sb = new StringBuilder();
        sb.append(fileSepMatcher.trimTrailingFrom(it.next().toString()));
        while (it.hasNext()) {
            sb.append('/');
            sb.append(fileSepMatcher.trimFrom(it.next().toString()));
        }
        return toForwardSlashes(sb.toString());
    }

    @NotNull
    public static String join(@NotNull String separator, @NotNull Object... parts) {
        Util.checkNotNull(separator);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            if (i == 0) {
                sb.append(parts[i]);
            } else {
                sb.append(separator);
                sb.append(parts[i]);
            }
        }
        return sb.toString();
    }

    @NotNull
    public static String join(@NotNull String separator, @NotNull Iterable<?> parts) {
        Util.checkNotNull(separator);
        Iterator<?> it = parts.iterator();
        if (!it.hasNext())
            return "";
        StringBuilder sb = new StringBuilder();
        sb.append(it.next().toString());
        while (it.hasNext()) {
            sb.append(separator);
            sb.append(it.next().toString());
        }
        return sb.toString();
    }

    /**
     * For the given file, returns an absolute path in which all backward slashes have been replaced by forward slashes.
     * <p>
     * Exception: If the file's path is a UNC path, the UNC path is returned as is.
     */
    @NotNull
    @SuppressAjWarnings
    public static String getAbsPath(@NotNull File file) {
        String absPath = file.getAbsolutePath();
        if (absPath.startsWith("\\\\")) // UNC path?
            return absPath;
        /*
         * We'll replace "//" with "/" here due to a bug in the* File.getAbsolutePath method: On Windows, 
         * if the given file has the path "SOME_PATH" and the current working directory is the root of a device, 
         * e.g. "C:\", then getAbsolutePath will return "C:\\SOME_PATH" rather than the more sensible value "C:\SOME_PATH".
         */
        return absPath.replace('\\', '/').replace("//", "/");
    }

    /** Returns whether given file's path is a UNC path. */
    public static boolean isUncPath(@NotNull File file) {
        return file.getPath().startsWith("\\\\");
    }

    /** For the given path string, returns an absolute path in which all backward slashes have been replaced by forward slashes. */
    @NotNull
    public static String getAbsPath(@NotNull String path) {
        return getAbsPath(new File(path));
    }

    @NotNull
    public static File getAbsFile(@NotNull File file) {
        return new File(getAbsPath(file));
    }

    /** Equivalent to {@link java.io.File#getAbsolutePath()}. */
    @NotNull
    @SuppressAjWarnings
    public static String getSystemAbsPath(@NotNull File file) {
        return file.getAbsolutePath();
    }

    /** Equivalent to {@link java.io.File#getAbsolutePath() new java.io.File(path).getAbsolutePath()}. */
    @NotNull
    @SuppressAjWarnings
    public static String getSystemAbsPath(@NotNull String path) {
        return new File(path).getAbsolutePath();
    }

    @NotNull
    @SuppressAjWarnings
    public static File getCanonicalFile(@NotNull String path) {
        return getCanonicalFile(new File(path));
    }

    @NotNull
    @SuppressAjWarnings
    public static File getCanonicalFile(@NotNull File file) {
        return new File(getCanonicalPath(file));
    }

    @NotNull
    @SuppressAjWarnings
    public static String getCanonicalPath(@NotNull File file) {
        if (IS_WINDOWS && isWindowsDevice(file.getPath())) {
            String driveLetter = getDriveLetter(file.getPath());
            assert driveLetter != null;
            return driveLetter + ":\\"; // the trailing slash is important here
        }

        // Calling getCanonicalPath leads to performance problems for files
        // located on a network, so it has been disabled. See:
        // https://sourceforge.net/p/docfetcher/discussion/702424/thread/4ed68957/

        //      try {
        //         return file.getCanonicalPath();
        //      }
        //      catch (IOException e) {
        //         return file.getAbsolutePath();
        //      }
        return file.getAbsolutePath();
    }

    public static boolean isCanonicallyEqual(@Nullable File file1, @Nullable File file2) {
        if (file1 == null || file2 == null)
            return false;
        return getCanonicalFile(file1).equals(getCanonicalFile(file2));
    }

    /**
     * Returns all files and directories directly underneath the given directory. This works like {@link File#listFiles()}, 
     * except that when access to the directory is denied, an empty array is returned, not a null pointer.
     */
    @NotNull
    @SuppressAjWarnings
    public static File[] listFiles(@NotNull File dir) {
        File[] files = dir.listFiles();
        return files == null ? new File[0] : files;
    }

    /**
     * Returns all files and directories directly underneath the given directory that are not filtered 
     * by the given {@code filter}. This works like {@link File#listFiles(FilenameFilter)}, except that 
     * when access to the directory is denied, an empty array is returned, not a null pointer.
     */
    @NotNull
    @SuppressAjWarnings
    public static File[] listFiles(@NotNull File dir, @Nullable FilenameFilter filter) {
        File[] files = dir.listFiles(filter);
        return files == null ? new File[0] : files;
    }

    /**
     * Returns all files and directories directly underneath the given directory that are not 
     * filtered by the given {@code filter}. This works like {@link File#listFiles(FileFilter)}, 
     * except that when access to the directory is denied, an empty array is returned, not a null pointer.
     */
    @NotNull
    @SuppressAjWarnings
    public static File[] listFiles(@NotNull File dir, @Nullable FileFilter filter) {
        File[] files = dir.listFiles(filter);
        return files == null ? new File[0] : files;
    }

    /**
     * Returns whether the given file is a symlink. Returns false if the file doesn't exists or if an IOException occured. 
     * The symlink detection is based on the comparison of the absolute and canonical path of a link: 
     * If those two differ, the given file can be assumed to be a symlink.
     * <p>
     * Note: If the given file is an instance of TFile and represents an archive entry, this method always returns false.
     */
    @SuppressAjWarnings
    public static boolean isSymLink(@NotNull File file) {
        try {
            /*
             * Earlier versions simply compared the absolute and canonical path of the given file. 
             * This did not work for files with 8.3 filenames, which were incorrectly identified as symlinks.
             */
            File canon;
            if (file.getParent() == null) {
                canon = file;
            } else {
                File canonDir = file.getParentFile().getCanonicalFile();
                canon = new File(canonDir, file.getName());
            }
            return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
        } catch (IOException e) {
            return false;
        }
    }

    private interface Kernel32 extends Library {
        public int GetFileAttributesW(WString fileName);
    }

    private static Kernel32 lib = null;

    private static int getWin32FileAttributes(File file) throws IOException {
        if (lib == null) {
            synchronized (Kernel32.class) {
                lib = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
            }
        }
        return lib.GetFileAttributesW(new WString(getCanonicalPath(file)));
    }

    /**
     * Returns whether the given file is a Windows junction or symlink. Returns false if the platform is not Windows, 
     * if the file doesn't exists or if an IOException occured.
     * <p>
     * Note: If the given file is an instance of TFile and represents an archive entry, this method always returns false.
     */
    public static boolean isJunctionOrSymlink(@NotNull File file) {
        if (!IS_WINDOWS)
            return false;
        try {
            // Wrap in java.io.File to shield against TFile instances
            return new File(file.getPath()).exists() && (0x400 & getWin32FileAttributes(file)) != 0;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Returns the parent of the given file. Unlike the standard method {@link File#getParentFile()}, 
     * this method will not return null if the given file was constructed with a relative path.
     */
    @NotNull
    @SuppressAjWarnings
    public static File getParentFile(@NotNull File file) {
        Util.checkNotNull(file);
        File parent = file.getParentFile();
        if (parent == null)
            parent = file.getAbsoluteFile().getParentFile();
        return parent;
    }

    @NotNull
    public static String toForwardSlashes(@NotNull String path) {
        if (path.startsWith("\\\\")) // UNC path?
            return path;
        return path.replace('\\', '/');
    }

    /** @see #getParentFile(File) */
    @NotNull
    @SuppressAjWarnings
    public static File getParentFile(@NotNull String path) {
        Util.checkNotNull(path);
        File file = new File(path);
        File parent = file.getParentFile();
        if (parent == null)
            parent = file.getAbsoluteFile().getParentFile();
        return parent;
    }

    /** Returns true if <tt>objects</tt> contains an object that is equal to <tt>object</tt>. Returns false if <tt>objects</tt> is null. */
    public static boolean containsEquality(@Nullable Object[] objects, @Nullable Object object) {
        if (objects == null)
            return false;
        for (Object candidate : objects)
            if (candidate.equals(object))
                return true;
        return false;
    }

    /**
     * Equivalent to {@link #splitFilename(String)
     * splitFilename(file.getName())}.
     *
     * @see Util#splitFilename(String)
     */
    public static String[] splitFilename(@NotNull File file) {
        return Util.splitFilename(file.getName());
    }

    /**
     * Splits the given filename into a base name and the file extension, omitting the '.' character, e.g. "data.xml" -> ["data", "xml"]. 
     * The file extension is an empty string if the file has no extension (i.e. it doesn't contain the '.' character). 
     * The returned file extension is always lowercase, even if it wasn't lowercased in the given filename. 
     * It is also guaranteed that the returned array is always of length 2.
     * <p>
     * Exception: If the file ends with ".xxx.gz", then the returned file extension is "xxx.gz", not "gz". Examples:
     * <ul>
     * <li>"archive.tar.gz" -> ["archive", "tar.gz"]
     * <li>"abiword.abw.gz" -> ["abiword", "abw.gz"]
     * </ul>
     * <p>
     * Note: This method also accepts filepaths.
     *
     * @throws NullPointerException
     *             if the given filename is null.
     */
    @NotNull
    public static String[] splitFilename(@NotNull String filename) {
        int index = filename.lastIndexOf('.');
        if (index == -1)
            return new String[] { filename, "" };
        String ext = filename.substring(index + 1).toLowerCase();
        if (ext.equals("gz")) {
            int index2 = filename.lastIndexOf('.', index - 1);
            if (index2 != -1) {
                return new String[] { filename.substring(0, index2), filename.substring(index2 + 1).toLowerCase() };
            }
        }
        return new String[] { filename.substring(0, index), ext };
    }

    @NotNull
    public static String getExtension(@NotNull String filename) {
        return splitFilename(filename)[1];
    }

    @NotNull
    public static String getExtension(@NotNull File file) {
        return splitFilename(file.getName())[1];
    }

    /**
     * For the given filename and a list of file extensions, this method returns true if any of the file extensions match 
     * the filename. A match occurs when the given filename, after being lower-cased, ends with '.' and the matching lower-cased file extension.
     * <p>
     * Example: The filename <code>'some_file.TXT'</code> matches the file extension <code>'txt'</code>.
     * <p>
     * Note: This method also accepts filepaths.
     */
    public static boolean hasExtension(@NotNull String filename, @NotNull String... extensions) {
        filename = filename.toLowerCase();
        for (String ext : extensions)
            if (filename.endsWith("." + ext.toLowerCase()))
                return true;
        return false;
    }

    /** @see #hasExtension(String, String...) */
    public static boolean hasExtension(@NotNull String filename, @NotNull Collection<String> extensions) {
        filename = filename.toLowerCase();
        for (String ext : extensions)
            if (filename.endsWith("." + ext.toLowerCase()))
                return true;
        return false;
    }

    /**
     * Deletes all the files within a directory. Does not delete the directory itself.
     * <p>
     * If the file argument is a symbolic link or there is a symbolic link in the path leading  to the 
     * directory, this method will do nothing. Symbolic links within the directory are not followed.
     */
    public static void deleteContents(@NotNull File directory) throws IOException {
        checkThat(directory.isDirectory());
        if (isSymLink(directory))
            return;
        for (File file : listFiles(directory))
            deleteRecursively(file);
    }

    /**
     * Deletes a file or directory and all contents recursively.
     * <p>
     * If the file argument is a symbolic link the link will be deleted but not the target of the link. 
     * If the argument is a directory, symbolic links within the directory will not be followed.
     */
    public static void deleteRecursively(@NotNull File file) throws IOException {
        if (file.isDirectory())
            deleteContents(file);
        if (!file.delete())
            throw new IOException("Failed to delete " + file);
    }

    /**
     * Returns the name of the given file. In contrast to the default {@link File#getName()} method, 
     * this method will return a drive letter instead of an empty string if the given file is a Windows 
     * root such as "C:". The {@code letterSuffix} argument is a string that will be appended
     * to the drive letter, if one is returned.
     */
    @NotNull
    public static String getNameOrLetter(@NotNull File file, @NotNull String letterSuffix) {
        Util.checkNotNull(file, letterSuffix);
        String filename = file.getName();

        /* Special case: If the file was created as 'new File("")', then its filename will be an empty string. */
        if (file.getAbsoluteFile().equals(USER_DIR))
            return USER_DIR.getName();

        /* Note: Do not use absolute files here, because this would turn "C:" into the working directory! (Strange but true.) */
        if (IS_WINDOWS && filename.length() == 0 && getParentFile(file) == null) {
            String driveLetter = getDriveLetter(file.getPath());
            if (driveLetter != null)
                return driveLetter + letterSuffix;
        }
        return filename;
    }

    private static Pattern drivePattern = Pattern.compile("([a-zA-Z]):.*");
    private static Pattern driveOnlyPattern = Pattern.compile("(?:[a-zA-Z]):(?:\\\\|/)*");

    /**
     * Returns the drive letter at the beginning of the given Windows path, or null if the path doesn't start with a drive letter.
     * <p>
     * Example: For "C:\Windows" this method returns "C".
     */
    @Nullable
    public static String getDriveLetter(@NotNull String path) {
        Util.checkNotNull(path);
        Matcher m = Util.drivePattern.matcher(path);
        if (m.matches())
            return m.group(1).toUpperCase();
        return null;
    }

    public static boolean isWindowsDevice(@NotNull String path) {
        return driveOnlyPattern.matcher(path).matches();
    }

    public static void assertSwtThread() {
        if (Display.getCurrent() == null)
            throw new IllegalStateException();
    }

    /** Throws an <code>IllegalArgumentException</code> if the given condition is false. */
    public static void checkThat(boolean condition) {
        if (!condition)
            throw new IllegalArgumentException();
    }

    /** Throws an <code>IllegalArgumentException</code> with the given error message if the given condition is false. */
    public static void checkThat(boolean condition, @NotNull String message) {
        if (!condition)
            throw new IllegalArgumentException(message);
    }

    /** Throws an <code>IllegalArgumentException</code> if the provided argument is null. If not, the argument is returned. */
    public static <T> T checkNotNull(T a) {
        /*
         * Generally, it does not make sense to check that a method argument of type Boolean is not null 
         * - if the Boolean is not allowed to be null, one could use a primitive boolean instead. If someone 
         * does call this method with a Boolean, he/she might have done so by accident by
         * confusing checkNotNull with checkThat. To prevent this, we'll throw an exception.
         */
        if (a instanceof Boolean)
            throw new UnsupportedOperationException();
        if (a == null)
            throw new IllegalArgumentException();
        return a;
    }

    /** Throws an <code>IllegalArgumentException</code> if any of the provided arguments is null. */
    public static void checkNotNull(Object a, Object b) {
        if (a == null || b == null)
            throw new IllegalArgumentException();
    }

    /** Throws an <code>IllegalArgumentException</code> if any of the provided arguments is null. */
    public static void checkNotNull(Object a, Object b, Object c) {
        if (a == null || b == null || c == null)
            throw new IllegalArgumentException();
    }

    /** Throws an <code>IllegalArgumentException</code> if any of the provided arguments is null.*/
    public static void checkNotNull(Object a, Object b, Object c, Object d) {
        if (a == null || b == null || c == null || d == null)
            throw new IllegalArgumentException();
    }

    /** Throws an <code>IllegalArgumentException</code> if any of the provided arguments is null. */
    public static void checkNotNull(Object a, Object b, Object c, Object d, Object e) {
        if (a == null || b == null || c == null || d == null || e == null)
            throw new IllegalArgumentException();
    }

    /** Returns the given string if it is not null, otherwise returns an empty string. */
    @NotNull
    public static String notNull(@Nullable String string) {
        return string == null ? "" : string;
    }

    private static long lastTimeStamp = -1;

    /**
     * Returns a unique identifier based on {@link System#currentTimeMillis()}.
     * The returned ID is guaranteed to differ from all previous IDs obtained by this method.
     */
    @ThreadSafe
    public static synchronized long getTimestamp() {
        /* Try to create a timestamp and don't return until the last timestamp and the current one are unequal. */
        long newTimeStamp = System.currentTimeMillis();
        while (newTimeStamp == lastTimeStamp)
            newTimeStamp = System.currentTimeMillis();
        lastTimeStamp = newTimeStamp;
        return newTimeStamp;
    }

    /** Returns true if the directory given by <tt>dir</tt> is a direct or indirect parent directory of the file or directory given by <tt>fileOrDir</tt>. */
    public static boolean contains(@NotNull File dir, @NotNull File fileOrDir) {
        return contains(getAbsPath(dir), getAbsPath(fileOrDir));
    }

    /**
     * Returns true if the directory given by the absolute path <tt>dirPath</tt> is a direct or indirect 
     * parent directory of the file or directory given by the absolute path <tt>fileOrDirPath</tt>.
     */
    public static boolean contains(@NotNull String dirPath, @NotNull String fileOrDirPath) {
        dirPath = dirPath.replace('\\', '/');
        fileOrDirPath = fileOrDirPath.replace('\\', '/');
        if (dirPath.length() >= fileOrDirPath.length())
            return false;
        char c = fileOrDirPath.charAt(dirPath.length());
        if (c != '/')
            return false;
        if (!fileOrDirPath.startsWith(dirPath))
            return false;
        return true;
    }

    /**
     * Returns the last element of the given list. Returns null if the given list is empty or null.
     */
    @Nullable
    public static <T> T getLast(@Nullable List<T> list) {
        if (list == null)
            return null;
        int size = list.size();
        if (size == 0)
            return null;
        return list.get(size - 1);
    }

    @MutableCopy
    @NotNull
    public static <T> List<T> createList(int extraCapacity, @NotNull T... elements) {
        Util.checkNotNull(elements);
        List<T> newList = new ArrayList<T>(elements.length + extraCapacity);
        for (T element : elements)
            newList.add(element);
        return newList;
    }

    /**
     * Creates a new list from the given collection and elements. The given collection is added first to the returned list.
     *
     * @see #createListReverse(Collection, Object...)
     */
    @MutableCopy
    @NotNull
    public static <T> List<T> createList(@NotNull Collection<? extends T> col, @NotNull T... elements) {
        Util.checkNotNull(col, elements);
        List<T> newList = new ArrayList<T>(col.size() + elements.length);
        newList.addAll(col);
        for (T element : elements)
            newList.add(element);
        return newList;
    }

    /**
     * Creates a new list from the given collection and elements. The given elements are added first to the returned list.
     *
     * @see #createList(Collection, Object...)
     */
    @MutableCopy
    @NotNull
    public static <T> List<T> createListReversed(@NotNull Collection<? extends T> col, @NotNull T... elements) {
        Util.checkNotNull(col, elements);
        List<T> newList = new ArrayList<T>(col.size() + elements.length);
        for (T element : elements)
            newList.add(element);
        newList.addAll(col);
        return newList;
    }

    @NotNull
    public static <T> List<T> createEmptyList(@NotNull Collection<?>... cols) {
        int size = 0;
        for (int i = 0; i < cols.length; i++)
            size += cols[i].size();
        return new ArrayList<T>(size);
    }

    /**
     * Runs the given {@code Runnable} in a way that avoids throwing errors of the type {@link SWT#ERROR_THREAD_INVALID_ACCESS}. 
     * This is useful for running GUI-accessing code from non-GUI threads.
     * <p>
     * The given Runnable is <b>not</b> run if the given given widget is null or disposed. This helps avoid the
     * common pitfall of trying to access widgets from a non-GUI thread when these widgets have already been disposed.
     * <p>
     * The returned Boolean indicates whether the Runnable was run (true) or not (false).
     */
    public static boolean runSwtSafe(@Nullable final Widget widget, @NotNull final Runnable runnable) {
        if (Display.getCurrent() != null) {
            boolean wasRun = widget != null && !widget.isDisposed();
            if (wasRun)
                runnable.run();
            return wasRun;
        } else {
            return runSyncExec(widget, runnable);
        }
    }

    /** @see #runSwtSafe(Widget, Runnable) */
    public static boolean runSwtSafe(@Nullable final Display display, @NotNull final Runnable runnable) {
        if (Display.getCurrent() != null) {
            boolean wasRun = display != null && !display.isDisposed();
            if (wasRun)
                runnable.run();
            return wasRun;
        } else {
            return runSyncExec(display, runnable);
        }
    }

    /**
     * Runs the given {@code Runnable} via {@link Display#syncExec(Runnable)}. This is useful for running GUI-accessing code from non-GUI threads.
     * <p>
     * The given Runnable is <b>not</b> run if the given given widget is null or disposed. This helps avoid the 
     * common pitfall of trying to access widgets from a non-GUI thread when these widgets have already been disposed.
     * <p>
     * The returned Boolean indicates whether the Runnable was run (true) or not (false).
     */
    public static boolean runSyncExec(@Nullable final Widget widget, @NotNull final Runnable runnable) {
        if (widget == null || widget.isDisposed())
            return false;
        final boolean[] wasRun = { false };
        widget.getDisplay().syncExec(new Runnable() {
            public void run() {
                wasRun[0] = !widget.isDisposed();
                if (wasRun[0])
                    runnable.run();
            }
        });
        return wasRun[0];
    }

    /** @see #runSyncExec(Widget, Runnable) */
    public static boolean runSyncExec(@Nullable final Display display, @NotNull final Runnable runnable) {
        if (display == null || display.isDisposed())
            return false;
        final boolean[] wasRun = { false };
        display.syncExec(new Runnable() {
            public void run() {
                wasRun[0] = !display.isDisposed();
                if (wasRun[0])
                    runnable.run();
            }
        });
        return wasRun[0];
    }

    /**
     * Runs the given {@code Runnable} via {@link Display#asyncExec(Runnable)}.
     * This is useful for running GUI-accessing code from non-GUI threads.
     * <p>
     * The given Runnable is <b>not</b> run if the given widget is null or
     * disposed. This helps avoid the common pitfall of trying to access widgets
     * from a non-GUI thread when these widgets have already been disposed.
     */
    public static void runAsyncExec(@Nullable final Widget widget, @NotNull final Runnable runnable) {
        /*
         * Note: Unlike the syncExec variant, here it's not possible to return a boolean flag 
         * that indicates whether the Runnable was run, since asyncExec may not execute the Runnable immediately.
         */
        if (widget == null || widget.isDisposed())
            return;
        widget.getDisplay().asyncExec(new Runnable() {
            public void run() {
                if (!widget.isDisposed())
                    runnable.run();
            }
        });
    }

    /**
     * Runs the given {@code Runnable} via {@link Display#asyncExec(Runnable)}.
     * This is useful for running GUI-accessing code from non-GUI threads.
     * <p>
     * The given Runnable is <b>not</b> run if the given display is null or
     * disposed. This helps avoid the common pitfall of trying to access widgets
     * from a non-GUI thread when these widgets have already been disposed.
     */
    public static void runAsyncExec(@Nullable final Display display, @NotNull final Runnable runnable) {
        /*
         * Note: Unlike the syncExec variant, here it's not possible to return a
         * boolean flag that indicates whether the Runnable was run, since
         * asyncExec may not execute the Runnable immediately.
         */
        if (display == null || display.isDisposed())
            return;
        display.asyncExec(new Runnable() {
            public void run() {
                if (!display.isDisposed())
                    runnable.run();
            }
        });
    }

    /**
     * Launches the given filename or filepath, and returns whether the file was successfully launched. 
     * This method first tries to launch the file via the SWT method {@link Program#launch(String)}. 
     * If this fails and the application is running on Linux, this method tries to call xdg-open. This
     * is what usually happens on KDE-based Linux variants, which are not supported by SWT.
     */
    @SuppressAjWarnings
    public static boolean launch(@NotNull String filename) {
        Util.checkNotNull(filename);

        /* On KDE with SWT 3.7, calling Program.launch will throw an UnsatisfiedLinkError, so don't do that and only try xdg-open. */
        if (!IS_LINUX_KDE && Program.launch(filename))
            return true;

        if (!IS_LINUX)
            return false;

        try {
            String[] cmd = { "xdg-open", filename };
            Process process = Runtime.getRuntime().exec(cmd);
            int exitValue = process.waitFor();
            return exitValue == 0;
        } catch (Exception e) {
            return false;
        }
    }

    /** @see #launch(String) */
    public static boolean launch(@NotNull File fileOrDir) {
        Util.checkNotNull(fileOrDir);
        return launch(getSystemAbsPath(fileOrDir));
    }

    @NotNull
    @SuppressAjWarnings
    public static File createTempDir() throws IOException {
        File dir = Files.createTempDir();

        /*
         * On Windows and Mac OS X, returning a canonical file avoids certain symlink-related issues:
         *
         * On Windows 7, Files.createTempDir() might return a directory that contains 8.3 filenames, 
         * for which the absolute and canonical paths differ, so that the directory will be incorrectly 
         * treated as a symlink. On Mac OS X, Files.createTempDir() will return an actual symlink.
         *
         * In both cases, returning a file that is or appears to be a symlink will lead to various problems, 
         * e.g. Files.deleteRecursively(File) failing to delete the temporary directory.
         */
        return IS_WINDOWS || IS_MAC_OS_X ? dir.getCanonicalFile() : dir;
    }

    /**
     * Equivalent to {@link #createTempFile(String, String, File)
     * createTempFile(String, String, null)}.
     */
    public static File createTempFile(@NotNull String prefix, @Nullable String suffix) throws IOException {
        return createTempFile(prefix, suffix, null);
    }

    /**
     * Equivalent to {@link File#createTempFile(String, String, File)}, except:
     * <ul>
     * <li>The returned file will be deleted automatically after JVM shutdown.
     * <li>Unlike {@link File#createTempFile(String, String, File)}, this method
     * will not throw an exception if the prefix is shorter than 3 characters.
     * Instead, the prefix will be right-padded with underscores to make it 3 characters long.
     * </ul>
     *
     * @see {@link File#createTempFile(String, String, File)}
     */
    @SuppressAjWarnings
    public static File createTempFile(@NotNull String prefix, @Nullable String suffix, @Nullable File directory)
            throws IOException {
        int prefixLength = prefix.length();
        if (prefixLength < 3)
            prefix += Strings.repeat("_", 3 - prefixLength);
        File file = File.createTempFile(prefix, suffix, directory);

        /*
         * On Mac OS X, File.createTempFile() will give us a symlink to a file, which is not what we want, because our 
         * file walker will silently ignore symlinks. The workaround is to return a canonical file on Mac OS X.
         */
        if (Util.IS_MAC_OS_X)
            file = file.getCanonicalFile();

        file.deleteOnExit();
        return file;
    }

    @NotNull
    public static File createDerivedTempFile(@NotNull String filename, @NotNull File tempDir) throws IOException {
        String[] nameParts = Util.splitFilename(filename);
        if (!nameParts[1].equals(""))
            nameParts[1] = "." + nameParts[1];
        return Util.createTempFile(nameParts[0], nameParts[1], tempDir);
    }

    @SuppressAjWarnings
    public static void println(@NotNull Object... objects) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Object object : objects) {
            if (first) {
                first = false;
            } else {
                sb.append("; ");
            }
            sb.append(object);
        }
        System.out.println(sb.toString());
    }

    /** Equivalent to <code>System.err.println(String)</code>. This method can be called instead to suppress AspectJ warnings. */
    @SuppressAjWarnings
    public static void printErr(@NotNull String message) {
        System.err.println(message);
    }

    /** Equivalent to {@link Throwable#printStackTrace()}. This method can be called instead to suppress AspectJ warnings. */
    @SuppressAjWarnings
    public static void printErr(@NotNull Throwable t) {
        t.printStackTrace();
    }

    @NotNull
    public static String getLowestMessage(@Nullable Throwable throwable) {
        if (throwable == null)
            return "";
        List<Throwable> chain = Throwables.getCausalChain(throwable);
        for (Throwable t : Lists.reverse(chain)) {
            String msg = t.getMessage();
            if (msg != null && !msg.trim().equals(""))
                return msg;
        }
        return "";
    }

    /**
     * Applying this method to the given widget will cause all the text in it to become selected if the user clicks on it 
     * after coming back from another part of the GUI or another program. The widget must be a Combo or a Text widget.
     */
    public static void selectAllOnFocus(@NotNull final Control text) {
        Util.checkThat(text instanceof Combo || text instanceof Text || text instanceof StyledText);

        class SelectAllOnFocus extends MouseAdapter implements FocusListener {
            private boolean focusGained = false;

            public void focusGained(FocusEvent e) {
                focusGained = true;
            }

            public void focusLost(FocusEvent e) {
            }

            public void mouseDown(MouseEvent e) {
                if (!focusGained)
                    return;
                if (text instanceof Combo)
                    selectAll((Combo) text);
                else if (text instanceof Text)
                    ((Text) text).selectAll();
                else if (text instanceof StyledText)
                    ((StyledText) text).selectAll();
                focusGained = false;
            }
        }

        SelectAllOnFocus listener = new SelectAllOnFocus();
        text.addFocusListener(listener);
        text.addMouseListener(listener);
    }

    @Nullable
    private static KeyListener selectAllKeyListener;

    public static void registerSelectAllKey(@NotNull final StyledText st) {
        if (selectAllKeyListener == null) {
            selectAllKeyListener = new KeyAdapter() {
                public void keyPressed(org.eclipse.swt.events.KeyEvent e) {
                    if (e.stateMask == SWT.MOD1 || e.keyCode == 'a')
                        st.selectAll();
                }
            };
        }
        st.addKeyListener(selectAllKeyListener);
    }

    /** Selects all the text in the given combo. */
    public static void selectAll(@NotNull Combo combo) {
        int length = combo.getText().length();
        combo.setSelection(new Point(0, length));
    }

    public static int clamp(int value, int minimum, int maximum) {
        Util.checkThat(minimum <= maximum);
        if (value > maximum)
            return maximum;
        if (value < minimum)
            return minimum;
        return value;
    }

    public static boolean isInterrupted() {
        return Thread.currentThread().isInterrupted();
    }

    /**
     * Returns an array of files from the system clipboard, or null if there are no files on the clipboard. This method 
     * should not be called from a non-GUI thread, and it should not be called before an SWT display has been created.
     */
    @Nullable
    public static List<File> getFilesFromClipboard() {
        assertSwtThread();
        Clipboard clipboard = new Clipboard(Display.getDefault());
        try {
            TransferData[] types = clipboard.getAvailableTypes();
            for (TransferData type : types) {
                if (!FileTransfer.getInstance().isSupportedType(type))
                    continue;

                Object data = clipboard.getContents(FileTransfer.getInstance());
                if (data == null || !(data instanceof String[]))
                    continue;

                String[] paths = (String[]) data;
                List<File> files = new ArrayList<File>(paths.length);
                for (String path : paths)
                    files.add(new File(path));
                return files;
            }
            return null;
        } finally {
            clipboard.dispose();
        }
    }

    public static void setClipboard(@NotNull String text) {
        Util.checkNotNull(text);
        Clipboard clipboard = new Clipboard(Display.getCurrent());
        Transfer[] types = new Transfer[] { TextTransfer.getInstance() };
        clipboard.setContents(new Object[] { text }, types);
        clipboard.dispose();
    }

    /**
     * Replaces the contents of the given clipboard with the given text and returns the clipboard. If the 
     * given clipboard is null, it will be created. This will only work if an SWT Display has been created.
     */
    public static void setClipboard(@NotNull Collection<File> files) {
        Util.checkNotNull(files);
        if (files.isEmpty())
            return;

        Clipboard clipboard = new Clipboard(Display.getCurrent());
        Transfer[] types = new Transfer[] { TextTransfer.getInstance(), FileTransfer.getInstance() };

        StringBuilder sb = new StringBuilder();
        String[] filePaths = new String[files.size()];
        int i = 0;
        for (File file : files) {
            if (i != 0)
                sb.append("\n");
            String path = Util.getSystemAbsPath(file);
            sb.append(path);
            filePaths[i] = path;
            i++;
        }

        clipboard.setContents(new Object[] { sb.toString(), filePaths }, types);
        clipboard.dispose();
    }

    /** Adds a {@link MouseTrackListener} to the given control that highlights the background when the mouse hovers over the control. */
    public static void addMouseHighlighter(@NotNull final Control control) {
        control.addMouseTrackListener(new MouseTrackAdapter() {
            public void mouseEnter(MouseEvent e) {
                control.setBackground(Col.WIDGET_HIGHLIGHT_SHADOW.get());
            }

            public void mouseExit(MouseEvent e) {
                control.setBackground(null);
            }
        });
    }

    /** Returns whether the given key code represents the Enter key, which can be either the 'normal' Enter key or the Enter key on the numpad. */
    public static boolean isEnterKey(int keyCode) {
        return keyCode == SWT.CR || keyCode == SWT.KEYPAD_CR;
    }

    // Any of the given resources may be null
    public static void disposeWith(@NotNull Widget widget, @NotNull final Resource... resources) {
        Util.checkNotNull(widget, resources);
        widget.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                for (Resource resource : resources)
                    if (resource != null)
                        resource.dispose();
            }
        });
    }

    /**
    * Launches the given filepath and select the file explorer.exe /select,F:\docfetcher\DocFetcher\aspectjtools.jar
    * returning whether the file was successfully launched This process is not working properly in Win7 64bit
    */
    public static boolean winOpenDir(String fileName) {
        if (IS_WINDOWS) {
            try {
                Runtime.getRuntime().exec("explorer /select, " + fileName);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

}