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