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