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