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