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