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