1 /*
   2  * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.shell;
  27 
  28 import java.awt.Image;
  29 import java.awt.Toolkit;
  30 import java.awt.image.AbstractMultiResolutionImage;
  31 import java.awt.image.BufferedImage;
  32 import java.awt.image.ImageObserver;
  33 import java.io.File;
  34 import java.io.FileNotFoundException;
  35 import java.io.IOException;
  36 import java.util.*;
  37 import java.util.concurrent.*;
  38 import javax.swing.SwingConstants;
  39 
  40 // NOTE: This class supersedes Win32ShellFolder, which was removed from
  41 //       distribution after version 1.4.2.
  42 
  43 /**
  44  * Win32 Shell Folders
  45  * <P>
  46  * <BR>
  47  * There are two fundamental types of shell folders : file system folders
  48  * and non-file system folders.  File system folders are relatively easy
  49  * to deal with.  Non-file system folders are items such as My Computer,
  50  * Network Neighborhood, and the desktop.  Some of these non-file system
  51  * folders have special values and properties.
  52  * <P>
  53  * <BR>
  54  * Win32 keeps two basic data structures for shell folders.  The first
  55  * of these is called an ITEMIDLIST.  Usually a pointer, called an
  56  * LPITEMIDLIST, or more frequently just "PIDL".  This structure holds
  57  * a series of identifiers and can be either relative to the desktop
  58  * (an absolute PIDL), or relative to the shell folder that contains them.
  59  * Some Win32 functions can take absolute or relative PIDL values, and
  60  * others can only accept relative values.
  61  * <BR>
  62  * The second data structure is an IShellFolder COM interface.  Using
  63  * this interface, one can enumerate the relative PIDLs in a shell
  64  * folder, get attributes, etc.
  65  * <BR>
  66  * All Win32ShellFolder2 objects which are folder types (even non-file
  67  * system folders) contain an IShellFolder object. Files are named in
  68  * directories via relative PIDLs.
  69  *
  70  * @author Michael Martak
  71  * @author Leif Samuelsson
  72  * @author Kenneth Russell
  73  * @since 1.4 */
  74 @SuppressWarnings("serial") // JDK-implementation class
  75 final class Win32ShellFolder2 extends ShellFolder {
  76 
  77     static final int SMALL_ICON_SIZE = 16;
  78     static final int LARGE_ICON_SIZE = 32;
  79 
  80     static final int FILE_ICON_ID = 1;
  81     static final int FOLDER_ICON_ID = 4;
  82 
  83     private static native void initIDs();
  84 
  85     static {
  86         initIDs();
  87     }
  88 
  89     // Win32 Shell Folder Constants
  90     public static final int DESKTOP = 0x0000;
  91     public static final int INTERNET = 0x0001;
  92     public static final int PROGRAMS = 0x0002;
  93     public static final int CONTROLS = 0x0003;
  94     public static final int PRINTERS = 0x0004;
  95     public static final int PERSONAL = 0x0005;
  96     public static final int FAVORITES = 0x0006;
  97     public static final int STARTUP = 0x0007;
  98     public static final int RECENT = 0x0008;
  99     public static final int SENDTO = 0x0009;
 100     public static final int BITBUCKET = 0x000a;
 101     public static final int STARTMENU = 0x000b;
 102     public static final int DESKTOPDIRECTORY = 0x0010;
 103     public static final int DRIVES = 0x0011;
 104     public static final int NETWORK = 0x0012;
 105     public static final int NETHOOD = 0x0013;
 106     public static final int FONTS = 0x0014;
 107     public static final int TEMPLATES = 0x0015;
 108     public static final int COMMON_STARTMENU = 0x0016;
 109     public static final int COMMON_PROGRAMS = 0X0017;
 110     public static final int COMMON_STARTUP = 0x0018;
 111     public static final int COMMON_DESKTOPDIRECTORY = 0x0019;
 112     public static final int APPDATA = 0x001a;
 113     public static final int PRINTHOOD = 0x001b;
 114     public static final int ALTSTARTUP = 0x001d;
 115     public static final int COMMON_ALTSTARTUP = 0x001e;
 116     public static final int COMMON_FAVORITES = 0x001f;
 117     public static final int INTERNET_CACHE = 0x0020;
 118     public static final int COOKIES = 0x0021;
 119     public static final int HISTORY = 0x0022;
 120 
 121     // Win32 shell folder attributes
 122     public static final int ATTRIB_CANCOPY          = 0x00000001;
 123     public static final int ATTRIB_CANMOVE          = 0x00000002;
 124     public static final int ATTRIB_CANLINK          = 0x00000004;
 125     public static final int ATTRIB_CANRENAME        = 0x00000010;
 126     public static final int ATTRIB_CANDELETE        = 0x00000020;
 127     public static final int ATTRIB_HASPROPSHEET     = 0x00000040;
 128     public static final int ATTRIB_DROPTARGET       = 0x00000100;
 129     public static final int ATTRIB_LINK             = 0x00010000;
 130     public static final int ATTRIB_SHARE            = 0x00020000;
 131     public static final int ATTRIB_READONLY         = 0x00040000;
 132     public static final int ATTRIB_GHOSTED          = 0x00080000;
 133     public static final int ATTRIB_HIDDEN           = 0x00080000;
 134     public static final int ATTRIB_FILESYSANCESTOR  = 0x10000000;
 135     public static final int ATTRIB_FOLDER           = 0x20000000;
 136     public static final int ATTRIB_FILESYSTEM       = 0x40000000;
 137     public static final int ATTRIB_HASSUBFOLDER     = 0x80000000;
 138     public static final int ATTRIB_VALIDATE         = 0x01000000;
 139     public static final int ATTRIB_REMOVABLE        = 0x02000000;
 140     public static final int ATTRIB_COMPRESSED       = 0x04000000;
 141     public static final int ATTRIB_BROWSABLE        = 0x08000000;
 142     public static final int ATTRIB_NONENUMERATED    = 0x00100000;
 143     public static final int ATTRIB_NEWCONTENT       = 0x00200000;
 144 
 145     // IShellFolder::GetDisplayNameOf constants
 146     public static final int SHGDN_NORMAL            = 0;
 147     public static final int SHGDN_INFOLDER          = 1;
 148     public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000;
 149     public static final int SHGDN_FORADDRESSBAR     = 0x4000;
 150     public static final int SHGDN_FORPARSING        = 0x8000;
 151 
 152     /** The referent to be registered with the Disposer. */
 153     private Object disposerReferent = new Object();
 154 
 155     // Values for system call LoadIcon()
 156     public enum SystemIcon {
 157         IDI_APPLICATION(32512),
 158         IDI_HAND(32513),
 159         IDI_ERROR(32513),
 160         IDI_QUESTION(32514),
 161         IDI_EXCLAMATION(32515),
 162         IDI_WARNING(32515),
 163         IDI_ASTERISK(32516),
 164         IDI_INFORMATION(32516),
 165         IDI_WINLOGO(32517);
 166 
 167         private final int iconID;
 168 
 169         SystemIcon(int iconID) {
 170             this.iconID = iconID;
 171         }
 172 
 173         public int getIconID() {
 174             return iconID;
 175         }
 176     }
 177 
 178     // Known Folder data
 179     static class KnownFolderDefinition {
 180         String guid;
 181         int category;
 182         String name;
 183         String description;
 184         String parent;
 185         String relativePath;
 186         String parsingName;
 187         String tooltip;
 188         String localizedName;
 189         String icon;
 190         String security;
 191         long attributes;
 192         int defenitionFlags;
 193         String ftidType;
 194         String path;
 195         String saveLocation;
 196         static final List<KnownFolderDefinition> libraries = getLibraries();
 197     }
 198 
 199     static class FolderDisposer implements sun.java2d.DisposerRecord {
 200         /*
 201          * This is cached as a concession to getFolderType(), which needs
 202          * an absolute PIDL.
 203          */
 204         long absolutePIDL;
 205         /*
 206          * We keep track of shell folders through the IShellFolder
 207          * interface of their parents plus their relative PIDL.
 208          */
 209         long pIShellFolder;
 210         long relativePIDL;
 211 
 212         boolean disposed;
 213         public void dispose() {
 214             if (disposed) return;
 215             invoke(new Callable<Void>() {
 216                 public Void call() {
 217                     if (relativePIDL != 0) {
 218                         releasePIDL(relativePIDL);
 219                     }
 220                     if (absolutePIDL != 0) {
 221                         releasePIDL(absolutePIDL);
 222                     }
 223                     if (pIShellFolder != 0) {
 224                         releaseIShellFolder(pIShellFolder);
 225                     }
 226                     return null;
 227                 }
 228             });
 229             disposed = true;
 230         }
 231     }
 232     FolderDisposer disposer = new FolderDisposer();
 233     private void setIShellFolder(long pIShellFolder) {
 234         disposer.pIShellFolder = pIShellFolder;
 235     }
 236     private void setRelativePIDL(long relativePIDL) {
 237         disposer.relativePIDL = relativePIDL;
 238     }
 239     /*
 240      * The following are for caching various shell folder properties.
 241      */
 242     private long pIShellIcon = -1L;
 243     private String folderType = null;
 244     private String displayName = null;
 245     private Image smallIcon = null;
 246     private Image largeIcon = null;
 247     private Boolean isDir = null;
 248     private final boolean isLib;
 249     private static final String FNAME = COLUMN_NAME;
 250     private static final String FSIZE = COLUMN_SIZE;
 251     private static final String FTYPE = "FileChooser.fileTypeHeaderText";
 252     private static final String FDATE = COLUMN_DATE;
 253 
 254     /*
 255      * The following is to identify the My Documents folder as being special
 256      */
 257     private boolean isPersonal;
 258 
 259     private static String composePathForCsidl(int csidl) throws IOException, InterruptedException {
 260         String path = getFileSystemPath(csidl);
 261         return path == null
 262                 ? ("ShellFolder: 0x" + Integer.toHexString(csidl))
 263                 : path;
 264     }
 265 
 266     /**
 267      * Create a system special shell folder, such as the
 268      * desktop or Network Neighborhood.
 269      */
 270     Win32ShellFolder2(final int csidl) throws IOException, InterruptedException {
 271         // Desktop is parent of DRIVES and NETWORK, not necessarily
 272         // other special shell folders.
 273         super(null, composePathForCsidl(csidl));
 274         isLib = false;
 275 
 276         invoke(new Callable<Void>() {
 277             public Void call() throws InterruptedException {
 278                 if (csidl == DESKTOP) {
 279                     initDesktop();
 280                 } else {
 281                     initSpecial(getDesktop().getIShellFolder(), csidl);
 282                     // At this point, the native method initSpecial() has set our relativePIDL
 283                     // relative to the Desktop, which may not be our immediate parent. We need
 284                     // to traverse this ID list and break it into a chain of shell folders from
 285                     // the top, with each one having an immediate parent and a relativePIDL
 286                     // relative to that parent.
 287                     long pIDL = disposer.relativePIDL;
 288                     parent = getDesktop();
 289                     while (pIDL != 0) {
 290                         // Get a child pidl relative to 'parent'
 291                         long childPIDL = copyFirstPIDLEntry(pIDL);
 292                         if (childPIDL != 0) {
 293                             // Get a handle to the rest of the ID list
 294                             // i,e, parent's grandchilren and down
 295                             pIDL = getNextPIDLEntry(pIDL);
 296                             if (pIDL != 0) {
 297                                 // Now we know that parent isn't immediate to 'this' because it
 298                                 // has a continued ID list. Create a shell folder for this child
 299                                 // pidl and make it the new 'parent'.
 300                                 parent = createShellFolder((Win32ShellFolder2) parent, childPIDL);
 301                             } else {
 302                                 // No grandchildren means we have arrived at the parent of 'this',
 303                                 // and childPIDL is directly relative to parent.
 304                                 disposer.relativePIDL = childPIDL;
 305                             }
 306                         } else {
 307                             break;
 308                         }
 309                     }
 310                 }
 311                 return null;
 312             }
 313         }, InterruptedException.class);
 314 
 315         sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer);
 316     }
 317 
 318 
 319     /**
 320      * Create a system shell folder
 321      */
 322     Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path, boolean isLib) {
 323         super(parent, (path != null) ? path : "ShellFolder: ");
 324         this.isLib = isLib;
 325         this.disposer.pIShellFolder = pIShellFolder;
 326         this.disposer.relativePIDL = relativePIDL;
 327         sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer);
 328     }
 329 
 330 
 331     /**
 332      * Creates a shell folder with a parent and relative PIDL
 333      */
 334     static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, long pIDL)
 335             throws InterruptedException {
 336         String path = invoke(new Callable<String>() {
 337             public String call() {
 338                 return getFileSystemPath(parent.getIShellFolder(), pIDL);
 339             }
 340         }, RuntimeException.class);
 341         String libPath = resolveLibrary(path);
 342         if (libPath == null) {
 343             return new Win32ShellFolder2(parent, 0, pIDL, path, false);
 344         } else {
 345             return new Win32ShellFolder2(parent, 0, pIDL, libPath, true);
 346         }
 347     }
 348 
 349     // Initializes the desktop shell folder
 350     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 351     private native void initDesktop();
 352 
 353     // Initializes a special, non-file system shell folder
 354     // from one of the above constants
 355     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 356     private native void initSpecial(long desktopIShellFolder, int csidl);
 357 
 358     /** Marks this folder as being the My Documents (Personal) folder */
 359     public void setIsPersonal() {
 360         isPersonal = true;
 361     }
 362 
 363     /**
 364      * This method is implemented to make sure that no instances
 365      * of {@code ShellFolder} are ever serialized. If {@code isFileSystem()} returns
 366      * {@code true}, then the object is representable with an instance of
 367      * {@code java.io.File} instead. If not, then the object depends
 368      * on native PIDL state and should not be serialized.
 369      *
 370      * @return a {@code java.io.File} replacement object. If the folder
 371      * is a not a normal directory, then returns the first non-removable
 372      * drive (normally "C:\").
 373      */
 374     protected Object writeReplace() throws java.io.ObjectStreamException {
 375         return invoke(new Callable<File>() {
 376             public File call() {
 377                 if (isFileSystem()) {
 378                     return new File(getPath());
 379                 } else {
 380                     Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives();
 381                     if (drives != null) {
 382                         File[] driveRoots = drives.listFiles();
 383                         if (driveRoots != null) {
 384                             for (int i = 0; i < driveRoots.length; i++) {
 385                                 if (driveRoots[i] instanceof Win32ShellFolder2) {
 386                                     Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i];
 387                                     if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) {
 388                                         return new File(sf.getPath());
 389                                     }
 390                                 }
 391                             }
 392                         }
 393                     }
 394                     // Ouch, we have no hard drives. Return something "valid" anyway.
 395                     return new File("C:\\");
 396                 }
 397             }
 398         });
 399     }
 400 
 401 
 402     /**
 403      * Finalizer to clean up any COM objects or PIDLs used by this object.
 404      */
 405     protected void dispose() {
 406         disposer.dispose();
 407     }
 408 
 409 
 410     // Given a (possibly multi-level) relative PIDL (with respect to
 411     // the desktop, at least in all of the usage cases in this code),
 412     // return a pointer to the next entry. Does not mutate the PIDL in
 413     // any way. Returns 0 if the null terminator is reached.
 414     // Needs to be accessible to Win32ShellFolderManager2
 415     static native long getNextPIDLEntry(long pIDL);
 416 
 417     // Given a (possibly multi-level) relative PIDL (with respect to
 418     // the desktop, at least in all of the usage cases in this code),
 419     // copy the first entry into a newly-allocated PIDL. Returns 0 if
 420     // the PIDL is at the end of the list.
 421     // Needs to be accessible to Win32ShellFolderManager2
 422     static native long copyFirstPIDLEntry(long pIDL);
 423 
 424     // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL
 425     private static native long combinePIDLs(long ppIDL, long pIDL);
 426 
 427     // Release a PIDL object
 428     // Needs to be accessible to Win32ShellFolderManager2
 429     static native void releasePIDL(long pIDL);
 430 
 431     // Release an IShellFolder object
 432     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 433     private static native void releaseIShellFolder(long pIShellFolder);
 434 
 435     /**
 436      * Accessor for IShellFolder
 437      */
 438     private long getIShellFolder() {
 439         if (disposer.pIShellFolder == 0) {
 440             try {
 441                 disposer.pIShellFolder = invoke(new Callable<Long>() {
 442                     public Long call() {
 443                         assert(isDirectory());
 444                         assert(parent != null);
 445                         long parentIShellFolder = getParentIShellFolder();
 446                         if (parentIShellFolder == 0) {
 447                             throw new InternalError("Parent IShellFolder was null for "
 448                                     + getAbsolutePath());
 449                         }
 450                         // We are a directory with a parent and a relative PIDL.
 451                         // We want to bind to the parent so we get an
 452                         // IShellFolder instance associated with us.
 453                         long pIShellFolder = bindToObject(parentIShellFolder,
 454                                 disposer.relativePIDL);
 455                         if (pIShellFolder == 0) {
 456                             throw new InternalError("Unable to bind "
 457                                     + getAbsolutePath() + " to parent");
 458                         }
 459                         return pIShellFolder;
 460                     }
 461                 }, RuntimeException.class);
 462             } catch (InterruptedException e) {
 463                 // Ignore error
 464             }
 465         }
 466         return disposer.pIShellFolder;
 467     }
 468 
 469     /**
 470      * Get the parent ShellFolder's IShellFolder interface
 471      */
 472     public long getParentIShellFolder() {
 473         Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile();
 474         if (parent == null) {
 475             // Parent should only be null if this is the desktop, whose
 476             // relativePIDL is relative to its own IShellFolder.
 477             return getIShellFolder();
 478         }
 479         return parent.getIShellFolder();
 480     }
 481 
 482     /**
 483      * Accessor for relative PIDL
 484      */
 485     public long getRelativePIDL() {
 486         if (disposer.relativePIDL == 0) {
 487             throw new InternalError("Should always have a relative PIDL");
 488         }
 489         return disposer.relativePIDL;
 490     }
 491 
 492     private long getAbsolutePIDL() {
 493         if (parent == null) {
 494             // This is the desktop
 495             return getRelativePIDL();
 496         } else {
 497             if (disposer.absolutePIDL == 0) {
 498                 disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL());
 499             }
 500 
 501             return disposer.absolutePIDL;
 502         }
 503     }
 504 
 505     /**
 506      * Helper function to return the desktop
 507      */
 508     public Win32ShellFolder2 getDesktop() {
 509         return Win32ShellFolderManager2.getDesktop();
 510     }
 511 
 512     /**
 513      * Helper function to return the desktop IShellFolder interface
 514      */
 515     public long getDesktopIShellFolder() {
 516         return getDesktop().getIShellFolder();
 517     }
 518 
 519     private static boolean pathsEqual(String path1, String path2) {
 520         // Same effective implementation as Win32FileSystem
 521         return path1.equalsIgnoreCase(path2);
 522     }
 523 
 524     /**
 525      * Check to see if two ShellFolder objects are the same
 526      */
 527     public boolean equals(Object o) {
 528         if (o == null || !(o instanceof Win32ShellFolder2)) {
 529             // Short-circuit circuitous delegation path
 530             if (!(o instanceof File)) {
 531                 return super.equals(o);
 532             }
 533             return pathsEqual(getPath(), ((File) o).getPath());
 534         }
 535         Win32ShellFolder2 rhs = (Win32ShellFolder2) o;
 536         if ((parent == null && rhs.parent != null) ||
 537             (parent != null && rhs.parent == null)) {
 538             return false;
 539         }
 540 
 541         if (isFileSystem() && rhs.isFileSystem()) {
 542             // Only folders with identical parents can be equal
 543             return (pathsEqual(getPath(), rhs.getPath()) &&
 544                     (parent == rhs.parent || parent.equals(rhs.parent)));
 545         }
 546 
 547         if (parent == rhs.parent || parent.equals(rhs.parent)) {
 548             try {
 549                 return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL);
 550             } catch (InterruptedException e) {
 551                 return false;
 552             }
 553         }
 554 
 555         return false;
 556     }
 557 
 558     private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2)
 559             throws InterruptedException {
 560         return invoke(new Callable<Boolean>() {
 561             public Boolean call() {
 562                 return compareIDs(pIShellFolder, pidl1, pidl2) == 0;
 563             }
 564         }, RuntimeException.class);
 565     }
 566 
 567     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 568     private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2);
 569 
 570     private volatile Boolean cachedIsFileSystem;
 571 
 572     /**
 573      * @return Whether this is a file system shell folder
 574      */
 575     public boolean isFileSystem() {
 576         if (cachedIsFileSystem == null) {
 577             cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM);
 578         }
 579 
 580         return cachedIsFileSystem;
 581     }
 582 
 583     /**
 584      * Return whether the given attribute flag is set for this object
 585      */
 586     public boolean hasAttribute(final int attribute) {
 587         Boolean result = invoke(new Callable<Boolean>() {
 588             public Boolean call() {
 589                 // Caching at this point doesn't seem to be cost efficient
 590                 return (getAttributes0(getParentIShellFolder(),
 591                     getRelativePIDL(), attribute)
 592                     & attribute) != 0;
 593             }
 594         });
 595 
 596         return result != null && result;
 597     }
 598 
 599     /**
 600      * Returns the queried attributes specified in attrsMask.
 601      *
 602      * Could plausibly be used for attribute caching but have to be
 603      * very careful not to touch network drives and file system roots
 604      * with a full attrsMask
 605      * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 606      */
 607 
 608     private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask);
 609 
 610     // Return the path to the underlying file system object
 611     // Should be called from the COM thread
 612     private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) {
 613         int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER;
 614         if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() &&
 615                 getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) {
 616 
 617             String s =
 618                     getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(),
 619                             getLinkLocation(parentIShellFolder, relativePIDL, false));
 620             if (s != null && s.startsWith("\\\\")) {
 621                 return s;
 622             }
 623         }
 624         String path = getDisplayNameOf(parentIShellFolder, relativePIDL,
 625                         SHGDN_FORPARSING);
 626         return path;
 627     }
 628 
 629     private static String resolveLibrary(String path) {
 630         // if this is a library its default save location is taken as a path
 631         // this is a temp fix until java.io starts support Libraries
 632         if( path != null && path.startsWith("::{") &&
 633                 path.toLowerCase().endsWith(".library-ms")) {
 634             for (KnownFolderDefinition kf : KnownFolderDefinition.libraries) {
 635                 if (path.toLowerCase().endsWith(
 636                         "\\" + kf.relativePath.toLowerCase()) &&
 637                         path.toUpperCase().startsWith(
 638                         kf.parsingName.substring(0, 40).toUpperCase())) {
 639                     return kf.saveLocation;
 640                 }
 641             }
 642         }
 643         return null;
 644     }
 645 
 646     // Needs to be accessible to Win32ShellFolderManager2
 647     static String getFileSystemPath(final int csidl) throws IOException, InterruptedException {
 648         String path = invoke(new Callable<String>() {
 649             public String call() throws IOException {
 650                 return getFileSystemPath0(csidl);
 651             }
 652         }, IOException.class);
 653         if (path != null) {
 654             SecurityManager security = System.getSecurityManager();
 655             if (security != null) {
 656                 security.checkRead(path);
 657             }
 658         }
 659         return path;
 660     }
 661 
 662     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 663     private static native String getFileSystemPath0(int csidl) throws IOException;
 664 
 665     // Return whether the path is a network root.
 666     // Path is assumed to be non-null
 667     private static boolean isNetworkRoot(String path) {
 668         return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/"));
 669     }
 670 
 671     /**
 672      * @return The parent shell folder of this shell folder, null if
 673      * there is no parent
 674      */
 675     public File getParentFile() {
 676         return parent;
 677     }
 678 
 679     public boolean isDirectory() {
 680         if (isDir == null) {
 681             // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are
 682             // not traversable in JFileChooser.
 683             if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) {
 684                 isDir = Boolean.TRUE;
 685             } else if (isLink()) {
 686                 ShellFolder linkLocation = getLinkLocation(false);
 687                 isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory());
 688             } else {
 689                 isDir = Boolean.FALSE;
 690             }
 691         }
 692         return isDir.booleanValue();
 693     }
 694 
 695     /*
 696      * Functions for enumerating an IShellFolder's children
 697      */
 698     // Returns an IEnumIDList interface for an IShellFolder.  The value
 699     // returned must be released using releaseEnumObjects().
 700     private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException {
 701         return invoke(new Callable<Long>() {
 702             public Long call() {
 703                 boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder();
 704 
 705                 return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles);
 706             }
 707         }, RuntimeException.class);
 708     }
 709 
 710     // Returns an IEnumIDList interface for an IShellFolder.  The value
 711     // returned must be released using releaseEnumObjects().
 712     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 713     private native long getEnumObjects(long pIShellFolder, boolean isDesktop,
 714                                        boolean includeHiddenFiles);
 715     // Returns the next sequential child as a relative PIDL
 716     // from an IEnumIDList interface.  The value returned must
 717     // be released using releasePIDL().
 718     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 719     private native long getNextChild(long pEnumObjects);
 720     // Releases the IEnumIDList interface
 721     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 722     private native void releaseEnumObjects(long pEnumObjects);
 723 
 724     // Returns the IShellFolder of a child from a parent IShellFolder
 725     // and a relative PIDL.  The value returned must be released
 726     // using releaseIShellFolder().
 727     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 728     private static native long bindToObject(long parentIShellFolder, long pIDL);
 729 
 730     /**
 731      * @return An array of shell folders that are children of this shell folder
 732      *         object. The array will be empty if the folder is empty.  Returns
 733      *         {@code null} if this shellfolder does not denote a directory.
 734      */
 735     public File[] listFiles(final boolean includeHiddenFiles) {
 736         SecurityManager security = System.getSecurityManager();
 737         if (security != null) {
 738             security.checkRead(getPath());
 739         }
 740 
 741         try {
 742             return invoke(new Callable<File[]>() {
 743                 public File[] call() throws InterruptedException {
 744                     if (!isDirectory()) {
 745                         return null;
 746                     }
 747                     // Links to directories are not directories and cannot be parents.
 748                     // This does not apply to folders in My Network Places (NetHood)
 749                     // because they are both links and real directories!
 750                     if (isLink() && !hasAttribute(ATTRIB_FOLDER)) {
 751                         return new File[0];
 752                     }
 753 
 754                     Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop();
 755                     Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal();
 756 
 757                     // If we are a directory, we have a parent and (at least) a
 758                     // relative PIDL. We must first ensure we are bound to the
 759                     // parent so we have an IShellFolder to query.
 760                     long pIShellFolder = getIShellFolder();
 761                     // Now we can enumerate the objects in this folder.
 762                     ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>();
 763                     long pEnumObjects = getEnumObjects(includeHiddenFiles);
 764                     if (pEnumObjects != 0) {
 765                         try {
 766                             long childPIDL;
 767                             int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR;
 768                             do {
 769                                 childPIDL = getNextChild(pEnumObjects);
 770                                 boolean releasePIDL = true;
 771                                 if (childPIDL != 0 &&
 772                                         (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) {
 773                                     Win32ShellFolder2 childFolder;
 774                                     if (Win32ShellFolder2.this.equals(desktop)
 775                                             && personal != null
 776                                             && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) {
 777                                         childFolder = personal;
 778                                     } else {
 779                                         childFolder = createShellFolder(Win32ShellFolder2.this, childPIDL);
 780                                         releasePIDL = false;
 781                                     }
 782                                     list.add(childFolder);
 783                                 }
 784                                 if (releasePIDL) {
 785                                     releasePIDL(childPIDL);
 786                                 }
 787                             } while (childPIDL != 0 && !Thread.currentThread().isInterrupted());
 788                         } finally {
 789                             releaseEnumObjects(pEnumObjects);
 790                         }
 791                     }
 792                     return Thread.currentThread().isInterrupted()
 793                         ? new File[0]
 794                         : list.toArray(new ShellFolder[list.size()]);
 795                 }
 796             }, InterruptedException.class);
 797         } catch (InterruptedException e) {
 798             return new File[0];
 799         }
 800     }
 801 
 802 
 803     /**
 804      * Look for (possibly special) child folder by it's path
 805      *
 806      * @return The child shellfolder, or null if not found.
 807      */
 808     Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
 809         return invoke(new Callable<Win32ShellFolder2>() {
 810             public Win32ShellFolder2 call() throws InterruptedException {
 811                 long pIShellFolder = getIShellFolder();
 812                 long pEnumObjects = getEnumObjects(true);
 813                 Win32ShellFolder2 child = null;
 814                 long childPIDL;
 815 
 816                 while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
 817                     if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
 818                         String path = getFileSystemPath(pIShellFolder, childPIDL);
 819                         if(isLib) path = resolveLibrary( path );
 820                         if (path != null && path.equalsIgnoreCase(filePath)) {
 821                             long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
 822                             child = new Win32ShellFolder2(Win32ShellFolder2.this,
 823                                     childIShellFolder, childPIDL, path, isLib);
 824                             break;
 825                         }
 826                     }
 827                     releasePIDL(childPIDL);
 828                 }
 829                 releaseEnumObjects(pEnumObjects);
 830                 return child;
 831             }
 832         }, InterruptedException.class);
 833     }
 834 
 835     private volatile Boolean cachedIsLink;
 836 
 837     /**
 838      * @return Whether this shell folder is a link
 839      */
 840     public boolean isLink() {
 841         if (cachedIsLink == null) {
 842             cachedIsLink = hasAttribute(ATTRIB_LINK);
 843         }
 844 
 845         return cachedIsLink;
 846     }
 847 
 848     /**
 849      * @return Whether this shell folder is marked as hidden
 850      */
 851     public boolean isHidden() {
 852         return hasAttribute(ATTRIB_HIDDEN);
 853     }
 854 
 855 
 856     // Return the link location of a shell folder
 857     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 858     private static native long getLinkLocation(long parentIShellFolder,
 859                                         long relativePIDL, boolean resolve);
 860 
 861     /**
 862      * @return The shell folder linked to by this shell folder, or null
 863      * if this shell folder is not a link or is a broken or invalid link
 864      */
 865     public ShellFolder getLinkLocation()  {
 866         return getLinkLocation(true);
 867     }
 868 
 869     private Win32ShellFolder2 getLinkLocation(final boolean resolve) {
 870         return invoke(new Callable<Win32ShellFolder2>() {
 871             public Win32ShellFolder2 call() {
 872                 if (!isLink()) {
 873                     return null;
 874                 }
 875 
 876                 Win32ShellFolder2 location = null;
 877                 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
 878                         getRelativePIDL(), resolve);
 879                 if (linkLocationPIDL != 0) {
 880                     try {
 881                         location =
 882                                 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
 883                                         linkLocationPIDL);
 884                     } catch (InterruptedException e) {
 885                         // Return null
 886                     } catch (InternalError e) {
 887                         // Could be a link to a non-bindable object, such as a network connection
 888                         // TODO: getIShellFolder() should throw FileNotFoundException instead
 889                     }
 890                 }
 891                 return location;
 892             }
 893         });
 894     }
 895 
 896     // Parse a display name into a PIDL relative to the current IShellFolder.
 897     long parseDisplayName(final String name) throws IOException, InterruptedException {
 898         return invoke(new Callable<Long>() {
 899             public Long call() throws IOException {
 900                 return parseDisplayName0(getIShellFolder(), name);
 901             }
 902         }, IOException.class);
 903     }
 904 
 905     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 906     private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;
 907 
 908     // Return the display name of a shell folder
 909     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 910     private static native String getDisplayNameOf(long parentIShellFolder,
 911                                                   long relativePIDL,
 912                                                   int attrs);
 913 
 914     // Returns data of all Known Folders registered in the system
 915     private static native KnownFolderDefinition[] loadKnownFolders();
 916 
 917     /**
 918      * @return The name used to display this shell folder
 919      */
 920     public String getDisplayName() {
 921         if (displayName == null) {
 922             displayName =
 923                 invoke(new Callable<String>() {
 924                     public String call() {
 925                         return getDisplayNameOf(getParentIShellFolder(),
 926                                 getRelativePIDL(), SHGDN_NORMAL);
 927                     }
 928                 });
 929         }
 930         return displayName;
 931     }
 932 
 933     // Return the folder type of a shell folder
 934     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 935     private static native String getFolderType(long pIDL);
 936 
 937     /**
 938      * @return The type of shell folder as a string
 939      */
 940     public String getFolderType() {
 941         if (folderType == null) {
 942             final long absolutePIDL = getAbsolutePIDL();
 943             folderType =
 944                 invoke(new Callable<String>() {
 945                     public String call() {
 946                         return getFolderType(absolutePIDL);
 947                     }
 948                 });
 949         }
 950         return folderType;
 951     }
 952 
 953     // Return the executable type of a file system shell folder
 954     private native String getExecutableType(String path);
 955 
 956     /**
 957      * @return The executable type as a string
 958      */
 959     public String getExecutableType() {
 960         if (!isFileSystem()) {
 961             return null;
 962         }
 963         return getExecutableType(getAbsolutePath());
 964     }
 965 
 966 
 967 
 968     // Icons
 969 
 970     private static Map<Integer, Image> smallSystemImages = new HashMap<>();
 971     private static Map<Integer, Image> largeSystemImages = new HashMap<>();
 972     private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>();
 973     private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>();
 974 
 975     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 976     private static native long getIShellIcon(long pIShellFolder);
 977 
 978     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 979     private static native int getIconIndex(long parentIShellIcon, long relativePIDL);
 980 
 981     // Return the icon of a file system shell folder in the form of an HICON
 982     private static native long getIcon(String absolutePath, boolean getLargeIcon);
 983 
 984     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 985     private static native long extractIcon(long parentIShellFolder, long relativePIDL,
 986                                            int size, boolean getDefaultIcon);
 987 
 988     // Returns an icon from the Windows system icon list in the form of an HICON
 989     private static native long getSystemIcon(int iconID);
 990     private static native long getIconResource(String libName, int iconID,
 991                                                int cxDesired, int cyDesired,
 992                                                boolean useVGAColors);
 993                                                // Note: useVGAColors is ignored on XP and later
 994 
 995     // Return the bits from an HICON.  This has a side effect of setting
 996     // the imageHash variable for efficient caching / comparing.
 997     private static native int[] getIconBits(long hIcon);
 998     // Dispose the HICON
 999     private static native void disposeIcon(long hIcon);
1000 
1001     // Get buttons from native toolbar implementation.
1002     static native int[] getStandardViewButton0(int iconIndex, boolean small);
1003 
1004     // Should be called from the COM thread
1005     private long getIShellIcon() {
1006         if (pIShellIcon == -1L) {
1007             pIShellIcon = getIShellIcon(getIShellFolder());
1008         }
1009 
1010         return pIShellIcon;
1011     }
1012 
1013     private static Image makeIcon(long hIcon, int size) {
1014         if (hIcon != 0L && hIcon != -1L) {
1015             // Get the bits.  This has the side effect of setting the imageHash value for this object.
1016             final int[] iconBits = getIconBits(hIcon);
1017             if (iconBits != null) {
1018                 // icons are always square
1019                 final int iconSize = (int) Math.sqrt(iconBits.length);
1020                 final BufferedImage img =
1021                         new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
1022                 img.setRGB(0, 0, iconSize, iconSize, iconBits, 0, iconSize);
1023                 return iconSize == size
1024                         ? img
1025                         : new MultiResolutionIconImage(size, img);
1026             }
1027         }
1028         return null;
1029     }
1030 
1031 
1032     /**
1033      * @return The icon image used to display this shell folder
1034      */
1035     public Image getIcon(final boolean getLargeIcon) {
1036         Image icon = getLargeIcon ? largeIcon : smallIcon;
1037         int size = getLargeIcon ? LARGE_ICON_SIZE : SMALL_ICON_SIZE;
1038         if (icon == null) {
1039             icon =
1040                 invoke(new Callable<Image>() {
1041                     public Image call() {
1042                         Image newIcon = null;
1043                         if (isLink()) {
1044                             Win32ShellFolder2 folder = getLinkLocation(false);
1045                             if (folder != null && folder.isLibrary()) {
1046                                 return folder.getIcon(getLargeIcon);
1047                             }
1048                         }
1049                         if (isFileSystem() || isLibrary()) {
1050                             long parentIShellIcon = (parent != null)
1051                                 ? ((Win32ShellFolder2) parent).getIShellIcon()
1052                                 : 0L;
1053                             long relativePIDL = getRelativePIDL();
1054 
1055                             // These are cached per type (using the index in the system image list)
1056                             int index = getIconIndex(parentIShellIcon, relativePIDL);
1057                             if (index > 0) {
1058                                 Map<Integer, Image> imageCache;
1059                                 if (isLink()) {
1060                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
1061                                 } else {
1062                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
1063                                 }
1064                                 newIcon = imageCache.get(Integer.valueOf(index));
1065                                 if (newIcon == null) {
1066                                     long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
1067                                     newIcon = makeIcon(hIcon, size);
1068                                     disposeIcon(hIcon);
1069                                     if (newIcon != null) {
1070                                         imageCache.put(Integer.valueOf(index), newIcon);
1071                                     }
1072                                 }
1073                             }
1074                         }
1075 
1076                         if (newIcon == null) {
1077                             // These are only cached per object
1078                             long hIcon = extractIcon(getParentIShellFolder(),
1079                                     getRelativePIDL(), size, false);
1080                             // E_PENDING: loading can take time so get the default
1081                             if(hIcon <= 0) {
1082                                 hIcon = extractIcon(getParentIShellFolder(),
1083                                          getRelativePIDL(), size, true);
1084                                 if(hIcon <= 0) {
1085                                     if (isDirectory()) {
1086                                         return getShell32Icon(FOLDER_ICON_ID, size);
1087                                     } else {
1088                                         return getShell32Icon(FILE_ICON_ID, size);
1089                                     }
1090                                 }
1091                             }
1092                             newIcon = makeIcon(hIcon, size);
1093                             disposeIcon(hIcon);
1094                         }
1095 
1096                         if (newIcon == null) {
1097                             newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
1098                         }
1099                         return newIcon;
1100                     }
1101                 });
1102         }
1103         return icon;
1104     }
1105 
1106     public Image getIcon(int size) {
1107         return invoke(() -> {
1108             Image newIcon = null;
1109             if (isLink()) {
1110                 Win32ShellFolder2 folder = getLinkLocation(false);
1111                 if (folder != null && folder.isLibrary()) {
1112                     return folder.getIcon(size);
1113                 }
1114             }
1115             long hIcon = extractIcon(getParentIShellFolder(),
1116                     getRelativePIDL(), size, false);
1117 
1118             // E_PENDING: loading can take time so get the default
1119             if (hIcon <= 0) {
1120                 hIcon = extractIcon(getParentIShellFolder(),
1121                         getRelativePIDL(), size, true);
1122                 if (hIcon <= 0) {
1123                     if (isDirectory()) {
1124                         return getShell32Icon(FOLDER_ICON_ID, size);
1125                     } else {
1126                         return getShell32Icon(FILE_ICON_ID, size);
1127                     }
1128                 }
1129             }
1130             newIcon = makeIcon(hIcon, size);
1131             disposeIcon(hIcon);
1132             return newIcon;
1133         });
1134     }
1135 
1136     /**
1137      * Gets an icon from the Windows system icon list as an {@code Image}
1138      */
1139     static Image getSystemIcon(SystemIcon iconType) {
1140         long hIcon = getSystemIcon(iconType.getIconID());
1141         Image icon = makeIcon(hIcon, LARGE_ICON_SIZE);
1142         disposeIcon(hIcon);
1143         return icon;
1144     }
1145 
1146     /**
1147      * Gets an icon from the Windows system icon list as an {@code Image}
1148      */
1149     static Image getShell32Icon(int iconID, int size) {
1150         boolean useVGAColors = true; // Will be ignored on XP and later
1151         Toolkit toolkit = Toolkit.getDefaultToolkit();
1152         String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
1153         if (shellIconBPP != null) {
1154             useVGAColors = shellIconBPP.equals("4");
1155         }
1156 
1157         long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
1158         if (hIcon != 0) {
1159             Image icon = makeIcon(hIcon, size);
1160             disposeIcon(hIcon);
1161             return icon;
1162         }
1163         return null;
1164     }
1165 
1166     /**
1167      * Returns the canonical form of this abstract pathname.  Equivalent to
1168      * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
1169      *
1170      * @see java.io.File#getCanonicalFile
1171      */
1172     public File getCanonicalFile() throws IOException {
1173         return this;
1174     }
1175 
1176     /*
1177      * Indicates whether this is a special folder (includes My Documents)
1178      */
1179     public boolean isSpecial() {
1180         return isPersonal || !isFileSystem() || (this == getDesktop());
1181     }
1182 
1183     /**
1184      * Compares this object with the specified object for order.
1185      *
1186      * @see sun.awt.shell.ShellFolder#compareTo(File)
1187      */
1188     public int compareTo(File file2) {
1189         if (!(file2 instanceof Win32ShellFolder2)) {
1190             if (isFileSystem() && !isSpecial()) {
1191                 return super.compareTo(file2);
1192             } else {
1193                 return -1; // Non-file shellfolders sort before files
1194             }
1195         }
1196         return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
1197     }
1198 
1199     // native constants from commctrl.h
1200     private static final int LVCFMT_LEFT = 0;
1201     private static final int LVCFMT_RIGHT = 1;
1202     private static final int LVCFMT_CENTER = 2;
1203 
1204     public ShellFolderColumnInfo[] getFolderColumns() {
1205         ShellFolder library = resolveLibrary();
1206         if (library != null) return library.getFolderColumns();
1207         return invoke(new Callable<ShellFolderColumnInfo[]>() {
1208             public ShellFolderColumnInfo[] call() {
1209                 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());
1210 
1211                 if (columns != null) {
1212                     List<ShellFolderColumnInfo> notNullColumns =
1213                             new ArrayList<ShellFolderColumnInfo>();
1214                     for (int i = 0; i < columns.length; i++) {
1215                         ShellFolderColumnInfo column = columns[i];
1216                         if (column != null) {
1217                             column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
1218                                     ? SwingConstants.RIGHT
1219                                     : column.getAlignment() == LVCFMT_CENTER
1220                                     ? SwingConstants.CENTER
1221                                     : SwingConstants.LEADING);
1222 
1223                             column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));
1224 
1225                             notNullColumns.add(column);
1226                         }
1227                     }
1228                     columns = new ShellFolderColumnInfo[notNullColumns.size()];
1229                     notNullColumns.toArray(columns);
1230                 }
1231                 return columns;
1232             }
1233         });
1234     }
1235 
1236     public Object getFolderColumnValue(final int column) {
1237         if(!isLibrary()) {
1238             ShellFolder library = resolveLibrary();
1239             if (library != null) return library.getFolderColumnValue(column);
1240         }
1241         return invoke(new Callable<Object>() {
1242             public Object call() {
1243                 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
1244             }
1245         });
1246     }
1247 
1248     boolean isLibrary() {
1249         return isLib;
1250     }
1251 
1252     private ShellFolder resolveLibrary() {
1253         for (ShellFolder f = this; f != null; f = f.parent) {
1254             if (!f.isFileSystem()) {
1255                 if (f instanceof Win32ShellFolder2 &&
1256                                            ((Win32ShellFolder2)f).isLibrary()) {
1257                     try {
1258                         return getShellFolder(new File(getPath()));
1259                     } catch (FileNotFoundException e) {
1260                     }
1261                 }
1262                 break;
1263             }
1264         }
1265         return null;
1266     }
1267 
1268     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1269     private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);
1270 
1271     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1272     private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);
1273 
1274     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1275     private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
1276 
1277 
1278     public void sortChildren(final List<? extends File> files) {
1279         // To avoid loads of synchronizations with Invoker and improve performance we
1280         // synchronize the whole code of the sort method once
1281         invoke(new Callable<Void>() {
1282             public Void call() {
1283                 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));
1284 
1285                 return null;
1286             }
1287         });
1288     }
1289 
1290     private static class ColumnComparator implements Comparator<File> {
1291         private final Win32ShellFolder2 shellFolder;
1292 
1293         private final int columnIdx;
1294 
1295         public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
1296             this.shellFolder = shellFolder;
1297             this.columnIdx = columnIdx;
1298         }
1299 
1300         // compares 2 objects within this folder by the specified column
1301         public int compare(final File o, final File o1) {
1302             Integer result = invoke(new Callable<Integer>() {
1303                 public Integer call() {
1304                     if (o instanceof Win32ShellFolder2
1305                         && o1 instanceof Win32ShellFolder2) {
1306                         // delegates comparison to native method
1307                         return compareIDsByColumn(shellFolder.getIShellFolder(),
1308                             ((Win32ShellFolder2) o).getRelativePIDL(),
1309                             ((Win32ShellFolder2) o1).getRelativePIDL(),
1310                             columnIdx);
1311                     }
1312                     return 0;
1313                 }
1314             });
1315 
1316             return result == null ? 0 : result;
1317         }
1318     }
1319 
1320     // Extracts libraries and their default save locations from Known Folders list
1321     private static List<KnownFolderDefinition> getLibraries() {
1322         return invoke(new Callable<List<KnownFolderDefinition>>() {
1323             @Override
1324             public List<KnownFolderDefinition> call() throws Exception {
1325                 KnownFolderDefinition[] all = loadKnownFolders();
1326                 List<KnownFolderDefinition> folders = new ArrayList<>();
1327                 if (all != null) {
1328                     for (KnownFolderDefinition kf : all) {
1329                         if (kf.relativePath == null || kf.parsingName == null ||
1330                                 kf.saveLocation == null) {
1331                             continue;
1332                         }
1333                         folders.add(kf);
1334                     }
1335                 }
1336                 return folders;
1337             }
1338         });
1339     }
1340 
1341     static class MultiResolutionIconImage extends AbstractMultiResolutionImage {
1342 
1343         final int baseSize;
1344         final Image resolutionVariant;
1345 
1346         public MultiResolutionIconImage(int baseSize, Image resolutionVariant) {
1347             this.baseSize = baseSize;
1348             this.resolutionVariant = resolutionVariant;
1349         }
1350 
1351         @Override
1352         public int getWidth(ImageObserver observer) {
1353             return baseSize;
1354         }
1355 
1356         @Override
1357         public int getHeight(ImageObserver observer) {
1358             return baseSize;
1359         }
1360 
1361         @Override
1362         protected Image getBaseImage() {
1363             return resolutionVariant;
1364         }
1365 
1366         @Override
1367         public Image getResolutionVariant(double width, double height) {
1368             return resolutionVariant;
1369         }
1370 
1371         @Override
1372         public List<Image> getResolutionVariants() {
1373             return Arrays.asList(resolutionVariant);
1374         }
1375     }
1376 }