src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java
author phh
Sat, 30 Nov 2019 14:33:05 -0800
changeset 59330 5b96c12f909d
parent 52168 0c50240bf61d
permissions -rw-r--r--
8234541: C1 emits an empty message when it inlines successfully Summary: Use "inline" as the message when successfull Reviewed-by: thartmann, mdoerr Contributed-by: navy.xliu@gmail.com

/*
 * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.shell;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.AbstractMultiResolutionImage;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import javax.swing.SwingConstants;

// NOTE: This class supersedes Win32ShellFolder, which was removed from
//       distribution after version 1.4.2.

/**
 * Win32 Shell Folders
 * <P>
 * <BR>
 * There are two fundamental types of shell folders : file system folders
 * and non-file system folders.  File system folders are relatively easy
 * to deal with.  Non-file system folders are items such as My Computer,
 * Network Neighborhood, and the desktop.  Some of these non-file system
 * folders have special values and properties.
 * <P>
 * <BR>
 * Win32 keeps two basic data structures for shell folders.  The first
 * of these is called an ITEMIDLIST.  Usually a pointer, called an
 * LPITEMIDLIST, or more frequently just "PIDL".  This structure holds
 * a series of identifiers and can be either relative to the desktop
 * (an absolute PIDL), or relative to the shell folder that contains them.
 * Some Win32 functions can take absolute or relative PIDL values, and
 * others can only accept relative values.
 * <BR>
 * The second data structure is an IShellFolder COM interface.  Using
 * this interface, one can enumerate the relative PIDLs in a shell
 * folder, get attributes, etc.
 * <BR>
 * All Win32ShellFolder2 objects which are folder types (even non-file
 * system folders) contain an IShellFolder object. Files are named in
 * directories via relative PIDLs.
 *
 * @author Michael Martak
 * @author Leif Samuelsson
 * @author Kenneth Russell
 * @since 1.4 */
@SuppressWarnings("serial") // JDK-implementation class
final class Win32ShellFolder2 extends ShellFolder {

    private static native void initIDs();

    static {
        initIDs();
    }

    // Win32 Shell Folder Constants
    public static final int DESKTOP = 0x0000;
    public static final int INTERNET = 0x0001;
    public static final int PROGRAMS = 0x0002;
    public static final int CONTROLS = 0x0003;
    public static final int PRINTERS = 0x0004;
    public static final int PERSONAL = 0x0005;
    public static final int FAVORITES = 0x0006;
    public static final int STARTUP = 0x0007;
    public static final int RECENT = 0x0008;
    public static final int SENDTO = 0x0009;
    public static final int BITBUCKET = 0x000a;
    public static final int STARTMENU = 0x000b;
    public static final int DESKTOPDIRECTORY = 0x0010;
    public static final int DRIVES = 0x0011;
    public static final int NETWORK = 0x0012;
    public static final int NETHOOD = 0x0013;
    public static final int FONTS = 0x0014;
    public static final int TEMPLATES = 0x0015;
    public static final int COMMON_STARTMENU = 0x0016;
    public static final int COMMON_PROGRAMS = 0X0017;
    public static final int COMMON_STARTUP = 0x0018;
    public static final int COMMON_DESKTOPDIRECTORY = 0x0019;
    public static final int APPDATA = 0x001a;
    public static final int PRINTHOOD = 0x001b;
    public static final int ALTSTARTUP = 0x001d;
    public static final int COMMON_ALTSTARTUP = 0x001e;
    public static final int COMMON_FAVORITES = 0x001f;
    public static final int INTERNET_CACHE = 0x0020;
    public static final int COOKIES = 0x0021;
    public static final int HISTORY = 0x0022;

    // Win32 shell folder attributes
    public static final int ATTRIB_CANCOPY          = 0x00000001;
    public static final int ATTRIB_CANMOVE          = 0x00000002;
    public static final int ATTRIB_CANLINK          = 0x00000004;
    public static final int ATTRIB_CANRENAME        = 0x00000010;
    public static final int ATTRIB_CANDELETE        = 0x00000020;
    public static final int ATTRIB_HASPROPSHEET     = 0x00000040;
    public static final int ATTRIB_DROPTARGET       = 0x00000100;
    public static final int ATTRIB_LINK             = 0x00010000;
    public static final int ATTRIB_SHARE            = 0x00020000;
    public static final int ATTRIB_READONLY         = 0x00040000;
    public static final int ATTRIB_GHOSTED          = 0x00080000;
    public static final int ATTRIB_HIDDEN           = 0x00080000;
    public static final int ATTRIB_FILESYSANCESTOR  = 0x10000000;
    public static final int ATTRIB_FOLDER           = 0x20000000;
    public static final int ATTRIB_FILESYSTEM       = 0x40000000;
    public static final int ATTRIB_HASSUBFOLDER     = 0x80000000;
    public static final int ATTRIB_VALIDATE         = 0x01000000;
    public static final int ATTRIB_REMOVABLE        = 0x02000000;
    public static final int ATTRIB_COMPRESSED       = 0x04000000;
    public static final int ATTRIB_BROWSABLE        = 0x08000000;
    public static final int ATTRIB_NONENUMERATED    = 0x00100000;
    public static final int ATTRIB_NEWCONTENT       = 0x00200000;

    // IShellFolder::GetDisplayNameOf constants
    public static final int SHGDN_NORMAL            = 0;
    public static final int SHGDN_INFOLDER          = 1;
    public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000;
    public static final int SHGDN_FORADDRESSBAR     = 0x4000;
    public static final int SHGDN_FORPARSING        = 0x8000;

    /** The referent to be registered with the Disposer. */
    private Object disposerReferent = new Object();

    // Values for system call LoadIcon()
    public enum SystemIcon {
        IDI_APPLICATION(32512),
        IDI_HAND(32513),
        IDI_ERROR(32513),
        IDI_QUESTION(32514),
        IDI_EXCLAMATION(32515),
        IDI_WARNING(32515),
        IDI_ASTERISK(32516),
        IDI_INFORMATION(32516),
        IDI_WINLOGO(32517);

        private final int iconID;

        SystemIcon(int iconID) {
            this.iconID = iconID;
        }

        public int getIconID() {
            return iconID;
        }
    }

    // Known Folder data
    static final class KnownFolderDefinition {
        String guid;
        int category;
        String name;
        String description;
        String parent;
        String relativePath;
        String parsingName;
        String tooltip;
        String localizedName;
        String icon;
        String security;
        long attributes;
        int defenitionFlags;
        String ftidType;
        String path;
        String saveLocation;
    }

    static final class KnownLibraries {
        static final List<KnownFolderDefinition> INSTANCE = getLibraries();
    }

    static class FolderDisposer implements sun.java2d.DisposerRecord {
        /*
         * This is cached as a concession to getFolderType(), which needs
         * an absolute PIDL.
         */
        long absolutePIDL;
        /*
         * We keep track of shell folders through the IShellFolder
         * interface of their parents plus their relative PIDL.
         */
        long pIShellFolder;
        long relativePIDL;

        boolean disposed;
        public void dispose() {
            if (disposed) return;
            invoke(new Callable<Void>() {
                public Void call() {
                    if (relativePIDL != 0) {
                        releasePIDL(relativePIDL);
                    }
                    if (absolutePIDL != 0) {
                        releasePIDL(absolutePIDL);
                    }
                    if (pIShellFolder != 0) {
                        releaseIShellFolder(pIShellFolder);
                    }
                    return null;
                }
            });
            disposed = true;
        }
    }
    FolderDisposer disposer = new FolderDisposer();
    private void setIShellFolder(long pIShellFolder) {
        disposer.pIShellFolder = pIShellFolder;
    }
    private void setRelativePIDL(long relativePIDL) {
        disposer.relativePIDL = relativePIDL;
    }
    /*
     * The following are for caching various shell folder properties.
     */
    private long pIShellIcon = -1L;
    private String folderType = null;
    private String displayName = null;
    private Image smallIcon = null;
    private Image largeIcon = null;
    private Boolean isDir = null;
    private final boolean isLib;
    private static final String FNAME = COLUMN_NAME;
    private static final String FSIZE = COLUMN_SIZE;
    private static final String FTYPE = "FileChooser.fileTypeHeaderText";
    private static final String FDATE = COLUMN_DATE;

    /*
     * The following is to identify the My Documents folder as being special
     */
    private boolean isPersonal;

    private static String composePathForCsidl(int csidl) throws IOException, InterruptedException {
        String path = getFileSystemPath(csidl);
        return path == null
                ? ("ShellFolder: 0x" + Integer.toHexString(csidl))
                : path;
    }

    /**
     * Create a system special shell folder, such as the
     * desktop or Network Neighborhood.
     */
    Win32ShellFolder2(final int csidl) throws IOException, InterruptedException {
        // Desktop is parent of DRIVES and NETWORK, not necessarily
        // other special shell folders.
        super(null, composePathForCsidl(csidl));
        isLib = false;

        invoke(new Callable<Void>() {
            public Void call() throws InterruptedException {
                if (csidl == DESKTOP) {
                    initDesktop();
                } else {
                    initSpecial(getDesktop().getIShellFolder(), csidl);
                    // At this point, the native method initSpecial() has set our relativePIDL
                    // relative to the Desktop, which may not be our immediate parent. We need
                    // to traverse this ID list and break it into a chain of shell folders from
                    // the top, with each one having an immediate parent and a relativePIDL
                    // relative to that parent.
                    long pIDL = disposer.relativePIDL;
                    parent = getDesktop();
                    while (pIDL != 0) {
                        // Get a child pidl relative to 'parent'
                        long childPIDL = copyFirstPIDLEntry(pIDL);
                        if (childPIDL != 0) {
                            // Get a handle to the rest of the ID list
                            // i,e, parent's grandchilren and down
                            pIDL = getNextPIDLEntry(pIDL);
                            if (pIDL != 0) {
                                // Now we know that parent isn't immediate to 'this' because it
                                // has a continued ID list. Create a shell folder for this child
                                // pidl and make it the new 'parent'.
                                parent = createShellFolder((Win32ShellFolder2) parent, childPIDL);
                            } else {
                                // No grandchildren means we have arrived at the parent of 'this',
                                // and childPIDL is directly relative to parent.
                                disposer.relativePIDL = childPIDL;
                            }
                        } else {
                            break;
                        }
                    }
                }
                return null;
            }
        }, InterruptedException.class);

        sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer);
    }


    /**
     * Create a system shell folder
     */
    Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path, boolean isLib) {
        super(parent, (path != null) ? path : "ShellFolder: ");
        this.isLib = isLib;
        this.disposer.pIShellFolder = pIShellFolder;
        this.disposer.relativePIDL = relativePIDL;
        sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer);
    }


    /**
     * Creates a shell folder with a parent and relative PIDL
     */
    static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, long pIDL)
            throws InterruptedException {
        String path = invoke(new Callable<String>() {
            public String call() {
                return getFileSystemPath(parent.getIShellFolder(), pIDL);
            }
        }, RuntimeException.class);
        String libPath = resolveLibrary(path);
        if (libPath == null) {
            return new Win32ShellFolder2(parent, 0, pIDL, path, false);
        } else {
            return new Win32ShellFolder2(parent, 0, pIDL, libPath, true);
        }
    }

    // Initializes the desktop shell folder
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native void initDesktop();

    // Initializes a special, non-file system shell folder
    // from one of the above constants
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native void initSpecial(long desktopIShellFolder, int csidl);

    /** Marks this folder as being the My Documents (Personal) folder */
    public void setIsPersonal() {
        isPersonal = true;
    }

    /**
     * This method is implemented to make sure that no instances
     * of {@code ShellFolder} are ever serialized. If {@code isFileSystem()} returns
     * {@code true}, then the object is representable with an instance of
     * {@code java.io.File} instead. If not, then the object depends
     * on native PIDL state and should not be serialized.
     *
     * @return a {@code java.io.File} replacement object. If the folder
     * is a not a normal directory, then returns the first non-removable
     * drive (normally "C:\").
     */
    protected Object writeReplace() throws java.io.ObjectStreamException {
        return invoke(new Callable<File>() {
            public File call() {
                if (isFileSystem()) {
                    return new File(getPath());
                } else {
                    Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives();
                    if (drives != null) {
                        File[] driveRoots = drives.listFiles();
                        if (driveRoots != null) {
                            for (int i = 0; i < driveRoots.length; i++) {
                                if (driveRoots[i] instanceof Win32ShellFolder2) {
                                    Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i];
                                    if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) {
                                        return new File(sf.getPath());
                                    }
                                }
                            }
                        }
                    }
                    // Ouch, we have no hard drives. Return something "valid" anyway.
                    return new File("C:\\");
                }
            }
        });
    }


    /**
     * Finalizer to clean up any COM objects or PIDLs used by this object.
     */
    protected void dispose() {
        disposer.dispose();
    }


    // Given a (possibly multi-level) relative PIDL (with respect to
    // the desktop, at least in all of the usage cases in this code),
    // return a pointer to the next entry. Does not mutate the PIDL in
    // any way. Returns 0 if the null terminator is reached.
    // Needs to be accessible to Win32ShellFolderManager2
    static native long getNextPIDLEntry(long pIDL);

    // Given a (possibly multi-level) relative PIDL (with respect to
    // the desktop, at least in all of the usage cases in this code),
    // copy the first entry into a newly-allocated PIDL. Returns 0 if
    // the PIDL is at the end of the list.
    // Needs to be accessible to Win32ShellFolderManager2
    static native long copyFirstPIDLEntry(long pIDL);

    // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL
    private static native long combinePIDLs(long ppIDL, long pIDL);

    // Release a PIDL object
    // Needs to be accessible to Win32ShellFolderManager2
    static native void releasePIDL(long pIDL);

    // Release an IShellFolder object
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native void releaseIShellFolder(long pIShellFolder);

    /**
     * Accessor for IShellFolder
     */
    private long getIShellFolder() {
        if (disposer.pIShellFolder == 0) {
            try {
                disposer.pIShellFolder = invoke(new Callable<Long>() {
                    public Long call() {
                        assert(isDirectory());
                        assert(parent != null);
                        long parentIShellFolder = getParentIShellFolder();
                        if (parentIShellFolder == 0) {
                            throw new InternalError("Parent IShellFolder was null for "
                                    + getAbsolutePath());
                        }
                        // We are a directory with a parent and a relative PIDL.
                        // We want to bind to the parent so we get an
                        // IShellFolder instance associated with us.
                        long pIShellFolder = bindToObject(parentIShellFolder,
                                disposer.relativePIDL);
                        if (pIShellFolder == 0) {
                            throw new InternalError("Unable to bind "
                                    + getAbsolutePath() + " to parent");
                        }
                        return pIShellFolder;
                    }
                }, RuntimeException.class);
            } catch (InterruptedException e) {
                // Ignore error
            }
        }
        return disposer.pIShellFolder;
    }

    /**
     * Get the parent ShellFolder's IShellFolder interface
     */
    public long getParentIShellFolder() {
        Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile();
        if (parent == null) {
            // Parent should only be null if this is the desktop, whose
            // relativePIDL is relative to its own IShellFolder.
            return getIShellFolder();
        }
        return parent.getIShellFolder();
    }

    /**
     * Accessor for relative PIDL
     */
    public long getRelativePIDL() {
        if (disposer.relativePIDL == 0) {
            throw new InternalError("Should always have a relative PIDL");
        }
        return disposer.relativePIDL;
    }

    private long getAbsolutePIDL() {
        if (parent == null) {
            // This is the desktop
            return getRelativePIDL();
        } else {
            if (disposer.absolutePIDL == 0) {
                disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL());
            }

            return disposer.absolutePIDL;
        }
    }

    /**
     * Helper function to return the desktop
     */
    public Win32ShellFolder2 getDesktop() {
        return Win32ShellFolderManager2.getDesktop();
    }

    /**
     * Helper function to return the desktop IShellFolder interface
     */
    public long getDesktopIShellFolder() {
        return getDesktop().getIShellFolder();
    }

    private static boolean pathsEqual(String path1, String path2) {
        // Same effective implementation as Win32FileSystem
        return path1.equalsIgnoreCase(path2);
    }

    /**
     * Check to see if two ShellFolder objects are the same
     */
    public boolean equals(Object o) {
        if (o == null || !(o instanceof Win32ShellFolder2)) {
            // Short-circuit circuitous delegation path
            if (!(o instanceof File)) {
                return super.equals(o);
            }
            return pathsEqual(getPath(), ((File) o).getPath());
        }
        Win32ShellFolder2 rhs = (Win32ShellFolder2) o;
        if ((parent == null && rhs.parent != null) ||
            (parent != null && rhs.parent == null)) {
            return false;
        }

        if (isFileSystem() && rhs.isFileSystem()) {
            // Only folders with identical parents can be equal
            return (pathsEqual(getPath(), rhs.getPath()) &&
                    (parent == rhs.parent || parent.equals(rhs.parent)));
        }

        if (parent == rhs.parent || parent.equals(rhs.parent)) {
            try {
                return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL);
            } catch (InterruptedException e) {
                return false;
            }
        }

        return false;
    }

    private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2)
            throws InterruptedException {
        return invoke(new Callable<Boolean>() {
            public Boolean call() {
                return compareIDs(pIShellFolder, pidl1, pidl2) == 0;
            }
        }, RuntimeException.class);
    }

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2);

    private volatile Boolean cachedIsFileSystem;

    /**
     * @return Whether this is a file system shell folder
     */
    public boolean isFileSystem() {
        if (cachedIsFileSystem == null) {
            cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM);
        }

        return cachedIsFileSystem;
    }

    /**
     * Return whether the given attribute flag is set for this object
     */
    public boolean hasAttribute(final int attribute) {
        Boolean result = invoke(new Callable<Boolean>() {
            public Boolean call() {
                // Caching at this point doesn't seem to be cost efficient
                return (getAttributes0(getParentIShellFolder(),
                    getRelativePIDL(), attribute)
                    & attribute) != 0;
            }
        });

        return result != null && result;
    }

    /**
     * Returns the queried attributes specified in attrsMask.
     *
     * Could plausibly be used for attribute caching but have to be
     * very careful not to touch network drives and file system roots
     * with a full attrsMask
     * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
     */

    private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask);

    // Return the path to the underlying file system object
    // Should be called from the COM thread
    private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) {
        int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER;
        if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() &&
                getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) {

            String s =
                    getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(),
                            getLinkLocation(parentIShellFolder, relativePIDL, false));
            if (s != null && s.startsWith("\\\\")) {
                return s;
            }
        }
        String path = getDisplayNameOf(parentIShellFolder, relativePIDL,
                        SHGDN_FORPARSING);
        return path;
    }

    private static String resolveLibrary(String path) {
        // if this is a library its default save location is taken as a path
        // this is a temp fix until java.io starts support Libraries
        if( path != null && path.startsWith("::{") &&
                path.toLowerCase().endsWith(".library-ms")) {
            for (KnownFolderDefinition kf : KnownLibraries.INSTANCE) {
                if (path.toLowerCase().endsWith(
                        "\\" + kf.relativePath.toLowerCase()) &&
                        path.toUpperCase().startsWith(
                        kf.parsingName.substring(0, 40).toUpperCase())) {
                    return kf.saveLocation;
                }
            }
        }
        return null;
    }

    // Needs to be accessible to Win32ShellFolderManager2
    static String getFileSystemPath(final int csidl) throws IOException, InterruptedException {
        String path = invoke(new Callable<String>() {
            public String call() throws IOException {
                return getFileSystemPath0(csidl);
            }
        }, IOException.class);
        if (path != null) {
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkRead(path);
            }
        }
        return path;
    }

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native String getFileSystemPath0(int csidl) throws IOException;

    // Return whether the path is a network root.
    // Path is assumed to be non-null
    private static boolean isNetworkRoot(String path) {
        return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/"));
    }

    /**
     * @return The parent shell folder of this shell folder, null if
     * there is no parent
     */
    public File getParentFile() {
        return parent;
    }

    public boolean isDirectory() {
        if (isDir == null) {
            // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are
            // not traversable in JFileChooser.
            if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) {
                isDir = Boolean.TRUE;
            } else if (isLink()) {
                ShellFolder linkLocation = getLinkLocation(false);
                isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory());
            } else {
                isDir = Boolean.FALSE;
            }
        }
        return isDir.booleanValue();
    }

    /*
     * Functions for enumerating an IShellFolder's children
     */
    // Returns an IEnumIDList interface for an IShellFolder.  The value
    // returned must be released using releaseEnumObjects().
    private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException {
        return invoke(new Callable<Long>() {
            public Long call() {
                boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder();

                return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles);
            }
        }, RuntimeException.class);
    }

    // Returns an IEnumIDList interface for an IShellFolder.  The value
    // returned must be released using releaseEnumObjects().
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native long getEnumObjects(long pIShellFolder, boolean isDesktop,
                                       boolean includeHiddenFiles);
    // Returns the next sequential child as a relative PIDL
    // from an IEnumIDList interface.  The value returned must
    // be released using releasePIDL().
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native long getNextChild(long pEnumObjects);
    // Releases the IEnumIDList interface
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native void releaseEnumObjects(long pEnumObjects);

    // Returns the IShellFolder of a child from a parent IShellFolder
    // and a relative PIDL.  The value returned must be released
    // using releaseIShellFolder().
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native long bindToObject(long parentIShellFolder, long pIDL);

    /**
     * @return An array of shell folders that are children of this shell folder
     *         object. The array will be empty if the folder is empty.  Returns
     *         {@code null} if this shellfolder does not denote a directory.
     */
    public File[] listFiles(final boolean includeHiddenFiles) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(getPath());
        }

        try {
            File[] files = invoke(new Callable<File[]>() {
                public File[] call() throws InterruptedException {
                    if (!isDirectory()) {
                        return null;
                    }
                    // Links to directories are not directories and cannot be parents.
                    // This does not apply to folders in My Network Places (NetHood)
                    // because they are both links and real directories!
                    if (isLink() && !hasAttribute(ATTRIB_FOLDER)) {
                        return new File[0];
                    }

                    Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop();
                    Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal();

                    // If we are a directory, we have a parent and (at least) a
                    // relative PIDL. We must first ensure we are bound to the
                    // parent so we have an IShellFolder to query.
                    long pIShellFolder = getIShellFolder();
                    // Now we can enumerate the objects in this folder.
                    ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>();
                    long pEnumObjects = getEnumObjects(includeHiddenFiles);
                    if (pEnumObjects != 0) {
                        try {
                            long childPIDL;
                            int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR;
                            do {
                                childPIDL = getNextChild(pEnumObjects);
                                boolean releasePIDL = true;
                                if (childPIDL != 0 &&
                                        (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) {
                                    Win32ShellFolder2 childFolder;
                                    if (Win32ShellFolder2.this.equals(desktop)
                                            && personal != null
                                            && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) {
                                        childFolder = personal;
                                    } else {
                                        childFolder = createShellFolder(Win32ShellFolder2.this, childPIDL);
                                        releasePIDL = false;
                                    }
                                    list.add(childFolder);
                                }
                                if (releasePIDL) {
                                    releasePIDL(childPIDL);
                                }
                            } while (childPIDL != 0 && !Thread.currentThread().isInterrupted());
                        } finally {
                            releaseEnumObjects(pEnumObjects);
                        }
                    }
                    return Thread.currentThread().isInterrupted()
                        ? new File[0]
                        : list.toArray(new ShellFolder[list.size()]);
                }
            }, InterruptedException.class);

            return Win32ShellFolderManager2.checkFiles(files);
        } catch (InterruptedException e) {
            return new File[0];
        }
    }


    /**
     * Look for (possibly special) child folder by it's path
     *
     * @return The child shellfolder, or null if not found.
     */
    Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
        return invoke(new Callable<Win32ShellFolder2>() {
            public Win32ShellFolder2 call() throws InterruptedException {
                long pIShellFolder = getIShellFolder();
                long pEnumObjects = getEnumObjects(true);
                Win32ShellFolder2 child = null;
                long childPIDL;

                while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
                    if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
                        String path = getFileSystemPath(pIShellFolder, childPIDL);
                        if(isLib) path = resolveLibrary( path );
                        if (path != null && path.equalsIgnoreCase(filePath)) {
                            long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
                            child = new Win32ShellFolder2(Win32ShellFolder2.this,
                                    childIShellFolder, childPIDL, path, isLib);
                            break;
                        }
                    }
                    releasePIDL(childPIDL);
                }
                releaseEnumObjects(pEnumObjects);
                return child;
            }
        }, InterruptedException.class);
    }

    private volatile Boolean cachedIsLink;

    /**
     * @return Whether this shell folder is a link
     */
    public boolean isLink() {
        if (cachedIsLink == null) {
            cachedIsLink = hasAttribute(ATTRIB_LINK);
        }

        return cachedIsLink;
    }

    /**
     * @return Whether this shell folder is marked as hidden
     */
    public boolean isHidden() {
        return hasAttribute(ATTRIB_HIDDEN);
    }


    // Return the link location of a shell folder
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native long getLinkLocation(long parentIShellFolder,
                                        long relativePIDL, boolean resolve);

    /**
     * @return The shell folder linked to by this shell folder, or null
     * if this shell folder is not a link or is a broken or invalid link
     */
    public ShellFolder getLinkLocation()  {
        return getLinkLocation(true);
    }

    private Win32ShellFolder2 getLinkLocation(final boolean resolve) {
        return invoke(new Callable<Win32ShellFolder2>() {
            public Win32ShellFolder2 call() {
                if (!isLink()) {
                    return null;
                }

                Win32ShellFolder2 location = null;
                long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
                        getRelativePIDL(), resolve);
                if (linkLocationPIDL != 0) {
                    try {
                        location =
                                Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
                                        linkLocationPIDL);
                    } catch (InterruptedException e) {
                        // Return null
                    } catch (InternalError e) {
                        // Could be a link to a non-bindable object, such as a network connection
                        // TODO: getIShellFolder() should throw FileNotFoundException instead
                    }
                }
                return location;
            }
        });
    }

    // Parse a display name into a PIDL relative to the current IShellFolder.
    long parseDisplayName(final String name) throws IOException, InterruptedException {
        return invoke(new Callable<Long>() {
            public Long call() throws IOException {
                return parseDisplayName0(getIShellFolder(), name);
            }
        }, IOException.class);
    }

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;

    // Return the display name of a shell folder
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native String getDisplayNameOf(long parentIShellFolder,
                                                  long relativePIDL,
                                                  int attrs);

    // Returns data of all Known Folders registered in the system
    private static native KnownFolderDefinition[] loadKnownFolders();

    /**
     * @return The name used to display this shell folder
     */
    public String getDisplayName() {
        if (displayName == null) {
            displayName =
                invoke(new Callable<String>() {
                    public String call() {
                        return getDisplayNameOf(getParentIShellFolder(),
                                getRelativePIDL(), SHGDN_NORMAL);
                    }
                });
        }
        return displayName;
    }

    // Return the folder type of a shell folder
    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native String getFolderType(long pIDL);

    /**
     * @return The type of shell folder as a string
     */
    public String getFolderType() {
        if (folderType == null) {
            final long absolutePIDL = getAbsolutePIDL();
            folderType =
                invoke(new Callable<String>() {
                    public String call() {
                        return getFolderType(absolutePIDL);
                    }
                });
        }
        return folderType;
    }

    // Return the executable type of a file system shell folder
    private native String getExecutableType(String path);

    /**
     * @return The executable type as a string
     */
    public String getExecutableType() {
        if (!isFileSystem()) {
            return null;
        }
        return getExecutableType(getAbsolutePath());
    }



    // Icons

    private static Map<Integer, Image> smallSystemImages = new HashMap<>();
    private static Map<Integer, Image> largeSystemImages = new HashMap<>();
    private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>();
    private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>();

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native long getIShellIcon(long pIShellFolder);

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native int getIconIndex(long parentIShellIcon, long relativePIDL);

    // Return the icon of a file system shell folder in the form of an HICON
    private static native long getIcon(String absolutePath, boolean getLargeIcon);

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native long extractIcon(long parentIShellFolder, long relativePIDL,
                                           boolean getLargeIcon, boolean getDefaultIcon);

    // Returns an icon from the Windows system icon list in the form of an HICON
    private static native long getSystemIcon(int iconID);
    private static native long getIconResource(String libName, int iconID,
                                               int cxDesired, int cyDesired,
                                               boolean useVGAColors);
                                               // Note: useVGAColors is ignored on XP and later

    // Return the bits from an HICON.  This has a side effect of setting
    // the imageHash variable for efficient caching / comparing.
    private static native int[] getIconBits(long hIcon);
    // Dispose the HICON
    private static native void disposeIcon(long hIcon);

    // Get buttons from native toolbar implementation.
    static native int[] getStandardViewButton0(int iconIndex, boolean small);

    // Should be called from the COM thread
    private long getIShellIcon() {
        if (pIShellIcon == -1L) {
            pIShellIcon = getIShellIcon(getIShellFolder());
        }

        return pIShellIcon;
    }

    private static Image makeIcon(long hIcon, boolean getLargeIcon) {
        if (hIcon != 0L && hIcon != -1L) {
            // Get the bits.  This has the side effect of setting the imageHash value for this object.
            final int[] iconBits = getIconBits(hIcon);
            if (iconBits != null) {
                // icons are always square
                final int size = (int) Math.sqrt(iconBits.length);
                final int baseSize = getLargeIcon ? 32 : 16;
                final BufferedImage img =
                        new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
                img.setRGB(0, 0, size, size, iconBits, 0, size);
                return size == baseSize
                        ? img
                        : new MultiResolutionIconImage(baseSize, img);
            }
        }
        return null;
    }


    /**
     * @return The icon image used to display this shell folder
     */
    public Image getIcon(final boolean getLargeIcon) {
        Image icon = getLargeIcon ? largeIcon : smallIcon;
        if (icon == null) {
            icon =
                invoke(new Callable<Image>() {
                    public Image call() {
                        Image newIcon = null;
                        if (isLink()) {
                            Win32ShellFolder2 folder = getLinkLocation(false);
                            if (folder != null && folder.isLibrary()) {
                                return folder.getIcon(getLargeIcon);
                            }
                        }
                        if (isFileSystem() || isLibrary()) {
                            long parentIShellIcon = (parent != null)
                                ? ((Win32ShellFolder2) parent).getIShellIcon()
                                : 0L;
                            long relativePIDL = getRelativePIDL();

                            // These are cached per type (using the index in the system image list)
                            int index = getIconIndex(parentIShellIcon, relativePIDL);
                            if (index > 0) {
                                Map<Integer, Image> imageCache;
                                if (isLink()) {
                                    imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
                                } else {
                                    imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
                                }
                                newIcon = imageCache.get(Integer.valueOf(index));
                                if (newIcon == null) {
                                    long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
                                    newIcon = makeIcon(hIcon, getLargeIcon);
                                    disposeIcon(hIcon);
                                    if (newIcon != null) {
                                        imageCache.put(Integer.valueOf(index), newIcon);
                                    }
                                }
                            }
                        }

                        if (newIcon == null) {
                            // These are only cached per object
                            long hIcon = extractIcon(getParentIShellFolder(),
                                    getRelativePIDL(), getLargeIcon, false);
                            // E_PENDING: loading can take time so get the default
                            if(hIcon <= 0) {
                                hIcon = extractIcon(getParentIShellFolder(),
                                         getRelativePIDL(), getLargeIcon, true);
                                if(hIcon <= 0) {
                                    if (isDirectory()) {
                                        return getShell32Icon(4, getLargeIcon);
                                    } else {
                                        return getShell32Icon(1, getLargeIcon);
                                    }
                                }
                            }
                            newIcon = makeIcon(hIcon, getLargeIcon);
                            disposeIcon(hIcon);
                        }

                        if (newIcon == null) {
                            newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
                        }
                        return newIcon;
                    }
                });
            if (getLargeIcon) {
                largeIcon = icon;
            } else {
                smallIcon = icon;
            }
        }
        return icon;
    }

    /**
     * Gets an icon from the Windows system icon list as an {@code Image}
     */
    static Image getSystemIcon(SystemIcon iconType) {
        long hIcon = getSystemIcon(iconType.getIconID());
        Image icon = makeIcon(hIcon, true);
        disposeIcon(hIcon);
        return icon;
    }

    /**
     * Gets an icon from the Windows system icon list as an {@code Image}
     */
    static Image getShell32Icon(int iconID, boolean getLargeIcon) {
        boolean useVGAColors = true; // Will be ignored on XP and later

        int size = getLargeIcon ? 32 : 16;

        Toolkit toolkit = Toolkit.getDefaultToolkit();
        String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
        if (shellIconBPP != null) {
            useVGAColors = shellIconBPP.equals("4");
        }

        long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
        if (hIcon != 0) {
            Image icon = makeIcon(hIcon, getLargeIcon);
            disposeIcon(hIcon);
            return icon;
        }
        return null;
    }

    /**
     * Returns the canonical form of this abstract pathname.  Equivalent to
     * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
     *
     * @see java.io.File#getCanonicalFile
     */
    public File getCanonicalFile() throws IOException {
        return this;
    }

    /*
     * Indicates whether this is a special folder (includes My Documents)
     */
    public boolean isSpecial() {
        return isPersonal || !isFileSystem() || (this == getDesktop());
    }

    /**
     * Compares this object with the specified object for order.
     *
     * @see sun.awt.shell.ShellFolder#compareTo(File)
     */
    public int compareTo(File file2) {
        if (!(file2 instanceof Win32ShellFolder2)) {
            if (isFileSystem() && !isSpecial()) {
                return super.compareTo(file2);
            } else {
                return -1; // Non-file shellfolders sort before files
            }
        }
        return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
    }

    // native constants from commctrl.h
    private static final int LVCFMT_LEFT = 0;
    private static final int LVCFMT_RIGHT = 1;
    private static final int LVCFMT_CENTER = 2;

    public ShellFolderColumnInfo[] getFolderColumns() {
        ShellFolder library = resolveLibrary();
        if (library != null) return library.getFolderColumns();
        return invoke(new Callable<ShellFolderColumnInfo[]>() {
            public ShellFolderColumnInfo[] call() {
                ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());

                if (columns != null) {
                    List<ShellFolderColumnInfo> notNullColumns =
                            new ArrayList<ShellFolderColumnInfo>();
                    for (int i = 0; i < columns.length; i++) {
                        ShellFolderColumnInfo column = columns[i];
                        if (column != null) {
                            column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
                                    ? SwingConstants.RIGHT
                                    : column.getAlignment() == LVCFMT_CENTER
                                    ? SwingConstants.CENTER
                                    : SwingConstants.LEADING);

                            column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));

                            notNullColumns.add(column);
                        }
                    }
                    columns = new ShellFolderColumnInfo[notNullColumns.size()];
                    notNullColumns.toArray(columns);
                }
                return columns;
            }
        });
    }

    public Object getFolderColumnValue(final int column) {
        if(!isLibrary()) {
            ShellFolder library = resolveLibrary();
            if (library != null) return library.getFolderColumnValue(column);
        }
        return invoke(new Callable<Object>() {
            public Object call() {
                return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
            }
        });
    }

    boolean isLibrary() {
        return isLib;
    }

    private ShellFolder resolveLibrary() {
        for (ShellFolder f = this; f != null; f = f.parent) {
            if (!f.isFileSystem()) {
                if (f instanceof Win32ShellFolder2 &&
                                           ((Win32ShellFolder2)f).isLibrary()) {
                    try {
                        return getShellFolder(new File(getPath()));
                    } catch (FileNotFoundException e) {
                    }
                }
                break;
            }
        }
        return null;
    }

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);

    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
    private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);


    public void sortChildren(final List<? extends File> files) {
        // To avoid loads of synchronizations with Invoker and improve performance we
        // synchronize the whole code of the sort method once
        invoke(new Callable<Void>() {
            public Void call() {
                Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));

                return null;
            }
        });
    }

    private static class ColumnComparator implements Comparator<File> {
        private final Win32ShellFolder2 shellFolder;

        private final int columnIdx;

        public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
            this.shellFolder = shellFolder;
            this.columnIdx = columnIdx;
        }

        // compares 2 objects within this folder by the specified column
        public int compare(final File o, final File o1) {
            Integer result = invoke(new Callable<Integer>() {
                public Integer call() {
                    if (o instanceof Win32ShellFolder2
                        && o1 instanceof Win32ShellFolder2) {
                        // delegates comparison to native method
                        return compareIDsByColumn(shellFolder.getIShellFolder(),
                            ((Win32ShellFolder2) o).getRelativePIDL(),
                            ((Win32ShellFolder2) o1).getRelativePIDL(),
                            columnIdx);
                    }
                    return 0;
                }
            });

            return result == null ? 0 : result;
        }
    }

    // Extracts libraries and their default save locations from Known Folders list
    private static List<KnownFolderDefinition> getLibraries() {
        return invoke(new Callable<List<KnownFolderDefinition>>() {
            @Override
            public List<KnownFolderDefinition> call() throws Exception {
                KnownFolderDefinition[] all = loadKnownFolders();
                List<KnownFolderDefinition> folders = new ArrayList<>();
                if (all != null) {
                    for (KnownFolderDefinition kf : all) {
                        if (kf.relativePath == null || kf.parsingName == null ||
                                kf.saveLocation == null) {
                            continue;
                        }
                        folders.add(kf);
                    }
                }
                return folders;
            }
        });
    }

    static class MultiResolutionIconImage extends AbstractMultiResolutionImage {

        final int baseSize;
        final Image resolutionVariant;

        public MultiResolutionIconImage(int baseSize, Image resolutionVariant) {
            this.baseSize = baseSize;
            this.resolutionVariant = resolutionVariant;
        }

        @Override
        public int getWidth(ImageObserver observer) {
            return baseSize;
        }

        @Override
        public int getHeight(ImageObserver observer) {
            return baseSize;
        }

        @Override
        protected Image getBaseImage() {
            return resolutionVariant;
        }

        @Override
        public Image getResolutionVariant(double width, double height) {
            return resolutionVariant;
        }

        @Override
        public List<Image> getResolutionVariants() {
            return Arrays.asList(resolutionVariant);
        }
    }
}