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