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 ShellFolder getLinkLocation(final boolean resolve) {
 855         return invoke(new Callable<ShellFolder>() {
 856             public ShellFolder call() {
 857                 if (!isLink()) {
 858                     return null;
 859                 }
 860 
 861                 ShellFolder 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);
 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 (isFileSystem()) {
1023                             long parentIShellIcon = (parent != null)
1024                                 ? ((Win32ShellFolder2) parent).getIShellIcon()
1025                                 : 0L;
1026                             long relativePIDL = getRelativePIDL();
1027 
1028                             // These are cached per type (using the index in the system image list)
1029                             int index = getIconIndex(parentIShellIcon, relativePIDL);
1030                             if (index > 0) {
1031                                 Map<Integer, Image> imageCache;
1032                                 if (isLink()) {
1033                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
1034                                 } else {
1035                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
1036                                 }
1037                                 newIcon = imageCache.get(Integer.valueOf(index));
1038                                 if (newIcon == null) {
1039                                     long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
1040                                     newIcon = makeIcon(hIcon, getLargeIcon);
1041                                     disposeIcon(hIcon);
1042                                     if (newIcon != null) {
1043                                         imageCache.put(Integer.valueOf(index), newIcon);
1044                                     }
1045                                 }
1046                             }
1047                         }
1048 
1049                         if (newIcon == null) {
1050                             // These are only cached per object
1051                             long hIcon = extractIcon(getParentIShellFolder(),
1052                                 getRelativePIDL(), getLargeIcon);
1053                             newIcon = makeIcon(hIcon, getLargeIcon);
1054                             disposeIcon(hIcon);
1055                         }
1056 
1057                         if (newIcon == null) {
1058                             newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
1059                         }
1060                         return newIcon;
1061                     }
1062                 });
1063             if (getLargeIcon) {
1064                 largeIcon = icon;
1065             } else {
1066                 smallIcon = icon;
1067             }
1068         }
1069         return icon;
1070     }
1071 
1072     /**
1073      * Gets an icon from the Windows system icon list as an <code>Image</code>
1074      */
1075     static Image getSystemIcon(SystemIcon iconType) {
1076         long hIcon = getSystemIcon(iconType.getIconID());
1077         Image icon = makeIcon(hIcon, true);
1078         disposeIcon(hIcon);
1079         return icon;
1080     }
1081 
1082     /**
1083      * Gets an icon from the Windows system icon list as an <code>Image</code>
1084      */
1085     static Image getShell32Icon(int iconID, boolean getLargeIcon) {
1086         boolean useVGAColors = true; // Will be ignored on XP and later
1087 
1088         int size = getLargeIcon ? 32 : 16;
1089 
1090         Toolkit toolkit = Toolkit.getDefaultToolkit();
1091         String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
1092         if (shellIconBPP != null) {
1093             useVGAColors = shellIconBPP.equals("4");
1094         }
1095 
1096         long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
1097         if (hIcon != 0) {
1098             Image icon = makeIcon(hIcon, getLargeIcon);
1099             disposeIcon(hIcon);
1100             return icon;
1101         }
1102         return null;
1103     }
1104 
1105     /**
1106      * Returns the canonical form of this abstract pathname.  Equivalent to
1107      * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
1108      *
1109      * @see java.io.File#getCanonicalFile
1110      */
1111     public File getCanonicalFile() throws IOException {
1112         return this;
1113     }
1114 
1115     /*
1116      * Indicates whether this is a special folder (includes My Documents)
1117      */
1118     public boolean isSpecial() {
1119         return isPersonal || !isFileSystem() || (this == getDesktop());
1120     }
1121 
1122     /**
1123      * Compares this object with the specified object for order.
1124      *
1125      * @see sun.awt.shell.ShellFolder#compareTo(File)
1126      */
1127     public int compareTo(File file2) {
1128         if (!(file2 instanceof Win32ShellFolder2)) {
1129             if (isFileSystem() && !isSpecial()) {
1130                 return super.compareTo(file2);
1131             } else {
1132                 return -1; // Non-file shellfolders sort before files
1133             }
1134         }
1135         return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
1136     }
1137 
1138     // native constants from commctrl.h
1139     private static final int LVCFMT_LEFT = 0;
1140     private static final int LVCFMT_RIGHT = 1;
1141     private static final int LVCFMT_CENTER = 2;
1142 
1143     public ShellFolderColumnInfo[] getFolderColumns() {
1144         ShellFolder library = resolveLibrary();
1145         if (library != null) return library.getFolderColumns();
1146         return invoke(new Callable<ShellFolderColumnInfo[]>() {
1147             public ShellFolderColumnInfo[] call() {
1148                 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());
1149 
1150                 if (columns != null) {
1151                     List<ShellFolderColumnInfo> notNullColumns =
1152                             new ArrayList<ShellFolderColumnInfo>();
1153                     for (int i = 0; i < columns.length; i++) {
1154                         ShellFolderColumnInfo column = columns[i];
1155                         if (column != null) {
1156                             column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
1157                                     ? SwingConstants.RIGHT
1158                                     : column.getAlignment() == LVCFMT_CENTER
1159                                     ? SwingConstants.CENTER
1160                                     : SwingConstants.LEADING);
1161 
1162                             column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));
1163 
1164                             notNullColumns.add(column);
1165                         }
1166                     }
1167                     columns = new ShellFolderColumnInfo[notNullColumns.size()];
1168                     notNullColumns.toArray(columns);
1169                 }
1170                 return columns;
1171             }
1172         });
1173     }
1174 
1175     public Object getFolderColumnValue(final int column) {
1176         if(!isLibrary()) {
1177             ShellFolder library = resolveLibrary();
1178             if (library != null) return library.getFolderColumnValue(column);
1179         }
1180         return invoke(new Callable<Object>() {
1181             public Object call() {
1182                 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
1183             }
1184         });
1185     }
1186 
1187     boolean isLibrary() {
1188         return isLib;
1189     }
1190 
1191     private ShellFolder resolveLibrary() {
1192         for (ShellFolder f = this; f != null; f = f.parent) {
1193             if (!f.isFileSystem()) {
1194                 if (f instanceof Win32ShellFolder2 &&
1195                                            ((Win32ShellFolder2)f).isLibrary()) {
1196                     try {
1197                         return getShellFolder(new File(getPath()));
1198                     } catch (FileNotFoundException e) {
1199                     }
1200                 }
1201                 break;
1202             }
1203         }
1204         return null;
1205     }
1206 
1207     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1208     private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);
1209 
1210     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1211     private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);
1212 
1213     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1214     private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
1215 
1216 
1217     public void sortChildren(final List<? extends File> files) {
1218         // To avoid loads of synchronizations with Invoker and improve performance we
1219         // synchronize the whole code of the sort method once
1220         invoke(new Callable<Void>() {
1221             public Void call() {
1222                 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));
1223 
1224                 return null;
1225             }
1226         });
1227     }
1228 
1229     private static class ColumnComparator implements Comparator<File> {
1230         private final Win32ShellFolder2 shellFolder;
1231 
1232         private final int columnIdx;
1233 
1234         public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
1235             this.shellFolder = shellFolder;
1236             this.columnIdx = columnIdx;
1237         }
1238 
1239         // compares 2 objects within this folder by the specified column
1240         public int compare(final File o, final File o1) {
1241             Integer result = invoke(new Callable<Integer>() {
1242                 public Integer call() {
1243                     if (o instanceof Win32ShellFolder2
1244                         && o1 instanceof Win32ShellFolder2) {
1245                         // delegates comparison to native method
1246                         return compareIDsByColumn(shellFolder.getIShellFolder(),
1247                             ((Win32ShellFolder2) o).getRelativePIDL(),
1248                             ((Win32ShellFolder2) o1).getRelativePIDL(),
1249                             columnIdx);
1250                     }
1251                     return 0;
1252                 }
1253             });
1254 
1255             return result == null ? 0 : result;
1256         }
1257     }
1258 
1259     // Extracts libraries and their default save locations from Known Folders list
1260     private static List<KnownFolderDefinition> getLibraries() {
1261         return invoke(new Callable<List<KnownFolderDefinition>>() {
1262             @Override
1263             public List<KnownFolderDefinition> call() throws Exception {
1264                 KnownFolderDefinition[] all = loadKnownFolders();
1265                 List<KnownFolderDefinition> folders = new ArrayList<>();
1266                 if (all != null) {
1267                     for (KnownFolderDefinition kf : all) {
1268                         if (kf.relativePath == null || kf.parsingName == null ||
1269                                 kf.saveLocation == null) {
1270                             continue;
1271                         }
1272                         folders.add(kf);
1273                     }
1274                 }
1275                 return folders;
1276             }
1277         });
1278     }
1279 
1280 }