1 /* 2 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 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 @SuppressWarnings("serial") // JDK-implementation class 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 rest of the ID list 255 // i,e, parent's grandchilren and down 256 pIDL = getNextPIDLEntry(pIDL); 257 if (pIDL != 0) { 258 // Now we know that parent isn't immediate to 'this' because it 259 // has a continued ID list. Create a shell folder for this child 260 // pidl and make it the new 'parent'. 261 parent = new Win32ShellFolder2((Win32ShellFolder2) parent, childPIDL); 262 } else { 263 // No grandchildren means we have arrived at the parent of 'this', 264 // and childPIDL is directly relative to parent. 265 disposer.relativePIDL = childPIDL; 266 } 267 } else { 268 break; 269 } 270 } 271 } 272 return null; 273 } 274 }, InterruptedException.class); 275 276 sun.java2d.Disposer.addRecord(this, disposer); 277 } 278 279 280 /** 281 * Create a system shell folder 282 */ 283 Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path) { 284 super(parent, (path != null) ? path : "ShellFolder: "); 285 this.disposer.pIShellFolder = pIShellFolder; 286 this.disposer.relativePIDL = relativePIDL; 287 sun.java2d.Disposer.addRecord(this, disposer); 288 } 289 290 291 /** 292 * Creates a shell folder with a parent and relative PIDL 293 */ 294 Win32ShellFolder2(final Win32ShellFolder2 parent, final long relativePIDL) throws InterruptedException { 295 super(parent, 296 invoke(new Callable<String>() { 297 public String call() { 298 return getFileSystemPath(parent.getIShellFolder(), relativePIDL); 299 } 300 }, RuntimeException.class) 301 ); 302 this.disposer.relativePIDL = relativePIDL; 303 sun.java2d.Disposer.addRecord(this, disposer); 304 } 305 306 // Initializes the desktop shell folder 307 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 308 private native void initDesktop(); 309 310 // Initializes a special, non-file system shell folder 311 // from one of the above constants 312 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 313 private native void initSpecial(long desktopIShellFolder, int csidl); 314 315 /** Marks this folder as being the My Documents (Personal) folder */ 316 public void setIsPersonal() { 317 isPersonal = true; 318 } 319 320 /** 321 * This method is implemented to make sure that no instances 322 * of <code>ShellFolder</code> are ever serialized. If <code>isFileSystem()</code> returns 323 * <code>true</code>, then the object is representable with an instance of 324 * <code>java.io.File</code> instead. If not, then the object depends 325 * on native PIDL state and should not be serialized. 326 * 327 * @return a <code>java.io.File</code> replacement object. If the folder 328 * is a not a normal directory, then returns the first non-removable 329 * drive (normally "C:\"). 330 */ 331 protected Object writeReplace() throws java.io.ObjectStreamException { 332 return invoke(new Callable<File>() { 333 public File call() { 334 if (isFileSystem()) { 335 return new File(getPath()); 336 } else { 337 Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives(); 338 if (drives != null) { 339 File[] driveRoots = drives.listFiles(); 340 if (driveRoots != null) { 341 for (int i = 0; i < driveRoots.length; i++) { 342 if (driveRoots[i] instanceof Win32ShellFolder2) { 343 Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i]; 344 if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) { 345 return new File(sf.getPath()); 346 } 347 } 348 } 349 } 350 } 351 // Ouch, we have no hard drives. Return something "valid" anyway. 352 return new File("C:\\"); 353 } 354 } 355 }); 356 } 357 358 359 /** 360 * Finalizer to clean up any COM objects or PIDLs used by this object. 361 */ 362 protected void dispose() { 363 disposer.dispose(); 364 } 365 366 367 // Given a (possibly multi-level) relative PIDL (with respect to 368 // the desktop, at least in all of the usage cases in this code), 369 // return a pointer to the next entry. Does not mutate the PIDL in 370 // any way. Returns 0 if the null terminator is reached. 371 // Needs to be accessible to Win32ShellFolderManager2 372 static native long getNextPIDLEntry(long pIDL); 373 374 // Given a (possibly multi-level) relative PIDL (with respect to 375 // the desktop, at least in all of the usage cases in this code), 376 // copy the first entry into a newly-allocated PIDL. Returns 0 if 377 // the PIDL is at the end of the list. 378 // Needs to be accessible to Win32ShellFolderManager2 379 static native long copyFirstPIDLEntry(long pIDL); 380 381 // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL 382 private static native long combinePIDLs(long ppIDL, long pIDL); 383 384 // Release a PIDL object 385 // Needs to be accessible to Win32ShellFolderManager2 386 static native void releasePIDL(long pIDL); 387 388 // Release an IShellFolder object 389 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 390 private static native void releaseIShellFolder(long pIShellFolder); 391 392 /** 393 * Accessor for IShellFolder 394 */ 395 private long getIShellFolder() { 396 if (disposer.pIShellFolder == 0) { 397 try { 398 disposer.pIShellFolder = invoke(new Callable<Long>() { 399 public Long call() { 400 assert(isDirectory()); 401 assert(parent != null); 402 long parentIShellFolder = getParentIShellFolder(); 403 if (parentIShellFolder == 0) { 404 throw new InternalError("Parent IShellFolder was null for " 405 + getAbsolutePath()); 406 } 407 // We are a directory with a parent and a relative PIDL. 408 // We want to bind to the parent so we get an 409 // IShellFolder instance associated with us. 410 long pIShellFolder = bindToObject(parentIShellFolder, 411 disposer.relativePIDL); 412 if (pIShellFolder == 0) { 413 throw new InternalError("Unable to bind " 414 + getAbsolutePath() + " to parent"); 415 } 416 return pIShellFolder; 417 } 418 }, RuntimeException.class); 419 } catch (InterruptedException e) { 420 // Ignore error 421 } 422 } 423 return disposer.pIShellFolder; 424 } 425 426 /** 427 * Get the parent ShellFolder's IShellFolder interface 428 */ 429 public long getParentIShellFolder() { 430 Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile(); 431 if (parent == null) { 432 // Parent should only be null if this is the desktop, whose 433 // relativePIDL is relative to its own IShellFolder. 434 return getIShellFolder(); 435 } 436 return parent.getIShellFolder(); 437 } 438 439 /** 440 * Accessor for relative PIDL 441 */ 442 public long getRelativePIDL() { 443 if (disposer.relativePIDL == 0) { 444 throw new InternalError("Should always have a relative PIDL"); 445 } 446 return disposer.relativePIDL; 447 } 448 449 private long getAbsolutePIDL() { 450 if (parent == null) { 451 // This is the desktop 452 return getRelativePIDL(); 453 } else { 454 if (disposer.absolutePIDL == 0) { 455 disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL()); 456 } 457 458 return disposer.absolutePIDL; 459 } 460 } 461 462 /** 463 * Helper function to return the desktop 464 */ 465 public Win32ShellFolder2 getDesktop() { 466 return Win32ShellFolderManager2.getDesktop(); 467 } 468 469 /** 470 * Helper function to return the desktop IShellFolder interface 471 */ 472 public long getDesktopIShellFolder() { 473 return getDesktop().getIShellFolder(); 474 } 475 476 private static boolean pathsEqual(String path1, String path2) { 477 // Same effective implementation as Win32FileSystem 478 return path1.equalsIgnoreCase(path2); 479 } 480 481 /** 482 * Check to see if two ShellFolder objects are the same 483 */ 484 public boolean equals(Object o) { 485 if (o == null || !(o instanceof Win32ShellFolder2)) { 486 // Short-circuit circuitous delegation path 487 if (!(o instanceof File)) { 488 return super.equals(o); 489 } 490 return pathsEqual(getPath(), ((File) o).getPath()); 491 } 492 Win32ShellFolder2 rhs = (Win32ShellFolder2) o; 493 if ((parent == null && rhs.parent != null) || 494 (parent != null && rhs.parent == null)) { 495 return false; 496 } 497 498 if (isFileSystem() && rhs.isFileSystem()) { 499 // Only folders with identical parents can be equal 500 return (pathsEqual(getPath(), rhs.getPath()) && 501 (parent == rhs.parent || parent.equals(rhs.parent))); 502 } 503 504 if (parent == rhs.parent || parent.equals(rhs.parent)) { 505 try { 506 return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL); 507 } catch (InterruptedException e) { 508 return false; 509 } 510 } 511 512 return false; 513 } 514 515 private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2) 516 throws InterruptedException { 517 return invoke(new Callable<Boolean>() { 518 public Boolean call() { 519 return compareIDs(pIShellFolder, pidl1, pidl2) == 0; 520 } 521 }, RuntimeException.class); 522 } 523 524 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 525 private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2); 526 527 private volatile Boolean cachedIsFileSystem; 528 529 /** 530 * @return Whether this is a file system shell folder 531 */ 532 public boolean isFileSystem() { 533 if (cachedIsFileSystem == null) { 534 cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM); 535 } 536 537 return cachedIsFileSystem; 538 } 539 540 /** 541 * Return whether the given attribute flag is set for this object 542 */ 543 public boolean hasAttribute(final int attribute) { 544 Boolean result = invoke(new Callable<Boolean>() { 545 public Boolean call() { 546 // Caching at this point doesn't seem to be cost efficient 547 return (getAttributes0(getParentIShellFolder(), 548 getRelativePIDL(), attribute) 549 & attribute) != 0; 550 } 551 }); 552 553 return result != null && result; 554 } 555 556 /** 557 * Returns the queried attributes specified in attrsMask. 558 * 559 * Could plausibly be used for attribute caching but have to be 560 * very careful not to touch network drives and file system roots 561 * with a full attrsMask 562 * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 563 */ 564 565 private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask); 566 567 // Return the path to the underlying file system object 568 // Should be called from the COM thread 569 private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) { 570 int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER; 571 if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() && 572 getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) { 573 574 String s = 575 getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(), 576 getLinkLocation(parentIShellFolder, relativePIDL, false)); 577 if (s != null && s.startsWith("\\\\")) { 578 return s; 579 } 580 } 581 return getDisplayNameOf(parentIShellFolder, relativePIDL, SHGDN_FORPARSING); 582 } 583 584 // Needs to be accessible to Win32ShellFolderManager2 585 static String getFileSystemPath(final int csidl) throws IOException, InterruptedException { 586 String path = invoke(new Callable<String>() { 587 public String call() throws IOException { 588 return getFileSystemPath0(csidl); 589 } 590 }, IOException.class); 591 if (path != null) { 592 SecurityManager security = System.getSecurityManager(); 593 if (security != null) { 594 security.checkRead(path); 595 } 596 } 597 return path; 598 } 599 600 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 601 private static native String getFileSystemPath0(int csidl) throws IOException; 602 603 // Return whether the path is a network root. 604 // Path is assumed to be non-null 605 private static boolean isNetworkRoot(String path) { 606 return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/")); 607 } 608 609 /** 610 * @return The parent shell folder of this shell folder, null if 611 * there is no parent 612 */ 613 public File getParentFile() { 614 return parent; 615 } 616 617 public boolean isDirectory() { 618 if (isDir == null) { 619 // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are 620 // not traversable in JFileChooser. 621 if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) { 622 isDir = Boolean.TRUE; 623 } else if (isLink()) { 624 ShellFolder linkLocation = getLinkLocation(false); 625 isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory()); 626 } else { 627 isDir = Boolean.FALSE; 628 } 629 } 630 return isDir.booleanValue(); 631 } 632 633 /* 634 * Functions for enumerating an IShellFolder's children 635 */ 636 // Returns an IEnumIDList interface for an IShellFolder. The value 637 // returned must be released using releaseEnumObjects(). 638 private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException { 639 return invoke(new Callable<Long>() { 640 public Long call() { 641 boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder(); 642 643 return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles); 644 } 645 }, RuntimeException.class); 646 } 647 648 // Returns an IEnumIDList interface for an IShellFolder. The value 649 // returned must be released using releaseEnumObjects(). 650 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 651 private native long getEnumObjects(long pIShellFolder, boolean isDesktop, 652 boolean includeHiddenFiles); 653 // Returns the next sequential child as a relative PIDL 654 // from an IEnumIDList interface. The value returned must 655 // be released using releasePIDL(). 656 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 657 private native long getNextChild(long pEnumObjects); 658 // Releases the IEnumIDList interface 659 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 660 private native void releaseEnumObjects(long pEnumObjects); 661 662 // Returns the IShellFolder of a child from a parent IShellFolder 663 // and a relative PIDL. The value returned must be released 664 // using releaseIShellFolder(). 665 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 666 private static native long bindToObject(long parentIShellFolder, long pIDL); 667 668 /** 669 * @return An array of shell folders that are children of this shell folder 670 * object. The array will be empty if the folder is empty. Returns 671 * <code>null</code> if this shellfolder does not denote a directory. 672 */ 673 public File[] listFiles(final boolean includeHiddenFiles) { 674 SecurityManager security = System.getSecurityManager(); 675 if (security != null) { 676 security.checkRead(getPath()); 677 } 678 679 try { 680 return invoke(new Callable<File[]>() { 681 public File[] call() throws InterruptedException { 682 if (!isDirectory()) { 683 return null; 684 } 685 // Links to directories are not directories and cannot be parents. 686 // This does not apply to folders in My Network Places (NetHood) 687 // because they are both links and real directories! 688 if (isLink() && !hasAttribute(ATTRIB_FOLDER)) { 689 return new File[0]; 690 } 691 692 Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop(); 693 Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal(); 694 695 // If we are a directory, we have a parent and (at least) a 696 // relative PIDL. We must first ensure we are bound to the 697 // parent so we have an IShellFolder to query. 698 long pIShellFolder = getIShellFolder(); 699 // Now we can enumerate the objects in this folder. 700 ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>(); 701 long pEnumObjects = getEnumObjects(includeHiddenFiles); 702 if (pEnumObjects != 0) { 703 try { 704 long childPIDL; 705 int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR; 706 do { 707 childPIDL = getNextChild(pEnumObjects); 708 boolean releasePIDL = true; 709 if (childPIDL != 0 && 710 (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) { 711 Win32ShellFolder2 childFolder; 712 if (Win32ShellFolder2.this.equals(desktop) 713 && personal != null 714 && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) { 715 childFolder = personal; 716 } else { 717 childFolder = new Win32ShellFolder2(Win32ShellFolder2.this, childPIDL); 718 releasePIDL = false; 719 } 720 list.add(childFolder); 721 } 722 if (releasePIDL) { 723 releasePIDL(childPIDL); 724 } 725 } while (childPIDL != 0 && !Thread.currentThread().isInterrupted()); 726 } finally { 727 releaseEnumObjects(pEnumObjects); 728 } 729 } 730 return Thread.currentThread().isInterrupted() 731 ? new File[0] 732 : list.toArray(new ShellFolder[list.size()]); 733 } 734 }, InterruptedException.class); 735 } catch (InterruptedException e) { 736 return new File[0]; 737 } 738 } 739 740 741 /** 742 * Look for (possibly special) child folder by it's path 743 * 744 * @return The child shellfolder, or null if not found. 745 */ 746 Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException { 747 return invoke(new Callable<Win32ShellFolder2>() { 748 public Win32ShellFolder2 call() throws InterruptedException { 749 long pIShellFolder = getIShellFolder(); 750 long pEnumObjects = getEnumObjects(true); 751 Win32ShellFolder2 child = null; 752 long childPIDL; 753 754 while ((childPIDL = getNextChild(pEnumObjects)) != 0) { 755 if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) { 756 String path = getFileSystemPath(pIShellFolder, childPIDL); 757 if (path != null && path.equalsIgnoreCase(filePath)) { 758 long childIShellFolder = bindToObject(pIShellFolder, childPIDL); 759 child = new Win32ShellFolder2(Win32ShellFolder2.this, 760 childIShellFolder, childPIDL, path); 761 break; 762 } 763 } 764 releasePIDL(childPIDL); 765 } 766 releaseEnumObjects(pEnumObjects); 767 return child; 768 } 769 }, InterruptedException.class); 770 } 771 772 private volatile Boolean cachedIsLink; 773 774 /** 775 * @return Whether this shell folder is a link 776 */ 777 public boolean isLink() { 778 if (cachedIsLink == null) { 779 cachedIsLink = hasAttribute(ATTRIB_LINK); 780 } 781 782 return cachedIsLink; 783 } 784 785 /** 786 * @return Whether this shell folder is marked as hidden 787 */ 788 public boolean isHidden() { 789 return hasAttribute(ATTRIB_HIDDEN); 790 } 791 792 793 // Return the link location of a shell folder 794 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 795 private static native long getLinkLocation(long parentIShellFolder, 796 long relativePIDL, boolean resolve); 797 798 /** 799 * @return The shell folder linked to by this shell folder, or null 800 * if this shell folder is not a link or is a broken or invalid link 801 */ 802 public ShellFolder getLinkLocation() { 803 return getLinkLocation(true); 804 } 805 806 private ShellFolder getLinkLocation(final boolean resolve) { 807 return invoke(new Callable<ShellFolder>() { 808 public ShellFolder call() { 809 if (!isLink()) { 810 return null; 811 } 812 813 ShellFolder location = null; 814 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(), 815 getRelativePIDL(), resolve); 816 if (linkLocationPIDL != 0) { 817 try { 818 location = 819 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(), 820 linkLocationPIDL); 821 } catch (InterruptedException e) { 822 // Return null 823 } catch (InternalError e) { 824 // Could be a link to a non-bindable object, such as a network connection 825 // TODO: getIShellFolder() should throw FileNotFoundException instead 826 } 827 } 828 return location; 829 } 830 }); 831 } 832 833 // Parse a display name into a PIDL relative to the current IShellFolder. 834 long parseDisplayName(final String name) throws IOException, InterruptedException { 835 return invoke(new Callable<Long>() { 836 public Long call() throws IOException { 837 return parseDisplayName0(getIShellFolder(), name); 838 } 839 }, IOException.class); 840 } 841 842 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 843 private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException; 844 845 // Return the display name of a shell folder 846 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 847 private static native String getDisplayNameOf(long parentIShellFolder, 848 long relativePIDL, 849 int attrs); 850 851 /** 852 * @return The name used to display this shell folder 853 */ 854 public String getDisplayName() { 855 if (displayName == null) { 856 displayName = 857 invoke(new Callable<String>() { 858 public String call() { 859 return getDisplayNameOf(getParentIShellFolder(), 860 getRelativePIDL(), SHGDN_NORMAL); 861 } 862 }); 863 } 864 return displayName; 865 } 866 867 // Return the folder type of a shell folder 868 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 869 private static native String getFolderType(long pIDL); 870 871 /** 872 * @return The type of shell folder as a string 873 */ 874 public String getFolderType() { 875 if (folderType == null) { 876 final long absolutePIDL = getAbsolutePIDL(); 877 folderType = 878 invoke(new Callable<String>() { 879 public String call() { 880 return getFolderType(absolutePIDL); 881 } 882 }); 883 } 884 return folderType; 885 } 886 887 // Return the executable type of a file system shell folder 888 private native String getExecutableType(String path); 889 890 /** 891 * @return The executable type as a string 892 */ 893 public String getExecutableType() { 894 if (!isFileSystem()) { 895 return null; 896 } 897 return getExecutableType(getAbsolutePath()); 898 } 899 900 901 902 // Icons 903 904 private static Map<Integer, Image> smallSystemImages = new HashMap<>(); 905 private static Map<Integer, Image> largeSystemImages = new HashMap<>(); 906 private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>(); 907 private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>(); 908 909 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 910 private static native long getIShellIcon(long pIShellFolder); 911 912 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 913 private static native int getIconIndex(long parentIShellIcon, long relativePIDL); 914 915 // Return the icon of a file system shell folder in the form of an HICON 916 private static native long getIcon(String absolutePath, boolean getLargeIcon); 917 918 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 919 private static native long extractIcon(long parentIShellFolder, long relativePIDL, 920 boolean getLargeIcon); 921 922 // Returns an icon from the Windows system icon list in the form of an HICON 923 private static native long getSystemIcon(int iconID); 924 private static native long getIconResource(String libName, int iconID, 925 int cxDesired, int cyDesired, 926 boolean useVGAColors); 927 // Note: useVGAColors is ignored on XP and later 928 929 // Return the bits from an HICON. This has a side effect of setting 930 // the imageHash variable for efficient caching / comparing. 931 private static native int[] getIconBits(long hIcon, int iconSize); 932 // Dispose the HICON 933 private static native void disposeIcon(long hIcon); 934 935 static native int[] getStandardViewButton0(int iconIndex); 936 937 // Should be called from the COM thread 938 private long getIShellIcon() { 939 if (pIShellIcon == -1L) { 940 pIShellIcon = getIShellIcon(getIShellFolder()); 941 } 942 943 return pIShellIcon; 944 } 945 946 private static Image makeIcon(long hIcon, boolean getLargeIcon) { 947 if (hIcon != 0L && hIcon != -1L) { 948 // Get the bits. This has the side effect of setting the imageHash value for this object. 949 int size = getLargeIcon ? 32 : 16; 950 int[] iconBits = getIconBits(hIcon, size); 951 if (iconBits != null) { 952 BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 953 img.setRGB(0, 0, size, size, iconBits, 0, size); 954 return img; 955 } 956 } 957 return null; 958 } 959 960 961 /** 962 * @return The icon image used to display this shell folder 963 */ 964 public Image getIcon(final boolean getLargeIcon) { 965 Image icon = getLargeIcon ? largeIcon : smallIcon; 966 if (icon == null) { 967 icon = 968 invoke(new Callable<Image>() { 969 public Image call() { 970 Image newIcon = null; 971 if (isFileSystem()) { 972 long parentIShellIcon = (parent != null) 973 ? ((Win32ShellFolder2) parent).getIShellIcon() 974 : 0L; 975 long relativePIDL = getRelativePIDL(); 976 977 // These are cached per type (using the index in the system image list) 978 int index = getIconIndex(parentIShellIcon, relativePIDL); 979 if (index > 0) { 980 Map<Integer, Image> imageCache; 981 if (isLink()) { 982 imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages; 983 } else { 984 imageCache = getLargeIcon ? largeSystemImages : smallSystemImages; 985 } 986 newIcon = imageCache.get(Integer.valueOf(index)); 987 if (newIcon == null) { 988 long hIcon = getIcon(getAbsolutePath(), getLargeIcon); 989 newIcon = makeIcon(hIcon, getLargeIcon); 990 disposeIcon(hIcon); 991 if (newIcon != null) { 992 imageCache.put(Integer.valueOf(index), newIcon); 993 } 994 } 995 } 996 } 997 998 if (newIcon == null) { 999 // These are only cached per object 1000 long hIcon = extractIcon(getParentIShellFolder(), 1001 getRelativePIDL(), getLargeIcon); 1002 newIcon = makeIcon(hIcon, getLargeIcon); 1003 disposeIcon(hIcon); 1004 } 1005 1006 if (newIcon == null) { 1007 newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon); 1008 } 1009 return newIcon; 1010 } 1011 }); 1012 if (getLargeIcon) { 1013 largeIcon = icon; 1014 } else { 1015 smallIcon = icon; 1016 } 1017 } 1018 return icon; 1019 } 1020 1021 /** 1022 * Gets an icon from the Windows system icon list as an <code>Image</code> 1023 */ 1024 static Image getSystemIcon(SystemIcon iconType) { 1025 long hIcon = getSystemIcon(iconType.getIconID()); 1026 Image icon = makeIcon(hIcon, true); 1027 disposeIcon(hIcon); 1028 return icon; 1029 } 1030 1031 /** 1032 * Gets an icon from the Windows system icon list as an <code>Image</code> 1033 */ 1034 static Image getShell32Icon(int iconID, boolean getLargeIcon) { 1035 boolean useVGAColors = true; // Will be ignored on XP and later 1036 1037 int size = getLargeIcon ? 32 : 16; 1038 1039 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1040 String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP"); 1041 if (shellIconBPP != null) { 1042 useVGAColors = shellIconBPP.equals("4"); 1043 } 1044 1045 long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors); 1046 if (hIcon != 0) { 1047 Image icon = makeIcon(hIcon, getLargeIcon); 1048 disposeIcon(hIcon); 1049 return icon; 1050 } 1051 return null; 1052 } 1053 1054 /** 1055 * Returns the canonical form of this abstract pathname. Equivalent to 1056 * <code>new 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 }