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