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