View Javadoc
1   /*
2    * Copyright (c) 2003, 2012, 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  
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     static class FolderDisposer implements sun.java2d.DisposerRecord {
167         /*
168          * This is cached as a concession to getFolderType(), which needs
169          * an absolute PIDL.
170          */
171         long absolutePIDL;
172         /*
173          * We keep track of shell folders through the IShellFolder
174          * interface of their parents plus their relative PIDL.
175          */
176         long pIShellFolder;
177         long relativePIDL;
178 
179         boolean disposed;
180         public void dispose() {
181             if (disposed) return;
182             invoke(new Callable<Void>() {
183                 public Void call() {
184                     if (relativePIDL != 0) {
185                         releasePIDL(relativePIDL);
186                     }
187                     if (absolutePIDL != 0) {
188                         releasePIDL(absolutePIDL);
189                     }
190                     if (pIShellFolder != 0) {
191                         releaseIShellFolder(pIShellFolder);
192                     }
193                     return null;
194                 }
195             });
196             disposed = true;
197         }
198     }
199     FolderDisposer disposer = new FolderDisposer();
200     private void setIShellFolder(long pIShellFolder) {
201         disposer.pIShellFolder = pIShellFolder;
202     }
203     private void setRelativePIDL(long relativePIDL) {
204         disposer.relativePIDL = relativePIDL;
205     }
206     /*
207      * The following are for caching various shell folder properties.
208      */
209     private long pIShellIcon = -1L;
210     private String folderType = null;
211     private String displayName = null;
212     private Image smallIcon = null;
213     private Image largeIcon = null;
214     private Boolean isDir = null;
215 
216     /*
217      * The following is to identify the My Documents folder as being special
218      */
219     private boolean isPersonal;
220 
221     private static String composePathForCsidl(int csidl) throws IOException, InterruptedException {
222         String path = getFileSystemPath(csidl);
223         return path == null
224                 ? ("ShellFolder: 0x" + Integer.toHexString(csidl))
225                 : path;
226     }
227 
228     /**
229      * Create a system special shell folder, such as the
230      * desktop or Network Neighborhood.
231      */
232     Win32ShellFolder2(final int csidl) throws IOException, InterruptedException {
233         // Desktop is parent of DRIVES and NETWORK, not necessarily
234         // other special shell folders.
235         super(null, composePathForCsidl(csidl));
236 
237         invoke(new Callable<Void>() {
238             public Void call() throws InterruptedException {
239                 if (csidl == DESKTOP) {
240                     initDesktop();
241                 } else {
242                     initSpecial(getDesktop().getIShellFolder(), csidl);
243                     // At this point, the native method initSpecial() has set our relativePIDL
244                     // relative to the Desktop, which may not be our immediate parent. We need
245                     // to traverse this ID list and break it into a chain of shell folders from
246                     // the top, with each one having an immediate parent and a relativePIDL
247                     // relative to that parent.
248                     long pIDL = disposer.relativePIDL;
249                     parent = getDesktop();
250                     while (pIDL != 0) {
251                         // Get a child pidl relative to 'parent'
252                         long childPIDL = copyFirstPIDLEntry(pIDL);
253                         if (childPIDL != 0) {
254                             // Get a handle to the the rest of the ID list
255                             // i,e, parent's grandchilren and down
256                             pIDL = getNextPIDLEntry(pIDL);
257                             if (pIDL != 0) {
258                                 // Now we know that parent isn't immediate to 'this' because it
259                                 // has a continued ID list. Create a shell folder for this child
260                                 // pidl and make it the new 'parent'.
261                                 parent = new Win32ShellFolder2((Win32ShellFolder2) parent, childPIDL);
262                             } else {
263                                 // No grandchildren means we have arrived at the parent of 'this',
264                                 // and childPIDL is directly relative to parent.
265                                 disposer.relativePIDL = childPIDL;
266                             }
267                         } else {
268                             break;
269                         }
270                     }
271                 }
272                 return null;
273             }
274         }, InterruptedException.class);
275 
276         sun.java2d.Disposer.addRecord(this, disposer);
277     }
278 
279 
280     /**
281      * Create a system shell folder
282      */
283     Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path) {
284         super(parent, (path != null) ? path : "ShellFolder: ");
285         this.disposer.pIShellFolder = pIShellFolder;
286         this.disposer.relativePIDL = relativePIDL;
287         sun.java2d.Disposer.addRecord(this, disposer);
288     }
289 
290 
291     /**
292      * Creates a shell folder with a parent and relative PIDL
293      */
294     Win32ShellFolder2(final Win32ShellFolder2 parent, final long relativePIDL) throws InterruptedException {
295         super(parent,
296             invoke(new Callable<String>() {
297                 public String call() {
298                     return getFileSystemPath(parent.getIShellFolder(), relativePIDL);
299                 }
300             }, RuntimeException.class)
301         );
302         this.disposer.relativePIDL = relativePIDL;
303         sun.java2d.Disposer.addRecord(this, disposer);
304     }
305 
306     // Initializes the desktop shell folder
307     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
308     private native void initDesktop();
309 
310     // Initializes a special, non-file system shell folder
311     // from one of the above constants
312     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
313     private native void initSpecial(long desktopIShellFolder, int csidl);
314 
315     /** Marks this folder as being the My Documents (Personal) folder */
316     public void setIsPersonal() {
317         isPersonal = true;
318     }
319 
320     /**
321      * This method is implemented to make sure that no instances
322      * of <code>ShellFolder</code> are ever serialized. If <code>isFileSystem()</code> returns
323      * <code>true</code>, then the object is representable with an instance of
324      * <code>java.io.File</code> instead. If not, then the object depends
325      * on native PIDL state and should not be serialized.
326      *
327      * @return a <code>java.io.File</code> replacement object. If the folder
328      * is a not a normal directory, then returns the first non-removable
329      * drive (normally "C:\").
330      */
331     protected Object writeReplace() throws java.io.ObjectStreamException {
332         return invoke(new Callable<File>() {
333             public File call() {
334                 if (isFileSystem()) {
335                     return new File(getPath());
336                 } else {
337                     Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives();
338                     if (drives != null) {
339                         File[] driveRoots = drives.listFiles();
340                         if (driveRoots != null) {
341                             for (int i = 0; i < driveRoots.length; i++) {
342                                 if (driveRoots[i] instanceof Win32ShellFolder2) {
343                                     Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i];
344                                     if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) {
345                                         return new File(sf.getPath());
346                                     }
347                                 }
348                             }
349                         }
350                     }
351                     // Ouch, we have no hard drives. Return something "valid" anyway.
352                     return new File("C:\\");
353                 }
354             }
355         });
356     }
357 
358 
359     /**
360      * Finalizer to clean up any COM objects or PIDLs used by this object.
361      */
362     protected void dispose() {
363         disposer.dispose();
364     }
365 
366 
367     // Given a (possibly multi-level) relative PIDL (with respect to
368     // the desktop, at least in all of the usage cases in this code),
369     // return a pointer to the next entry. Does not mutate the PIDL in
370     // any way. Returns 0 if the null terminator is reached.
371     // Needs to be accessible to Win32ShellFolderManager2
372     static native long getNextPIDLEntry(long pIDL);
373 
374     // Given a (possibly multi-level) relative PIDL (with respect to
375     // the desktop, at least in all of the usage cases in this code),
376     // copy the first entry into a newly-allocated PIDL. Returns 0 if
377     // the PIDL is at the end of the list.
378     // Needs to be accessible to Win32ShellFolderManager2
379     static native long copyFirstPIDLEntry(long pIDL);
380 
381     // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL
382     private static native long combinePIDLs(long ppIDL, long pIDL);
383 
384     // Release a PIDL object
385     // Needs to be accessible to Win32ShellFolderManager2
386     static native void releasePIDL(long pIDL);
387 
388     // Release an IShellFolder object
389     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
390     private static native void releaseIShellFolder(long pIShellFolder);
391 
392     /**
393      * Accessor for IShellFolder
394      */
395     private long getIShellFolder() {
396         if (disposer.pIShellFolder == 0) {
397             try {
398                 disposer.pIShellFolder = invoke(new Callable<Long>() {
399                     public Long call() {
400                         assert(isDirectory());
401                         assert(parent != null);
402                         long parentIShellFolder = getParentIShellFolder();
403                         if (parentIShellFolder == 0) {
404                             throw new InternalError("Parent IShellFolder was null for "
405                                     + getAbsolutePath());
406                         }
407                         // We are a directory with a parent and a relative PIDL.
408                         // We want to bind to the parent so we get an
409                         // IShellFolder instance associated with us.
410                         long pIShellFolder = bindToObject(parentIShellFolder,
411                                 disposer.relativePIDL);
412                         if (pIShellFolder == 0) {
413                             throw new InternalError("Unable to bind "
414                                     + getAbsolutePath() + " to parent");
415                         }
416                         return pIShellFolder;
417                     }
418                 }, RuntimeException.class);
419             } catch (InterruptedException e) {
420                 // Ignore error
421             }
422         }
423         return disposer.pIShellFolder;
424     }
425 
426     /**
427      * Get the parent ShellFolder's IShellFolder interface
428      */
429     public long getParentIShellFolder() {
430         Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile();
431         if (parent == null) {
432             // Parent should only be null if this is the desktop, whose
433             // relativePIDL is relative to its own IShellFolder.
434             return getIShellFolder();
435         }
436         return parent.getIShellFolder();
437     }
438 
439     /**
440      * Accessor for relative PIDL
441      */
442     public long getRelativePIDL() {
443         if (disposer.relativePIDL == 0) {
444             throw new InternalError("Should always have a relative PIDL");
445         }
446         return disposer.relativePIDL;
447     }
448 
449     private long getAbsolutePIDL() {
450         if (parent == null) {
451             // This is the desktop
452             return getRelativePIDL();
453         } else {
454             if (disposer.absolutePIDL == 0) {
455                 disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL());
456             }
457 
458             return disposer.absolutePIDL;
459         }
460     }
461 
462     /**
463      * Helper function to return the desktop
464      */
465     public Win32ShellFolder2 getDesktop() {
466         return Win32ShellFolderManager2.getDesktop();
467     }
468 
469     /**
470      * Helper function to return the desktop IShellFolder interface
471      */
472     public long getDesktopIShellFolder() {
473         return getDesktop().getIShellFolder();
474     }
475 
476     private static boolean pathsEqual(String path1, String path2) {
477         // Same effective implementation as Win32FileSystem
478         return path1.equalsIgnoreCase(path2);
479     }
480 
481     /**
482      * Check to see if two ShellFolder objects are the same
483      */
484     public boolean equals(Object o) {
485         if (o == null || !(o instanceof Win32ShellFolder2)) {
486             // Short-circuit circuitous delegation path
487             if (!(o instanceof File)) {
488                 return super.equals(o);
489             }
490             return pathsEqual(getPath(), ((File) o).getPath());
491         }
492         Win32ShellFolder2 rhs = (Win32ShellFolder2) o;
493         if ((parent == null && rhs.parent != null) ||
494             (parent != null && rhs.parent == null)) {
495             return false;
496         }
497 
498         if (isFileSystem() && rhs.isFileSystem()) {
499             // Only folders with identical parents can be equal
500             return (pathsEqual(getPath(), rhs.getPath()) &&
501                     (parent == rhs.parent || parent.equals(rhs.parent)));
502         }
503 
504         if (parent == rhs.parent || parent.equals(rhs.parent)) {
505             try {
506                 return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL);
507             } catch (InterruptedException e) {
508                 return false;
509             }
510         }
511 
512         return false;
513     }
514 
515     private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2)
516             throws InterruptedException {
517         return invoke(new Callable<Boolean>() {
518             public Boolean call() {
519                 return compareIDs(pIShellFolder, pidl1, pidl2) == 0;
520             }
521         }, RuntimeException.class);
522     }
523 
524     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
525     private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2);
526 
527     private volatile Boolean cachedIsFileSystem;
528 
529     /**
530      * @return Whether this is a file system shell folder
531      */
532     public boolean isFileSystem() {
533         if (cachedIsFileSystem == null) {
534             cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM);
535         }
536 
537         return cachedIsFileSystem;
538     }
539 
540     /**
541      * Return whether the given attribute flag is set for this object
542      */
543     public boolean hasAttribute(final int attribute) {
544         Boolean result = invoke(new Callable<Boolean>() {
545             public Boolean call() {
546                 // Caching at this point doesn't seem to be cost efficient
547                 return (getAttributes0(getParentIShellFolder(),
548                     getRelativePIDL(), attribute)
549                     & attribute) != 0;
550             }
551         });
552 
553         return result != null && result;
554     }
555 
556     /**
557      * Returns the queried attributes specified in attrsMask.
558      *
559      * Could plausibly be used for attribute caching but have to be
560      * very careful not to touch network drives and file system roots
561      * with a full attrsMask
562      * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
563      */
564 
565     private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask);
566 
567     // Return the path to the underlying file system object
568     // Should be called from the COM thread
569     private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) {
570         int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER;
571         if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() &&
572                 getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) {
573 
574             String s =
575                     getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(),
576                             getLinkLocation(parentIShellFolder, relativePIDL, false));
577             if (s != null && s.startsWith("\\\\")) {
578                 return s;
579             }
580         }
581         return getDisplayNameOf(parentIShellFolder, relativePIDL, SHGDN_FORPARSING);
582     }
583 
584     // Needs to be accessible to Win32ShellFolderManager2
585     static String getFileSystemPath(final int csidl) throws IOException, InterruptedException {
586         return invoke(new Callable<String>() {
587             public String call() throws IOException {
588                 return getFileSystemPath0(csidl);
589             }
590         }, IOException.class);
591     }
592 
593     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
594     private static native String getFileSystemPath0(int csidl) throws IOException;
595 
596     // Return whether the path is a network root.
597     // Path is assumed to be non-null
598     private static boolean isNetworkRoot(String path) {
599         return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/"));
600     }
601 
602     /**
603      * @return The parent shell folder of this shell folder, null if
604      * there is no parent
605      */
606     public File getParentFile() {
607         return parent;
608     }
609 
610     public boolean isDirectory() {
611         if (isDir == null) {
612             // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are
613             // not traversable in JFileChooser.
614             if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) {
615                 isDir = Boolean.TRUE;
616             } else if (isLink()) {
617                 ShellFolder linkLocation = getLinkLocation(false);
618                 isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory());
619             } else {
620                 isDir = Boolean.FALSE;
621             }
622         }
623         return isDir.booleanValue();
624     }
625 
626     /*
627      * Functions for enumerating an IShellFolder's children
628      */
629     // Returns an IEnumIDList interface for an IShellFolder.  The value
630     // returned must be released using releaseEnumObjects().
631     private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException {
632         return invoke(new Callable<Long>() {
633             public Long call() {
634                 boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder();
635 
636                 return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles);
637             }
638         }, RuntimeException.class);
639     }
640 
641     // Returns an IEnumIDList interface for an IShellFolder.  The value
642     // returned must be released using releaseEnumObjects().
643     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
644     private native long getEnumObjects(long pIShellFolder, boolean isDesktop,
645                                        boolean includeHiddenFiles);
646     // Returns the next sequential child as a relative PIDL
647     // from an IEnumIDList interface.  The value returned must
648     // be released using releasePIDL().
649     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
650     private native long getNextChild(long pEnumObjects);
651     // Releases the IEnumIDList interface
652     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
653     private native void releaseEnumObjects(long pEnumObjects);
654 
655     // Returns the IShellFolder of a child from a parent IShellFolder
656     // and a relative PIDL.  The value returned must be released
657     // using releaseIShellFolder().
658     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
659     private static native long bindToObject(long parentIShellFolder, long pIDL);
660 
661     /**
662      * @return An array of shell folders that are children of this shell folder
663      *         object. The array will be empty if the folder is empty.  Returns
664      *         <code>null</code> if this shellfolder does not denote a directory.
665      */
666     public File[] listFiles(final boolean includeHiddenFiles) {
667         SecurityManager security = System.getSecurityManager();
668         if (security != null) {
669             security.checkRead(getPath());
670         }
671 
672         try {
673             return invoke(new Callable<File[]>() {
674                 public File[] call() throws InterruptedException {
675                     if (!isDirectory()) {
676                         return null;
677                     }
678                     // Links to directories are not directories and cannot be parents.
679                     // This does not apply to folders in My Network Places (NetHood)
680                     // because they are both links and real directories!
681                     if (isLink() && !hasAttribute(ATTRIB_FOLDER)) {
682                         return new File[0];
683                     }
684 
685                     Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop();
686                     Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal();
687 
688                     // If we are a directory, we have a parent and (at least) a
689                     // relative PIDL. We must first ensure we are bound to the
690                     // parent so we have an IShellFolder to query.
691                     long pIShellFolder = getIShellFolder();
692                     // Now we can enumerate the objects in this folder.
693                     ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>();
694                     long pEnumObjects = getEnumObjects(includeHiddenFiles);
695                     if (pEnumObjects != 0) {
696                         try {
697                             long childPIDL;
698                             int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR;
699                             do {
700                                 childPIDL = getNextChild(pEnumObjects);
701                                 boolean releasePIDL = true;
702                                 if (childPIDL != 0 &&
703                                         (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) {
704                                     Win32ShellFolder2 childFolder;
705                                     if (Win32ShellFolder2.this.equals(desktop)
706                                             && personal != null
707                                             && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) {
708                                         childFolder = personal;
709                                     } else {
710                                         childFolder = new Win32ShellFolder2(Win32ShellFolder2.this, childPIDL);
711                                         releasePIDL = false;
712                                     }
713                                     list.add(childFolder);
714                                 }
715                                 if (releasePIDL) {
716                                     releasePIDL(childPIDL);
717                                 }
718                             } while (childPIDL != 0 && !Thread.currentThread().isInterrupted());
719                         } finally {
720                             releaseEnumObjects(pEnumObjects);
721                         }
722                     }
723                     return Thread.currentThread().isInterrupted()
724                         ? new File[0]
725                         : list.toArray(new ShellFolder[list.size()]);
726                 }
727             }, InterruptedException.class);
728         } catch (InterruptedException e) {
729             return new File[0];
730         }
731     }
732 
733 
734     /**
735      * Look for (possibly special) child folder by it's path
736      *
737      * @return The child shellfolder, or null if not found.
738      */
739     Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
740         return invoke(new Callable<Win32ShellFolder2>() {
741             public Win32ShellFolder2 call() throws InterruptedException {
742                 long pIShellFolder = getIShellFolder();
743                 long pEnumObjects = getEnumObjects(true);
744                 Win32ShellFolder2 child = null;
745                 long childPIDL;
746 
747                 while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
748                     if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
749                         String path = getFileSystemPath(pIShellFolder, childPIDL);
750                         if (path != null && path.equalsIgnoreCase(filePath)) {
751                             long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
752                             child = new Win32ShellFolder2(Win32ShellFolder2.this,
753                                     childIShellFolder, childPIDL, path);
754                             break;
755                         }
756                     }
757                     releasePIDL(childPIDL);
758                 }
759                 releaseEnumObjects(pEnumObjects);
760                 return child;
761             }
762         }, InterruptedException.class);
763     }
764 
765     private volatile Boolean cachedIsLink;
766 
767     /**
768      * @return Whether this shell folder is a link
769      */
770     public boolean isLink() {
771         if (cachedIsLink == null) {
772             cachedIsLink = hasAttribute(ATTRIB_LINK);
773         }
774 
775         return cachedIsLink;
776     }
777 
778     /**
779      * @return Whether this shell folder is marked as hidden
780      */
781     public boolean isHidden() {
782         return hasAttribute(ATTRIB_HIDDEN);
783     }
784 
785 
786     // Return the link location of a shell folder
787     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
788     private static native long getLinkLocation(long parentIShellFolder,
789                                         long relativePIDL, boolean resolve);
790 
791     /**
792      * @return The shell folder linked to by this shell folder, or null
793      * if this shell folder is not a link or is a broken or invalid link
794      */
795     public ShellFolder getLinkLocation()  {
796         return getLinkLocation(true);
797     }
798 
799     private ShellFolder getLinkLocation(final boolean resolve) {
800         return invoke(new Callable<ShellFolder>() {
801             public ShellFolder call() {
802                 if (!isLink()) {
803                     return null;
804                 }
805 
806                 ShellFolder location = null;
807                 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
808                         getRelativePIDL(), resolve);
809                 if (linkLocationPIDL != 0) {
810                     try {
811                         location =
812                                 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
813                                         linkLocationPIDL);
814                     } catch (InterruptedException e) {
815                         // Return null
816                     } catch (InternalError e) {
817                         // Could be a link to a non-bindable object, such as a network connection
818                         // TODO: getIShellFolder() should throw FileNotFoundException instead
819                     }
820                 }
821                 return location;
822             }
823         });
824     }
825 
826     // Parse a display name into a PIDL relative to the current IShellFolder.
827     long parseDisplayName(final String name) throws IOException, InterruptedException {
828         return invoke(new Callable<Long>() {
829             public Long call() throws IOException {
830                 return parseDisplayName0(getIShellFolder(), name);
831             }
832         }, IOException.class);
833     }
834 
835     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
836     private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;
837 
838     // Return the display name of a shell folder
839     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
840     private static native String getDisplayNameOf(long parentIShellFolder,
841                                                   long relativePIDL,
842                                                   int attrs);
843 
844     /**
845      * @return The name used to display this shell folder
846      */
847     public String getDisplayName() {
848         if (displayName == null) {
849             displayName =
850                 invoke(new Callable<String>() {
851                     public String call() {
852                         return getDisplayNameOf(getParentIShellFolder(),
853                                 getRelativePIDL(), SHGDN_NORMAL);
854                     }
855                 });
856         }
857         return displayName;
858     }
859 
860     // Return the folder type of a shell folder
861     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
862     private static native String getFolderType(long pIDL);
863 
864     /**
865      * @return The type of shell folder as a string
866      */
867     public String getFolderType() {
868         if (folderType == null) {
869             final long absolutePIDL = getAbsolutePIDL();
870             folderType =
871                 invoke(new Callable<String>() {
872                     public String call() {
873                         return getFolderType(absolutePIDL);
874                     }
875                 });
876         }
877         return folderType;
878     }
879 
880     // Return the executable type of a file system shell folder
881     private native String getExecutableType(String path);
882 
883     /**
884      * @return The executable type as a string
885      */
886     public String getExecutableType() {
887         if (!isFileSystem()) {
888             return null;
889         }
890         return getExecutableType(getAbsolutePath());
891     }
892 
893 
894 
895     // Icons
896 
897     private static Map smallSystemImages = new HashMap();
898     private static Map largeSystemImages = new HashMap();
899     private static Map smallLinkedSystemImages = new HashMap();
900     private static Map largeLinkedSystemImages = new HashMap();
901 
902     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
903     private static native long getIShellIcon(long pIShellFolder);
904 
905     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
906     private static native int getIconIndex(long parentIShellIcon, long relativePIDL);
907 
908     // Return the icon of a file system shell folder in the form of an HICON
909     private static native long getIcon(String absolutePath, boolean getLargeIcon);
910 
911     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
912     private static native long extractIcon(long parentIShellFolder, long relativePIDL,
913                                            boolean getLargeIcon);
914 
915     // Returns an icon from the Windows system icon list in the form of an HICON
916     private static native long getSystemIcon(int iconID);
917     private static native long getIconResource(String libName, int iconID,
918                                                int cxDesired, int cyDesired,
919                                                boolean useVGAColors);
920                                                // Note: useVGAColors is ignored on XP and later
921 
922     // Return the bits from an HICON.  This has a side effect of setting
923     // the imageHash variable for efficient caching / comparing.
924     private static native int[] getIconBits(long hIcon, int iconSize);
925     // Dispose the HICON
926     private static native void disposeIcon(long hIcon);
927 
928     static native int[] getStandardViewButton0(int iconIndex);
929 
930     // Should be called from the COM thread
931     private long getIShellIcon() {
932         if (pIShellIcon == -1L) {
933             pIShellIcon = getIShellIcon(getIShellFolder());
934         }
935 
936         return pIShellIcon;
937     }
938 
939     private static Image makeIcon(long hIcon, boolean getLargeIcon) {
940         if (hIcon != 0L && hIcon != -1L) {
941             // Get the bits.  This has the side effect of setting the imageHash value for this object.
942             int size = getLargeIcon ? 32 : 16;
943             int[] iconBits = getIconBits(hIcon, size);
944             if (iconBits != null) {
945                 BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
946                 img.setRGB(0, 0, size, size, iconBits, 0, size);
947                 return img;
948             }
949         }
950         return null;
951     }
952 
953 
954     /**
955      * @return The icon image used to display this shell folder
956      */
957     public Image getIcon(final boolean getLargeIcon) {
958         Image icon = getLargeIcon ? largeIcon : smallIcon;
959         if (icon == null) {
960             icon =
961                 invoke(new Callable<Image>() {
962                     public Image call() {
963                         Image newIcon = null;
964                         if (isFileSystem()) {
965                             long parentIShellIcon = (parent != null)
966                                 ? ((Win32ShellFolder2) parent).getIShellIcon()
967                                 : 0L;
968                             long relativePIDL = getRelativePIDL();
969 
970                             // These are cached per type (using the index in the system image list)
971                             int index = getIconIndex(parentIShellIcon, relativePIDL);
972                             if (index > 0) {
973                                 Map imageCache;
974                                 if (isLink()) {
975                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
976                                 } else {
977                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
978                                 }
979                                 newIcon = (Image) imageCache.get(Integer.valueOf(index));
980                                 if (newIcon == null) {
981                                     long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
982                                     newIcon = makeIcon(hIcon, getLargeIcon);
983                                     disposeIcon(hIcon);
984                                     if (newIcon != null) {
985                                         imageCache.put(Integer.valueOf(index), newIcon);
986                                     }
987                                 }
988                             }
989                         }
990 
991                         if (newIcon == null) {
992                             // These are only cached per object
993                             long hIcon = extractIcon(getParentIShellFolder(),
994                                 getRelativePIDL(), getLargeIcon);
995                             newIcon = makeIcon(hIcon, getLargeIcon);
996                             disposeIcon(hIcon);
997                         }
998 
999                         if (newIcon == null) {
1000                             newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
1001                         }
1002                         return newIcon;
1003                     }
1004                 });
1005             if (getLargeIcon) {
1006                 largeIcon = icon;
1007             } else {
1008                 smallIcon = icon;
1009             }
1010         }
1011         return icon;
1012     }
1013 
1014     /**
1015      * Gets an icon from the Windows system icon list as an <code>Image</code>
1016      */
1017     static Image getSystemIcon(SystemIcon iconType) {
1018         long hIcon = getSystemIcon(iconType.getIconID());
1019         Image icon = makeIcon(hIcon, true);
1020         disposeIcon(hIcon);
1021         return icon;
1022     }
1023 
1024     /**
1025      * Gets an icon from the Windows system icon list as an <code>Image</code>
1026      */
1027     static Image getShell32Icon(int iconID, boolean getLargeIcon) {
1028         boolean useVGAColors = true; // Will be ignored on XP and later
1029 
1030         int size = getLargeIcon ? 32 : 16;
1031 
1032         Toolkit toolkit = Toolkit.getDefaultToolkit();
1033         String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
1034         if (shellIconBPP != null) {
1035             useVGAColors = shellIconBPP.equals("4");
1036         }
1037 
1038         long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
1039         if (hIcon != 0) {
1040             Image icon = makeIcon(hIcon, getLargeIcon);
1041             disposeIcon(hIcon);
1042             return icon;
1043         }
1044         return null;
1045     }
1046 
1047     /**
1048      * Returns the canonical form of this abstract pathname.  Equivalent to
1049      * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
1050      *
1051      * @see java.io.File#getCanonicalFile
1052      */
1053     public File getCanonicalFile() throws IOException {
1054         return this;
1055     }
1056 
1057     /*
1058      * Indicates whether this is a special folder (includes My Documents)
1059      */
1060     public boolean isSpecial() {
1061         return isPersonal || !isFileSystem() || (this == getDesktop());
1062     }
1063 
1064     /**
1065      * Compares this object with the specified object for order.
1066      *
1067      * @see sun.awt.shell.ShellFolder#compareTo(File)
1068      */
1069     public int compareTo(File file2) {
1070         if (!(file2 instanceof Win32ShellFolder2)) {
1071             if (isFileSystem() && !isSpecial()) {
1072                 return super.compareTo(file2);
1073             } else {
1074                 return -1; // Non-file shellfolders sort before files
1075             }
1076         }
1077         return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
1078     }
1079 
1080     // native constants from commctrl.h
1081     private static final int LVCFMT_LEFT = 0;
1082     private static final int LVCFMT_RIGHT = 1;
1083     private static final int LVCFMT_CENTER = 2;
1084 
1085     public ShellFolderColumnInfo[] getFolderColumns() {
1086         return invoke(new Callable<ShellFolderColumnInfo[]>() {
1087             public ShellFolderColumnInfo[] call() {
1088                 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());
1089 
1090                 if (columns != null) {
1091                     List<ShellFolderColumnInfo> notNullColumns =
1092                             new ArrayList<ShellFolderColumnInfo>();
1093                     for (int i = 0; i < columns.length; i++) {
1094                         ShellFolderColumnInfo column = columns[i];
1095                         if (column != null) {
1096                             column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
1097                                     ? SwingConstants.RIGHT
1098                                     : column.getAlignment() == LVCFMT_CENTER
1099                                     ? SwingConstants.CENTER
1100                                     : SwingConstants.LEADING);
1101 
1102                             column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));
1103 
1104                             notNullColumns.add(column);
1105                         }
1106                     }
1107                     columns = new ShellFolderColumnInfo[notNullColumns.size()];
1108                     notNullColumns.toArray(columns);
1109                 }
1110                 return columns;
1111             }
1112         });
1113     }
1114 
1115     public Object getFolderColumnValue(final int column) {
1116         return invoke(new Callable<Object>() {
1117             public Object call() {
1118                 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
1119             }
1120         });
1121     }
1122 
1123     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1124     private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);
1125 
1126     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1127     private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);
1128 
1129     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1130     private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
1131 
1132 
1133     public void sortChildren(final List<? extends File> files) {
1134         // To avoid loads of synchronizations with Invoker and improve performance we
1135         // synchronize the whole code of the sort method once
1136         invoke(new Callable<Void>() {
1137             public Void call() {
1138                 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));
1139 
1140                 return null;
1141             }
1142         });
1143     }
1144 
1145     private static class ColumnComparator implements Comparator<File> {
1146         private final Win32ShellFolder2 shellFolder;
1147 
1148         private final int columnIdx;
1149 
1150         public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
1151             this.shellFolder = shellFolder;
1152             this.columnIdx = columnIdx;
1153         }
1154 
1155         // compares 2 objects within this folder by the specified column
1156         public int compare(final File o, final File o1) {
1157             Integer result = invoke(new Callable<Integer>() {
1158                 public Integer call() {
1159                     if (o instanceof Win32ShellFolder2
1160                         && o1 instanceof Win32ShellFolder2) {
1161                         // delegates comparison to native method
1162                         return compareIDsByColumn(shellFolder.getIShellFolder(),
1163                             ((Win32ShellFolder2) o).getRelativePIDL(),
1164                             ((Win32ShellFolder2) o1).getRelativePIDL(),
1165                             columnIdx);
1166                     }
1167                     return 0;
1168                 }
1169             });
1170 
1171             return result == null ? 0 : result;
1172         }
1173     }
1174 }