Win32ShellFolder2.java revision 11099:678faa7d1a6a
1/*
2 * Copyright (c) 2003, 2014, 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
26package sun.awt.shell;
27
28import java.awt.Image;
29import java.awt.Toolkit;
30import java.awt.image.BufferedImage;
31import java.io.File;
32import java.io.IOException;
33import java.util.*;
34import java.util.concurrent.*;
35import javax.swing.SwingConstants;
36
37// NOTE: This class supersedes Win32ShellFolder, which was removed from
38//       distribution after version 1.4.2.
39
40/**
41 * Win32 Shell Folders
42 * <P>
43 * <BR>
44 * There are two fundamental types of shell folders : file system folders
45 * and non-file system folders.  File system folders are relatively easy
46 * to deal with.  Non-file system folders are items such as My Computer,
47 * Network Neighborhood, and the desktop.  Some of these non-file system
48 * folders have special values and properties.
49 * <P>
50 * <BR>
51 * Win32 keeps two basic data structures for shell folders.  The first
52 * of these is called an ITEMIDLIST.  Usually a pointer, called an
53 * LPITEMIDLIST, or more frequently just "PIDL".  This structure holds
54 * a series of identifiers and can be either relative to the desktop
55 * (an absolute PIDL), or relative to the shell folder that contains them.
56 * Some Win32 functions can take absolute or relative PIDL values, and
57 * others can only accept relative values.
58 * <BR>
59 * The second data structure is an IShellFolder COM interface.  Using
60 * this interface, one can enumerate the relative PIDLs in a shell
61 * folder, get attributes, etc.
62 * <BR>
63 * All Win32ShellFolder2 objects which are folder types (even non-file
64 * system folders) contain an IShellFolder object. Files are named in
65 * directories via relative PIDLs.
66 *
67 * @author Michael Martak
68 * @author Leif Samuelsson
69 * @author Kenneth Russell
70 * @since 1.4 */
71@SuppressWarnings("serial") // JDK-implementation class
72final 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 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        String path = invoke(new Callable<String>() {
587            public String call() throws IOException {
588                return getFileSystemPath0(csidl);
589            }
590        }, IOException.class);
591        if (path != null) {
592            SecurityManager security = System.getSecurityManager();
593            if (security != null) {
594                security.checkRead(path);
595            }
596        }
597        return path;
598    }
599
600    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
601    private static native String getFileSystemPath0(int csidl) throws IOException;
602
603    // Return whether the path is a network root.
604    // Path is assumed to be non-null
605    private static boolean isNetworkRoot(String path) {
606        return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/"));
607    }
608
609    /**
610     * @return The parent shell folder of this shell folder, null if
611     * there is no parent
612     */
613    public File getParentFile() {
614        return parent;
615    }
616
617    public boolean isDirectory() {
618        if (isDir == null) {
619            // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are
620            // not traversable in JFileChooser.
621            if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) {
622                isDir = Boolean.TRUE;
623            } else if (isLink()) {
624                ShellFolder linkLocation = getLinkLocation(false);
625                isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory());
626            } else {
627                isDir = Boolean.FALSE;
628            }
629        }
630        return isDir.booleanValue();
631    }
632
633    /*
634     * Functions for enumerating an IShellFolder's children
635     */
636    // Returns an IEnumIDList interface for an IShellFolder.  The value
637    // returned must be released using releaseEnumObjects().
638    private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException {
639        return invoke(new Callable<Long>() {
640            public Long call() {
641                boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder();
642
643                return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles);
644            }
645        }, RuntimeException.class);
646    }
647
648    // Returns an IEnumIDList interface for an IShellFolder.  The value
649    // returned must be released using releaseEnumObjects().
650    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
651    private native long getEnumObjects(long pIShellFolder, boolean isDesktop,
652                                       boolean includeHiddenFiles);
653    // Returns the next sequential child as a relative PIDL
654    // from an IEnumIDList interface.  The value returned must
655    // be released using releasePIDL().
656    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
657    private native long getNextChild(long pEnumObjects);
658    // Releases the IEnumIDList interface
659    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
660    private native void releaseEnumObjects(long pEnumObjects);
661
662    // Returns the IShellFolder of a child from a parent IShellFolder
663    // and a relative PIDL.  The value returned must be released
664    // using releaseIShellFolder().
665    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
666    private static native long bindToObject(long parentIShellFolder, long pIDL);
667
668    /**
669     * @return An array of shell folders that are children of this shell folder
670     *         object. The array will be empty if the folder is empty.  Returns
671     *         <code>null</code> if this shellfolder does not denote a directory.
672     */
673    public File[] listFiles(final boolean includeHiddenFiles) {
674        SecurityManager security = System.getSecurityManager();
675        if (security != null) {
676            security.checkRead(getPath());
677        }
678
679        try {
680            return invoke(new Callable<File[]>() {
681                public File[] call() throws InterruptedException {
682                    if (!isDirectory()) {
683                        return null;
684                    }
685                    // Links to directories are not directories and cannot be parents.
686                    // This does not apply to folders in My Network Places (NetHood)
687                    // because they are both links and real directories!
688                    if (isLink() && !hasAttribute(ATTRIB_FOLDER)) {
689                        return new File[0];
690                    }
691
692                    Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop();
693                    Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal();
694
695                    // If we are a directory, we have a parent and (at least) a
696                    // relative PIDL. We must first ensure we are bound to the
697                    // parent so we have an IShellFolder to query.
698                    long pIShellFolder = getIShellFolder();
699                    // Now we can enumerate the objects in this folder.
700                    ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>();
701                    long pEnumObjects = getEnumObjects(includeHiddenFiles);
702                    if (pEnumObjects != 0) {
703                        try {
704                            long childPIDL;
705                            int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR;
706                            do {
707                                childPIDL = getNextChild(pEnumObjects);
708                                boolean releasePIDL = true;
709                                if (childPIDL != 0 &&
710                                        (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) {
711                                    Win32ShellFolder2 childFolder;
712                                    if (Win32ShellFolder2.this.equals(desktop)
713                                            && personal != null
714                                            && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) {
715                                        childFolder = personal;
716                                    } else {
717                                        childFolder = new Win32ShellFolder2(Win32ShellFolder2.this, childPIDL);
718                                        releasePIDL = false;
719                                    }
720                                    list.add(childFolder);
721                                }
722                                if (releasePIDL) {
723                                    releasePIDL(childPIDL);
724                                }
725                            } while (childPIDL != 0 && !Thread.currentThread().isInterrupted());
726                        } finally {
727                            releaseEnumObjects(pEnumObjects);
728                        }
729                    }
730                    return Thread.currentThread().isInterrupted()
731                        ? new File[0]
732                        : list.toArray(new ShellFolder[list.size()]);
733                }
734            }, InterruptedException.class);
735        } catch (InterruptedException e) {
736            return new File[0];
737        }
738    }
739
740
741    /**
742     * Look for (possibly special) child folder by it's path
743     *
744     * @return The child shellfolder, or null if not found.
745     */
746    Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
747        return invoke(new Callable<Win32ShellFolder2>() {
748            public Win32ShellFolder2 call() throws InterruptedException {
749                long pIShellFolder = getIShellFolder();
750                long pEnumObjects = getEnumObjects(true);
751                Win32ShellFolder2 child = null;
752                long childPIDL;
753
754                while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
755                    if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
756                        String path = getFileSystemPath(pIShellFolder, childPIDL);
757                        if (path != null && path.equalsIgnoreCase(filePath)) {
758                            long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
759                            child = new Win32ShellFolder2(Win32ShellFolder2.this,
760                                    childIShellFolder, childPIDL, path);
761                            break;
762                        }
763                    }
764                    releasePIDL(childPIDL);
765                }
766                releaseEnumObjects(pEnumObjects);
767                return child;
768            }
769        }, InterruptedException.class);
770    }
771
772    private volatile Boolean cachedIsLink;
773
774    /**
775     * @return Whether this shell folder is a link
776     */
777    public boolean isLink() {
778        if (cachedIsLink == null) {
779            cachedIsLink = hasAttribute(ATTRIB_LINK);
780        }
781
782        return cachedIsLink;
783    }
784
785    /**
786     * @return Whether this shell folder is marked as hidden
787     */
788    public boolean isHidden() {
789        return hasAttribute(ATTRIB_HIDDEN);
790    }
791
792
793    // Return the link location of a shell folder
794    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
795    private static native long getLinkLocation(long parentIShellFolder,
796                                        long relativePIDL, boolean resolve);
797
798    /**
799     * @return The shell folder linked to by this shell folder, or null
800     * if this shell folder is not a link or is a broken or invalid link
801     */
802    public ShellFolder getLinkLocation()  {
803        return getLinkLocation(true);
804    }
805
806    private ShellFolder getLinkLocation(final boolean resolve) {
807        return invoke(new Callable<ShellFolder>() {
808            public ShellFolder call() {
809                if (!isLink()) {
810                    return null;
811                }
812
813                ShellFolder location = null;
814                long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
815                        getRelativePIDL(), resolve);
816                if (linkLocationPIDL != 0) {
817                    try {
818                        location =
819                                Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
820                                        linkLocationPIDL);
821                    } catch (InterruptedException e) {
822                        // Return null
823                    } catch (InternalError e) {
824                        // Could be a link to a non-bindable object, such as a network connection
825                        // TODO: getIShellFolder() should throw FileNotFoundException instead
826                    }
827                }
828                return location;
829            }
830        });
831    }
832
833    // Parse a display name into a PIDL relative to the current IShellFolder.
834    long parseDisplayName(final String name) throws IOException, InterruptedException {
835        return invoke(new Callable<Long>() {
836            public Long call() throws IOException {
837                return parseDisplayName0(getIShellFolder(), name);
838            }
839        }, IOException.class);
840    }
841
842    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
843    private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;
844
845    // Return the display name of a shell folder
846    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
847    private static native String getDisplayNameOf(long parentIShellFolder,
848                                                  long relativePIDL,
849                                                  int attrs);
850
851    /**
852     * @return The name used to display this shell folder
853     */
854    public String getDisplayName() {
855        if (displayName == null) {
856            displayName =
857                invoke(new Callable<String>() {
858                    public String call() {
859                        return getDisplayNameOf(getParentIShellFolder(),
860                                getRelativePIDL(), SHGDN_NORMAL);
861                    }
862                });
863        }
864        return displayName;
865    }
866
867    // Return the folder type of a shell folder
868    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
869    private static native String getFolderType(long pIDL);
870
871    /**
872     * @return The type of shell folder as a string
873     */
874    public String getFolderType() {
875        if (folderType == null) {
876            final long absolutePIDL = getAbsolutePIDL();
877            folderType =
878                invoke(new Callable<String>() {
879                    public String call() {
880                        return getFolderType(absolutePIDL);
881                    }
882                });
883        }
884        return folderType;
885    }
886
887    // Return the executable type of a file system shell folder
888    private native String getExecutableType(String path);
889
890    /**
891     * @return The executable type as a string
892     */
893    public String getExecutableType() {
894        if (!isFileSystem()) {
895            return null;
896        }
897        return getExecutableType(getAbsolutePath());
898    }
899
900
901
902    // Icons
903
904    private static Map<Integer, Image> smallSystemImages = new HashMap<>();
905    private static Map<Integer, Image> largeSystemImages = new HashMap<>();
906    private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>();
907    private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>();
908
909    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
910    private static native long getIShellIcon(long pIShellFolder);
911
912    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
913    private static native int getIconIndex(long parentIShellIcon, long relativePIDL);
914
915    // Return the icon of a file system shell folder in the form of an HICON
916    private static native long getIcon(String absolutePath, boolean getLargeIcon);
917
918    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
919    private static native long extractIcon(long parentIShellFolder, long relativePIDL,
920                                           boolean getLargeIcon);
921
922    // Returns an icon from the Windows system icon list in the form of an HICON
923    private static native long getSystemIcon(int iconID);
924    private static native long getIconResource(String libName, int iconID,
925                                               int cxDesired, int cyDesired,
926                                               boolean useVGAColors);
927                                               // Note: useVGAColors is ignored on XP and later
928
929    // Return the bits from an HICON.  This has a side effect of setting
930    // the imageHash variable for efficient caching / comparing.
931    private static native int[] getIconBits(long hIcon, int iconSize);
932    // Dispose the HICON
933    private static native void disposeIcon(long hIcon);
934
935    static native int[] getStandardViewButton0(int iconIndex);
936
937    // Should be called from the COM thread
938    private long getIShellIcon() {
939        if (pIShellIcon == -1L) {
940            pIShellIcon = getIShellIcon(getIShellFolder());
941        }
942
943        return pIShellIcon;
944    }
945
946    private static Image makeIcon(long hIcon, boolean getLargeIcon) {
947        if (hIcon != 0L && hIcon != -1L) {
948            // Get the bits.  This has the side effect of setting the imageHash value for this object.
949            int size = getLargeIcon ? 32 : 16;
950            int[] iconBits = getIconBits(hIcon, size);
951            if (iconBits != null) {
952                BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
953                img.setRGB(0, 0, size, size, iconBits, 0, size);
954                return img;
955            }
956        }
957        return null;
958    }
959
960
961    /**
962     * @return The icon image used to display this shell folder
963     */
964    public Image getIcon(final boolean getLargeIcon) {
965        Image icon = getLargeIcon ? largeIcon : smallIcon;
966        if (icon == null) {
967            icon =
968                invoke(new Callable<Image>() {
969                    public Image call() {
970                        Image newIcon = null;
971                        if (isFileSystem()) {
972                            long parentIShellIcon = (parent != null)
973                                ? ((Win32ShellFolder2) parent).getIShellIcon()
974                                : 0L;
975                            long relativePIDL = getRelativePIDL();
976
977                            // These are cached per type (using the index in the system image list)
978                            int index = getIconIndex(parentIShellIcon, relativePIDL);
979                            if (index > 0) {
980                                Map<Integer, Image> imageCache;
981                                if (isLink()) {
982                                    imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
983                                } else {
984                                    imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
985                                }
986                                newIcon = imageCache.get(Integer.valueOf(index));
987                                if (newIcon == null) {
988                                    long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
989                                    newIcon = makeIcon(hIcon, getLargeIcon);
990                                    disposeIcon(hIcon);
991                                    if (newIcon != null) {
992                                        imageCache.put(Integer.valueOf(index), newIcon);
993                                    }
994                                }
995                            }
996                        }
997
998                        if (newIcon == null) {
999                            // These are only cached per object
1000                            long hIcon = extractIcon(getParentIShellFolder(),
1001                                getRelativePIDL(), getLargeIcon);
1002                            newIcon = makeIcon(hIcon, getLargeIcon);
1003                            disposeIcon(hIcon);
1004                        }
1005
1006                        if (newIcon == null) {
1007                            newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon);
1008                        }
1009                        return newIcon;
1010                    }
1011                });
1012            if (getLargeIcon) {
1013                largeIcon = icon;
1014            } else {
1015                smallIcon = icon;
1016            }
1017        }
1018        return icon;
1019    }
1020
1021    /**
1022     * Gets an icon from the Windows system icon list as an <code>Image</code>
1023     */
1024    static Image getSystemIcon(SystemIcon iconType) {
1025        long hIcon = getSystemIcon(iconType.getIconID());
1026        Image icon = makeIcon(hIcon, true);
1027        disposeIcon(hIcon);
1028        return icon;
1029    }
1030
1031    /**
1032     * Gets an icon from the Windows system icon list as an <code>Image</code>
1033     */
1034    static Image getShell32Icon(int iconID, boolean getLargeIcon) {
1035        boolean useVGAColors = true; // Will be ignored on XP and later
1036
1037        int size = getLargeIcon ? 32 : 16;
1038
1039        Toolkit toolkit = Toolkit.getDefaultToolkit();
1040        String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
1041        if (shellIconBPP != null) {
1042            useVGAColors = shellIconBPP.equals("4");
1043        }
1044
1045        long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
1046        if (hIcon != 0) {
1047            Image icon = makeIcon(hIcon, getLargeIcon);
1048            disposeIcon(hIcon);
1049            return icon;
1050        }
1051        return null;
1052    }
1053
1054    /**
1055     * Returns the canonical form of this abstract pathname.  Equivalent to
1056     * <code>new&nbsp;Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>.
1057     *
1058     * @see java.io.File#getCanonicalFile
1059     */
1060    public File getCanonicalFile() throws IOException {
1061        return this;
1062    }
1063
1064    /*
1065     * Indicates whether this is a special folder (includes My Documents)
1066     */
1067    public boolean isSpecial() {
1068        return isPersonal || !isFileSystem() || (this == getDesktop());
1069    }
1070
1071    /**
1072     * Compares this object with the specified object for order.
1073     *
1074     * @see sun.awt.shell.ShellFolder#compareTo(File)
1075     */
1076    public int compareTo(File file2) {
1077        if (!(file2 instanceof Win32ShellFolder2)) {
1078            if (isFileSystem() && !isSpecial()) {
1079                return super.compareTo(file2);
1080            } else {
1081                return -1; // Non-file shellfolders sort before files
1082            }
1083        }
1084        return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2);
1085    }
1086
1087    // native constants from commctrl.h
1088    private static final int LVCFMT_LEFT = 0;
1089    private static final int LVCFMT_RIGHT = 1;
1090    private static final int LVCFMT_CENTER = 2;
1091
1092    public ShellFolderColumnInfo[] getFolderColumns() {
1093        return invoke(new Callable<ShellFolderColumnInfo[]>() {
1094            public ShellFolderColumnInfo[] call() {
1095                ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder());
1096
1097                if (columns != null) {
1098                    List<ShellFolderColumnInfo> notNullColumns =
1099                            new ArrayList<ShellFolderColumnInfo>();
1100                    for (int i = 0; i < columns.length; i++) {
1101                        ShellFolderColumnInfo column = columns[i];
1102                        if (column != null) {
1103                            column.setAlignment(column.getAlignment() == LVCFMT_RIGHT
1104                                    ? SwingConstants.RIGHT
1105                                    : column.getAlignment() == LVCFMT_CENTER
1106                                    ? SwingConstants.CENTER
1107                                    : SwingConstants.LEADING);
1108
1109                            column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i));
1110
1111                            notNullColumns.add(column);
1112                        }
1113                    }
1114                    columns = new ShellFolderColumnInfo[notNullColumns.size()];
1115                    notNullColumns.toArray(columns);
1116                }
1117                return columns;
1118            }
1119        });
1120    }
1121
1122    public Object getFolderColumnValue(final int column) {
1123        return invoke(new Callable<Object>() {
1124            public Object call() {
1125                return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column);
1126            }
1127        });
1128    }
1129
1130    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1131    private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2);
1132
1133    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1134    private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx);
1135
1136    // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
1137    private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx);
1138
1139
1140    public void sortChildren(final List<? extends File> files) {
1141        // To avoid loads of synchronizations with Invoker and improve performance we
1142        // synchronize the whole code of the sort method once
1143        invoke(new Callable<Void>() {
1144            public Void call() {
1145                Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0));
1146
1147                return null;
1148            }
1149        });
1150    }
1151
1152    private static class ColumnComparator implements Comparator<File> {
1153        private final Win32ShellFolder2 shellFolder;
1154
1155        private final int columnIdx;
1156
1157        public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) {
1158            this.shellFolder = shellFolder;
1159            this.columnIdx = columnIdx;
1160        }
1161
1162        // compares 2 objects within this folder by the specified column
1163        public int compare(final File o, final File o1) {
1164            Integer result = invoke(new Callable<Integer>() {
1165                public Integer call() {
1166                    if (o instanceof Win32ShellFolder2
1167                        && o1 instanceof Win32ShellFolder2) {
1168                        // delegates comparison to native method
1169                        return compareIDsByColumn(shellFolder.getIShellFolder(),
1170                            ((Win32ShellFolder2) o).getRelativePIDL(),
1171                            ((Win32ShellFolder2) o1).getRelativePIDL(),
1172                            columnIdx);
1173                    }
1174                    return 0;
1175                }
1176            });
1177
1178            return result == null ? 0 : result;
1179        }
1180    }
1181}
1182