1 /*
2  * Copyright (c) 2003, 2018, 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 
initIDs()77     private static native void initIDs();
78 
79     static {
initIDs()80         initIDs();
81     }
82 
83     // Win32 Shell Folder Constants
84     public static final int DESKTOP = 0x0000;
85     public static final int INTERNET = 0x0001;
86     public static final int PROGRAMS = 0x0002;
87     public static final int CONTROLS = 0x0003;
88     public static final int PRINTERS = 0x0004;
89     public static final int PERSONAL = 0x0005;
90     public static final int FAVORITES = 0x0006;
91     public static final int STARTUP = 0x0007;
92     public static final int RECENT = 0x0008;
93     public static final int SENDTO = 0x0009;
94     public static final int BITBUCKET = 0x000a;
95     public static final int STARTMENU = 0x000b;
96     public static final int DESKTOPDIRECTORY = 0x0010;
97     public static final int DRIVES = 0x0011;
98     public static final int NETWORK = 0x0012;
99     public static final int NETHOOD = 0x0013;
100     public static final int FONTS = 0x0014;
101     public static final int TEMPLATES = 0x0015;
102     public static final int COMMON_STARTMENU = 0x0016;
103     public static final int COMMON_PROGRAMS = 0X0017;
104     public static final int COMMON_STARTUP = 0x0018;
105     public static final int COMMON_DESKTOPDIRECTORY = 0x0019;
106     public static final int APPDATA = 0x001a;
107     public static final int PRINTHOOD = 0x001b;
108     public static final int ALTSTARTUP = 0x001d;
109     public static final int COMMON_ALTSTARTUP = 0x001e;
110     public static final int COMMON_FAVORITES = 0x001f;
111     public static final int INTERNET_CACHE = 0x0020;
112     public static final int COOKIES = 0x0021;
113     public static final int HISTORY = 0x0022;
114 
115     // Win32 shell folder attributes
116     public static final int ATTRIB_CANCOPY          = 0x00000001;
117     public static final int ATTRIB_CANMOVE          = 0x00000002;
118     public static final int ATTRIB_CANLINK          = 0x00000004;
119     public static final int ATTRIB_CANRENAME        = 0x00000010;
120     public static final int ATTRIB_CANDELETE        = 0x00000020;
121     public static final int ATTRIB_HASPROPSHEET     = 0x00000040;
122     public static final int ATTRIB_DROPTARGET       = 0x00000100;
123     public static final int ATTRIB_LINK             = 0x00010000;
124     public static final int ATTRIB_SHARE            = 0x00020000;
125     public static final int ATTRIB_READONLY         = 0x00040000;
126     public static final int ATTRIB_GHOSTED          = 0x00080000;
127     public static final int ATTRIB_HIDDEN           = 0x00080000;
128     public static final int ATTRIB_FILESYSANCESTOR  = 0x10000000;
129     public static final int ATTRIB_FOLDER           = 0x20000000;
130     public static final int ATTRIB_FILESYSTEM       = 0x40000000;
131     public static final int ATTRIB_HASSUBFOLDER     = 0x80000000;
132     public static final int ATTRIB_VALIDATE         = 0x01000000;
133     public static final int ATTRIB_REMOVABLE        = 0x02000000;
134     public static final int ATTRIB_COMPRESSED       = 0x04000000;
135     public static final int ATTRIB_BROWSABLE        = 0x08000000;
136     public static final int ATTRIB_NONENUMERATED    = 0x00100000;
137     public static final int ATTRIB_NEWCONTENT       = 0x00200000;
138 
139     // IShellFolder::GetDisplayNameOf constants
140     public static final int SHGDN_NORMAL            = 0;
141     public static final int SHGDN_INFOLDER          = 1;
142     public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000;
143     public static final int SHGDN_FORADDRESSBAR     = 0x4000;
144     public static final int SHGDN_FORPARSING        = 0x8000;
145 
146     /** The referent to be registered with the Disposer. */
147     private Object disposerReferent = new Object();
148 
149     // Values for system call LoadIcon()
150     public enum SystemIcon {
151         IDI_APPLICATION(32512),
152         IDI_HAND(32513),
153         IDI_ERROR(32513),
154         IDI_QUESTION(32514),
155         IDI_EXCLAMATION(32515),
156         IDI_WARNING(32515),
157         IDI_ASTERISK(32516),
158         IDI_INFORMATION(32516),
159         IDI_WINLOGO(32517);
160 
161         private final int iconID;
162 
SystemIcon(int iconID)163         SystemIcon(int iconID) {
164             this.iconID = iconID;
165         }
166 
getIconID()167         public int getIconID() {
168             return iconID;
169         }
170     }
171 
172     // Known Folder data
173     static final class KnownFolderDefinition {
174         String guid;
175         int category;
176         String name;
177         String description;
178         String parent;
179         String relativePath;
180         String parsingName;
181         String tooltip;
182         String localizedName;
183         String icon;
184         String security;
185         long attributes;
186         int defenitionFlags;
187         String ftidType;
188         String path;
189         String saveLocation;
190     }
191 
192     static final class KnownLibraries {
193         static final List<KnownFolderDefinition> INSTANCE = 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;
dispose()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();
setIShellFolder(long pIShellFolder)230     private void setIShellFolder(long pIShellFolder) {
231         disposer.pIShellFolder = pIShellFolder;
232     }
setRelativePIDL(long relativePIDL)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 
composePathForCsidl(int csidl)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      */
Win32ShellFolder2(final int csidl)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      */
Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path, boolean isLib)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      */
createShellFolder(Win32ShellFolder2 parent, long pIDL)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
initDesktop()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
initSpecial(long desktopIShellFolder, int csidl)353     private native void initSpecial(long desktopIShellFolder, int csidl);
354 
355     /** Marks this folder as being the My Documents (Personal) folder */
setIsPersonal()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      */
writeReplace()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 : KnownLibraries.INSTANCE) {
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             File[] files = 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 
795             return Win32ShellFolderManager2.checkFiles(files);
796         } catch (InterruptedException e) {
797             return new File[0];
798         }
799     }
800 
801 
802     /**
803      * Look for (possibly special) child folder by it's path
804      *
805      * @return The child shellfolder, or null if not found.
806      */
807     Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
808         return invoke(new Callable<Win32ShellFolder2>() {
809             public Win32ShellFolder2 call() throws InterruptedException {
810                 long pIShellFolder = getIShellFolder();
811                 long pEnumObjects = getEnumObjects(true);
812                 Win32ShellFolder2 child = null;
813                 long childPIDL;
814 
815                 while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
816                     if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
817                         String path = getFileSystemPath(pIShellFolder, childPIDL);
818                         if(isLib) path = resolveLibrary( path );
819                         if (path != null && path.equalsIgnoreCase(filePath)) {
820                             long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
821                             child = new Win32ShellFolder2(Win32ShellFolder2.this,
822                                     childIShellFolder, childPIDL, path, isLib);
823                             break;
824                         }
825                     }
826                     releasePIDL(childPIDL);
827                 }
828                 releaseEnumObjects(pEnumObjects);
829                 return child;
830             }
831         }, InterruptedException.class);
832     }
833 
834     private volatile Boolean cachedIsLink;
835 
836     /**
837      * @return Whether this shell folder is a link
838      */
839     public boolean isLink() {
840         if (cachedIsLink == null) {
841             cachedIsLink = hasAttribute(ATTRIB_LINK);
842         }
843 
844         return cachedIsLink;
845     }
846 
847     /**
848      * @return Whether this shell folder is marked as hidden
849      */
850     public boolean isHidden() {
851         return hasAttribute(ATTRIB_HIDDEN);
852     }
853 
854 
855     // Return the link location of a shell folder
856     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
857     private static native long getLinkLocation(long parentIShellFolder,
858                                         long relativePIDL, boolean resolve);
859 
860     /**
861      * @return The shell folder linked to by this shell folder, or null
862      * if this shell folder is not a link or is a broken or invalid link
863      */
864     public ShellFolder getLinkLocation()  {
865         return getLinkLocation(true);
866     }
867 
868     private Win32ShellFolder2 getLinkLocation(final boolean resolve) {
869         return invoke(new Callable<Win32ShellFolder2>() {
870             public Win32ShellFolder2 call() {
871                 if (!isLink()) {
872                     return null;
873                 }
874 
875                 Win32ShellFolder2 location = null;
876                 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
877                         getRelativePIDL(), resolve);
878                 if (linkLocationPIDL != 0) {
879                     try {
880                         location =
881                                 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
882                                         linkLocationPIDL);
883                     } catch (InterruptedException e) {
884                         // Return null
885                     } catch (InternalError e) {
886                         // Could be a link to a non-bindable object, such as a network connection
887                         // TODO: getIShellFolder() should throw FileNotFoundException instead
888                     }
889                 }
890                 return location;
891             }
892         });
893     }
894 
895     // Parse a display name into a PIDL relative to the current IShellFolder.
896     long parseDisplayName(final String name) throws IOException, InterruptedException {
897         return invoke(new Callable<Long>() {
898             public Long call() throws IOException {
899                 return parseDisplayName0(getIShellFolder(), name);
900             }
901         }, IOException.class);
902     }
903 
904     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
905     private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;
906 
907     // Return the display name of a shell folder
908     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
909     private static native String getDisplayNameOf(long parentIShellFolder,
910                                                   long relativePIDL,
911                                                   int attrs);
912 
913     // Returns data of all Known Folders registered in the system
914     private static native KnownFolderDefinition[] loadKnownFolders();
915 
916     /**
917      * @return The name used to display this shell folder
918      */
919     public String getDisplayName() {
920         if (displayName == null) {
921             displayName =
922                 invoke(new Callable<String>() {
923                     public String call() {
924                         return getDisplayNameOf(getParentIShellFolder(),
925                                 getRelativePIDL(), SHGDN_NORMAL);
926                     }
927                 });
928         }
929         return displayName;
930     }
931 
932     // Return the folder type of a shell folder
933     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
934     private static native String getFolderType(long pIDL);
935 
936     /**
937      * @return The type of shell folder as a string
938      */
939     public String getFolderType() {
940         if (folderType == null) {
941             final long absolutePIDL = getAbsolutePIDL();
942             folderType =
943                 invoke(new Callable<String>() {
944                     public String call() {
945                         return getFolderType(absolutePIDL);
946                     }
947                 });
948         }
949         return folderType;
950     }
951 
952     // Return the executable type of a file system shell folder
953     private native String getExecutableType(String path);
954 
955     /**
956      * @return The executable type as a string
957      */
958     public String getExecutableType() {
959         if (!isFileSystem()) {
960             return null;
961         }
962         return getExecutableType(getAbsolutePath());
963     }
964 
965 
966 
967     // Icons
968 
969     private static Map<Integer, Image> smallSystemImages = new HashMap<>();
970     private static Map<Integer, Image> largeSystemImages = new HashMap<>();
971     private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>();
972     private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>();
973 
974     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
975     private static native long getIShellIcon(long pIShellFolder);
976 
977     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
978     private static native int getIconIndex(long parentIShellIcon, long relativePIDL);
979 
980     // Return the icon of a file system shell folder in the form of an HICON
981     private static native long getIcon(String absolutePath, boolean getLargeIcon);
982 
983     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
984     private static native long extractIcon(long parentIShellFolder, long relativePIDL,
985                                            boolean getLargeIcon, boolean getDefaultIcon);
986 
987     // Returns an icon from the Windows system icon list in the form of an HICON
988     private static native long getSystemIcon(int iconID);
989     private static native long getIconResource(String libName, int iconID,
990                                                int cxDesired, int cyDesired,
991                                                boolean useVGAColors);
992                                                // Note: useVGAColors is ignored on XP and later
993 
994     // Return the bits from an HICON.  This has a side effect of setting
995     // the imageHash variable for efficient caching / comparing.
996     private static native int[] getIconBits(long hIcon);
997     // Dispose the HICON
998     private static native void disposeIcon(long hIcon);
999 
1000     // Get buttons from native toolbar implementation.
1001     static native int[] getStandardViewButton0(int iconIndex, boolean small);
1002 
1003     // Should be called from the COM thread
1004     private long getIShellIcon() {
1005         if (pIShellIcon == -1L) {
1006             pIShellIcon = getIShellIcon(getIShellFolder());
1007         }
1008 
1009         return pIShellIcon;
1010     }
1011 
1012     private static Image makeIcon(long hIcon, boolean getLargeIcon) {
1013         if (hIcon != 0L && hIcon != -1L) {
1014             // Get the bits.  This has the side effect of setting the imageHash value for this object.
1015             final int[] iconBits = getIconBits(hIcon);
1016             if (iconBits != null) {
1017                 // icons are always square
1018                 final int size = (int) Math.sqrt(iconBits.length);
1019                 final int baseSize = getLargeIcon ? 32 : 16;
1020                 final BufferedImage img =
1021                         new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
1022                 img.setRGB(0, 0, size, size, iconBits, 0, size);
1023                 return size == baseSize
1024                         ? img
1025                         : new MultiResolutionIconImage(baseSize, img);
1026             }
1027         }
1028         return null;
1029     }
1030 
1031 
1032     /**
1033      * @return The icon image used to display this shell folder
1034      */
1035     public Image getIcon(final boolean getLargeIcon) {
1036         Image icon = getLargeIcon ? largeIcon : smallIcon;
1037         if (icon == null) {
1038             icon =
1039                 invoke(new Callable<Image>() {
1040                     public Image call() {
1041                         Image newIcon = null;
1042                         if (isLink()) {
1043                             Win32ShellFolder2 folder = getLinkLocation(false);
1044                             if (folder != null && folder.isLibrary()) {
1045                                 return folder.getIcon(getLargeIcon);
1046                             }
1047                         }
1048                         if (isFileSystem() || isLibrary()) {
1049                             long parentIShellIcon = (parent != null)
1050                                 ? ((Win32ShellFolder2) parent).getIShellIcon()
1051                                 : 0L;
1052                             long relativePIDL = getRelativePIDL();
1053 
1054                             // These are cached per type (using the index in the system image list)
1055                             int index = getIconIndex(parentIShellIcon, relativePIDL);
1056                             if (index > 0) {
1057                                 Map<Integer, Image> imageCache;
1058                                 if (isLink()) {
1059                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
1060                                 } else {
1061                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
1062                                 }
1063                                 newIcon = imageCache.get(Integer.valueOf(index));
1064                                 if (newIcon == null) {
1065                                     long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
1066                                     newIcon = makeIcon(hIcon, getLargeIcon);
1067                                     disposeIcon(hIcon);
1068                                     if (newIcon != null) {
1069                                         imageCache.put(Integer.valueOf(index), newIcon);
1070                                     }
1071                                 }
1072                             }
1073                         }
1074 
1075                         if (newIcon == null) {
1076                             // These are only cached per object
1077                             long hIcon = extractIcon(getParentIShellFolder(),
1078                                     getRelativePIDL(), getLargeIcon, false);
1079                             // E_PENDING: loading can take time so get the default
1080                             if(hIcon <= 0) {
1081                                 hIcon = extractIcon(getParentIShellFolder(),
1082                                          getRelativePIDL(), getLargeIcon, true);
1083                                 if(hIcon <= 0) {
1084                                     if (isDirectory()) {
1085                                         return getShell32Icon(4, getLargeIcon);
1086                                     } else {
1087                                         return getShell32Icon(1, getLargeIcon);
1088                                     }
1089                                 }
1090                             }
1091                             newIcon = makeIcon(hIcon, getLargeIcon);
1092                             disposeIcon(hIcon);
1093                         }
1094 
1095                         if (newIcon == null) {
1096                             newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
1097                         }
1098                         return newIcon;
1099                     }
1100                 });
1101             if (getLargeIcon) {
1102                 largeIcon = icon;
1103             } else {
1104                 smallIcon = icon;
1105             }
1106         }
1107         return icon;
1108     }
1109 
1110     /**
1111      * Gets an icon from the Windows system icon list as an {@code Image}
1112      */
1113     static Image getSystemIcon(SystemIcon iconType) {
1114         long hIcon = getSystemIcon(iconType.getIconID());
1115         Image icon = makeIcon(hIcon, true);
1116         disposeIcon(hIcon);
1117         return icon;
1118     }
1119 
1120     /**
1121      * Gets an icon from the Windows system icon list as an {@code Image}
1122      */
1123     static Image getShell32Icon(int iconID, boolean getLargeIcon) {
1124         boolean useVGAColors = true; // Will be ignored on XP and later
1125 
1126         int size = getLargeIcon ? 32 : 16;
1127 
1128         Toolkit toolkit = Toolkit.getDefaultToolkit();
1129         String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
1130         if (shellIconBPP != null) {
1131             useVGAColors = shellIconBPP.equals("4");
1132         }
1133 
1134         long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
1135         if (hIcon != 0) {
1136             Image icon = makeIcon(hIcon, getLargeIcon);
1137             disposeIcon(hIcon);
1138             return icon;
1139         }
1140         return null;
1141     }
1142 
1143     /**
1144      * Returns the canonical form of this abstract pathname.  Equivalent to
1145      * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
1146      *
1147      * @see java.io.File#getCanonicalFile
1148      */
1149     public File getCanonicalFile() throws IOException {
1150         return this;
1151     }
1152 
1153     /*
1154      * Indicates whether this is a special folder (includes My Documents)
1155      */
1156     public boolean isSpecial() {
1157         return isPersonal || !isFileSystem() || (this == getDesktop());
1158     }
1159 
1160     /**
1161      * Compares this object with the specified object for order.
1162      *
1163      * @see sun.awt.shell.ShellFolder#compareTo(File)
1164      */
1165     public int compareTo(File file2) {
1166         if (!(file2 instanceof Win32ShellFolder2)) {
1167             if (isFileSystem() && !isSpecial()) {
1168                 return super.compareTo(file2);
1169             } else {
1170                 return -1; // Non-file shellfolders sort before files
1171             }
1172         }
1173         return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
1174     }
1175 
1176     // native constants from commctrl.h
1177     private static final int LVCFMT_LEFT = 0;
1178     private static final int LVCFMT_RIGHT = 1;
1179     private static final int LVCFMT_CENTER = 2;
1180 
1181     public ShellFolderColumnInfo[] getFolderColumns() {
1182         ShellFolder library = resolveLibrary();
1183         if (library != null) return library.getFolderColumns();
1184         return invoke(new Callable<ShellFolderColumnInfo[]>() {
1185             public ShellFolderColumnInfo[] call() {
1186                 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());
1187 
1188                 if (columns != null) {
1189                     List<ShellFolderColumnInfo> notNullColumns =
1190                             new ArrayList<ShellFolderColumnInfo>();
1191                     for (int i = 0; i < columns.length; i++) {
1192                         ShellFolderColumnInfo column = columns[i];
1193                         if (column != null) {
1194                             column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
1195                                     ? SwingConstants.RIGHT
1196                                     : column.getAlignment() == LVCFMT_CENTER
1197                                     ? SwingConstants.CENTER
1198                                     : SwingConstants.LEADING);
1199 
1200                             column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));
1201 
1202                             notNullColumns.add(column);
1203                         }
1204                     }
1205                     columns = new ShellFolderColumnInfo[notNullColumns.size()];
1206                     notNullColumns.toArray(columns);
1207                 }
1208                 return columns;
1209             }
1210         });
1211     }
1212 
1213     public Object getFolderColumnValue(final int column) {
1214         if(!isLibrary()) {
1215             ShellFolder library = resolveLibrary();
1216             if (library != null) return library.getFolderColumnValue(column);
1217         }
1218         return invoke(new Callable<Object>() {
1219             public Object call() {
1220                 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
1221             }
1222         });
1223     }
1224 
1225     boolean isLibrary() {
1226         return isLib;
1227     }
1228 
1229     private ShellFolder resolveLibrary() {
1230         for (ShellFolder f = this; f != null; f = f.parent) {
1231             if (!f.isFileSystem()) {
1232                 if (f instanceof Win32ShellFolder2 &&
1233                                            ((Win32ShellFolder2)f).isLibrary()) {
1234                     try {
1235                         return getShellFolder(new File(getPath()));
1236                     } catch (FileNotFoundException e) {
1237                     }
1238                 }
1239                 break;
1240             }
1241         }
1242         return null;
1243     }
1244 
1245     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1246     private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);
1247 
1248     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1249     private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);
1250 
1251     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1252     private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
1253 
1254 
1255     public void sortChildren(final List<? extends File> files) {
1256         // To avoid loads of synchronizations with Invoker and improve performance we
1257         // synchronize the whole code of the sort method once
1258         invoke(new Callable<Void>() {
1259             public Void call() {
1260                 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));
1261 
1262                 return null;
1263             }
1264         });
1265     }
1266 
1267     private static class ColumnComparator implements Comparator<File> {
1268         private final Win32ShellFolder2 shellFolder;
1269 
1270         private final int columnIdx;
1271 
1272         public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
1273             this.shellFolder = shellFolder;
1274             this.columnIdx = columnIdx;
1275         }
1276 
1277         // compares 2 objects within this folder by the specified column
1278         public int compare(final File o, final File o1) {
1279             Integer result = invoke(new Callable<Integer>() {
1280                 public Integer call() {
1281                     if (o instanceof Win32ShellFolder2
1282                         && o1 instanceof Win32ShellFolder2) {
1283                         // delegates comparison to native method
1284                         return compareIDsByColumn(shellFolder.getIShellFolder(),
1285                             ((Win32ShellFolder2) o).getRelativePIDL(),
1286                             ((Win32ShellFolder2) o1).getRelativePIDL(),
1287                             columnIdx);
1288                     }
1289                     return 0;
1290                 }
1291             });
1292 
1293             return result == null ? 0 : result;
1294         }
1295     }
1296 
1297     // Extracts libraries and their default save locations from Known Folders list
1298     private static List<KnownFolderDefinition> getLibraries() {
1299         return invoke(new Callable<List<KnownFolderDefinition>>() {
1300             @Override
1301             public List<KnownFolderDefinition> call() throws Exception {
1302                 KnownFolderDefinition[] all = loadKnownFolders();
1303                 List<KnownFolderDefinition> folders = new ArrayList<>();
1304                 if (all != null) {
1305                     for (KnownFolderDefinition kf : all) {
1306                         if (kf.relativePath == null || kf.parsingName == null ||
1307                                 kf.saveLocation == null) {
1308                             continue;
1309                         }
1310                         folders.add(kf);
1311                     }
1312                 }
1313                 return folders;
1314             }
1315         });
1316     }
1317 
1318     static class MultiResolutionIconImage extends AbstractMultiResolutionImage {
1319 
1320         final int baseSize;
1321         final Image resolutionVariant;
1322 
1323         public MultiResolutionIconImage(int baseSize, Image resolutionVariant) {
1324             this.baseSize = baseSize;
1325             this.resolutionVariant = resolutionVariant;
1326         }
1327 
1328         @Override
1329         public int getWidth(ImageObserver observer) {
1330             return baseSize;
1331         }
1332 
1333         @Override
1334         public int getHeight(ImageObserver observer) {
1335             return baseSize;
1336         }
1337 
1338         @Override
1339         protected Image getBaseImage() {
1340             return resolutionVariant;
1341         }
1342 
1343         @Override
1344         public Image getResolutionVariant(double width, double height) {
1345             return resolutionVariant;
1346         }
1347 
1348         @Override
1349         public List<Image> getResolutionVariants() {
1350             return Arrays.asList(resolutionVariant);
1351         }
1352     }
1353 }
1354