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         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 smallSystemImages = new HashMap();
 905     private static Map largeSystemImages = new HashMap();
 906     private static Map smallLinkedSystemImages = new HashMap();
 907     private static Map 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 imageCache;
 981                                 if (isLink()) {
 982                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
 983                                 } else {
 984                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
 985                                 }
 986                                 newIcon = (Image) 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 }