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