1 /* 2 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.awt.shell; 27 28 import java.awt.Image; 29 import java.awt.Toolkit; 30 import java.awt.image.BufferedImage; 31 import java.io.File; 32 import java.io.IOException; 33 import java.util.*; 34 import java.util.concurrent.*; 35 import javax.swing.SwingConstants; 36 37 // NOTE: This class supersedes Win32ShellFolder, which was removed from 38 // distribution after version 1.4.2. 39 40 /** 41 * Win32 Shell Folders 42 * <P> 43 * <BR> 44 * There are two fundamental types of shell folders : file system folders 45 * and non-file system folders. File system folders are relatively easy 46 * to deal with. Non-file system folders are items such as My Computer, 47 * Network Neighborhood, and the desktop. Some of these non-file system 48 * folders have special values and properties. 49 * <P> 50 * <BR> 51 * Win32 keeps two basic data structures for shell folders. The first 52 * of these is called an ITEMIDLIST. Usually a pointer, called an 53 * LPITEMIDLIST, or more frequently just "PIDL". This structure holds 54 * a series of identifiers and can be either relative to the desktop 55 * (an absolute PIDL), or relative to the shell folder that contains them. 56 * Some Win32 functions can take absolute or relative PIDL values, and 57 * others can only accept relative values. 58 * <BR> 59 * The second data structure is an IShellFolder COM interface. Using 60 * this interface, one can enumerate the relative PIDLs in a shell 61 * folder, get attributes, etc. 62 * <BR> 63 * All Win32ShellFolder2 objects which are folder types (even non-file 64 * system folders) contain an IShellFolder object. Files are named in 65 * directories via relative PIDLs. 66 * 67 * @author Michael Martak 68 * @author Leif Samuelsson 69 * @author Kenneth Russell 70 * @since 1.4 */ 71 @SuppressWarnings("serial") // JDK-implementation class 72 final class Win32ShellFolder2 extends ShellFolder { 73 74 private static native void initIDs(); 75 76 static { 77 initIDs(); 78 } 79 80 // Win32 Shell Folder Constants 81 public static final int DESKTOP = 0x0000; 82 public static final int INTERNET = 0x0001; 83 public static final int PROGRAMS = 0x0002; 84 public static final int CONTROLS = 0x0003; 85 public static final int PRINTERS = 0x0004; 86 public static final int PERSONAL = 0x0005; 87 public static final int FAVORITES = 0x0006; 88 public static final int STARTUP = 0x0007; 89 public static final int RECENT = 0x0008; 90 public static final int SENDTO = 0x0009; 91 public static final int BITBUCKET = 0x000a; 92 public static final int STARTMENU = 0x000b; 93 public static final int DESKTOPDIRECTORY = 0x0010; 94 public static final int DRIVES = 0x0011; 95 public static final int NETWORK = 0x0012; 96 public static final int NETHOOD = 0x0013; 97 public static final int FONTS = 0x0014; 98 public static final int TEMPLATES = 0x0015; 99 public static final int COMMON_STARTMENU = 0x0016; 100 public static final int COMMON_PROGRAMS = 0X0017; 101 public static final int COMMON_STARTUP = 0x0018; 102 public static final int COMMON_DESKTOPDIRECTORY = 0x0019; 103 public static final int APPDATA = 0x001a; 104 public static final int PRINTHOOD = 0x001b; 105 public static final int ALTSTARTUP = 0x001d; 106 public static final int COMMON_ALTSTARTUP = 0x001e; 107 public static final int COMMON_FAVORITES = 0x001f; 108 public static final int INTERNET_CACHE = 0x0020; 109 public static final int COOKIES = 0x0021; 110 public static final int HISTORY = 0x0022; 111 112 // Win32 shell folder attributes 113 public static final int ATTRIB_CANCOPY = 0x00000001; 114 public static final int ATTRIB_CANMOVE = 0x00000002; 115 public static final int ATTRIB_CANLINK = 0x00000004; 116 public static final int ATTRIB_CANRENAME = 0x00000010; 117 public static final int ATTRIB_CANDELETE = 0x00000020; 118 public static final int ATTRIB_HASPROPSHEET = 0x00000040; 119 public static final int ATTRIB_DROPTARGET = 0x00000100; 120 public static final int ATTRIB_LINK = 0x00010000; 121 public static final int ATTRIB_SHARE = 0x00020000; 122 public static final int ATTRIB_READONLY = 0x00040000; 123 public static final int ATTRIB_GHOSTED = 0x00080000; 124 public static final int ATTRIB_HIDDEN = 0x00080000; 125 public static final int ATTRIB_FILESYSANCESTOR = 0x10000000; 126 public static final int ATTRIB_FOLDER = 0x20000000; 127 public static final int ATTRIB_FILESYSTEM = 0x40000000; 128 public static final int ATTRIB_HASSUBFOLDER = 0x80000000; 129 public static final int ATTRIB_VALIDATE = 0x01000000; 130 public static final int ATTRIB_REMOVABLE = 0x02000000; 131 public static final int ATTRIB_COMPRESSED = 0x04000000; 132 public static final int ATTRIB_BROWSABLE = 0x08000000; 133 public static final int ATTRIB_NONENUMERATED = 0x00100000; 134 public static final int ATTRIB_NEWCONTENT = 0x00200000; 135 136 // IShellFolder::GetDisplayNameOf constants 137 public static final int SHGDN_NORMAL = 0; 138 public static final int SHGDN_INFOLDER = 1; 139 public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000; 140 public static final int SHGDN_FORADDRESSBAR = 0x4000; 141 public static final int SHGDN_FORPARSING = 0x8000; 142 143 // Values for system call LoadIcon() 144 public enum SystemIcon { 145 IDI_APPLICATION(32512), 146 IDI_HAND(32513), 147 IDI_ERROR(32513), 148 IDI_QUESTION(32514), 149 IDI_EXCLAMATION(32515), 150 IDI_WARNING(32515), 151 IDI_ASTERISK(32516), 152 IDI_INFORMATION(32516), 153 IDI_WINLOGO(32517); 154 155 private final int iconID; 156 157 SystemIcon(int iconID) { 158 this.iconID = iconID; 159 } 160 161 public int getIconID() { 162 return iconID; 163 } 164 } 165 166 // 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); 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 (isFileSystem()) { 1011 long parentIShellIcon = (parent != null) 1012 ? ((Win32ShellFolder2) parent).getIShellIcon() 1013 : 0L; 1014 long relativePIDL = getRelativePIDL(); 1015 1016 // These are cached per type (using the index in the system image list) 1017 int index = getIconIndex(parentIShellIcon, relativePIDL); 1018 if (index > 0) { 1019 Map<Integer, Image> imageCache; 1020 if (isLink()) { 1021 imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages; 1022 } else { 1023 imageCache = getLargeIcon ? largeSystemImages : smallSystemImages; 1024 } 1025 newIcon = imageCache.get(Integer.valueOf(index)); 1026 if (newIcon == null) { 1027 long hIcon = getIcon(getAbsolutePath(), getLargeIcon); 1028 newIcon = makeIcon(hIcon, getLargeIcon); 1029 disposeIcon(hIcon); 1030 if (newIcon != null) { 1031 imageCache.put(Integer.valueOf(index), newIcon); 1032 } 1033 } 1034 } 1035 } 1036 1037 if (newIcon == null) { 1038 // These are only cached per object 1039 long hIcon = extractIcon(getParentIShellFolder(), 1040 getRelativePIDL(), getLargeIcon); 1041 newIcon = makeIcon(hIcon, getLargeIcon); 1042 disposeIcon(hIcon); 1043 } 1044 1045 if (newIcon == null) { 1046 newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon); 1047 } 1048 return newIcon; 1049 } 1050 }); 1051 if (getLargeIcon) { 1052 largeIcon = icon; 1053 } else { 1054 smallIcon = icon; 1055 } 1056 } 1057 return icon; 1058 } 1059 1060 /** 1061 * Gets an icon from the Windows system icon list as an <code>Image</code> 1062 */ 1063 static Image getSystemIcon(SystemIcon iconType) { 1064 long hIcon = getSystemIcon(iconType.getIconID()); 1065 Image icon = makeIcon(hIcon, true); 1066 disposeIcon(hIcon); 1067 return icon; 1068 } 1069 1070 /** 1071 * Gets an icon from the Windows system icon list as an <code>Image</code> 1072 */ 1073 static Image getShell32Icon(int iconID, boolean getLargeIcon) { 1074 boolean useVGAColors = true; // Will be ignored on XP and later 1075 1076 int size = getLargeIcon ? 32 : 16; 1077 1078 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1079 String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP"); 1080 if (shellIconBPP != null) { 1081 useVGAColors = shellIconBPP.equals("4"); 1082 } 1083 1084 long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors); 1085 if (hIcon != 0) { 1086 Image icon = makeIcon(hIcon, getLargeIcon); 1087 disposeIcon(hIcon); 1088 return icon; 1089 } 1090 return null; 1091 } 1092 1093 /** 1094 * Returns the canonical form of this abstract pathname. Equivalent to 1095 * <code>new Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>. 1096 * 1097 * @see java.io.File#getCanonicalFile 1098 */ 1099 public File getCanonicalFile() throws IOException { 1100 return this; 1101 } 1102 1103 /* 1104 * Indicates whether this is a special folder (includes My Documents) 1105 */ 1106 public boolean isSpecial() { 1107 return isPersonal || !isFileSystem() || (this == getDesktop()); 1108 } 1109 1110 /** 1111 * Compares this object with the specified object for order. 1112 * 1113 * @see sun.awt.shell.ShellFolder#compareTo(File) 1114 */ 1115 public int compareTo(File file2) { 1116 if (!(file2 instanceof Win32ShellFolder2)) { 1117 if (isFileSystem() && !isSpecial()) { 1118 return super.compareTo(file2); 1119 } else { 1120 return -1; // Non-file shellfolders sort before files 1121 } 1122 } 1123 return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2); 1124 } 1125 1126 // native constants from commctrl.h 1127 private static final int LVCFMT_LEFT = 0; 1128 private static final int LVCFMT_RIGHT = 1; 1129 private static final int LVCFMT_CENTER = 2; 1130 1131 public ShellFolderColumnInfo[] getFolderColumns() { 1132 return invoke(new Callable<ShellFolderColumnInfo[]>() { 1133 public ShellFolderColumnInfo[] call() { 1134 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder()); 1135 1136 if (columns != null) { 1137 List<ShellFolderColumnInfo> notNullColumns = 1138 new ArrayList<ShellFolderColumnInfo>(); 1139 for (int i = 0; i < columns.length; i++) { 1140 ShellFolderColumnInfo column = columns[i]; 1141 if (column != null) { 1142 column.setAlignment(column.getAlignment() == LVCFMT_RIGHT 1143 ? SwingConstants.RIGHT 1144 : column.getAlignment() == LVCFMT_CENTER 1145 ? SwingConstants.CENTER 1146 : SwingConstants.LEADING); 1147 1148 column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i)); 1149 1150 notNullColumns.add(column); 1151 } 1152 } 1153 columns = new ShellFolderColumnInfo[notNullColumns.size()]; 1154 notNullColumns.toArray(columns); 1155 } 1156 return columns; 1157 } 1158 }); 1159 } 1160 1161 public Object getFolderColumnValue(final int column) { 1162 return invoke(new Callable<Object>() { 1163 public Object call() { 1164 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column); 1165 } 1166 }); 1167 } 1168 1169 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1170 private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2); 1171 1172 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1173 private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx); 1174 1175 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1176 private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx); 1177 1178 1179 public void sortChildren(final List<? extends File> files) { 1180 // To avoid loads of synchronizations with Invoker and improve performance we 1181 // synchronize the whole code of the sort method once 1182 invoke(new Callable<Void>() { 1183 public Void call() { 1184 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0)); 1185 1186 return null; 1187 } 1188 }); 1189 } 1190 1191 private static class ColumnComparator implements Comparator<File> { 1192 private final Win32ShellFolder2 shellFolder; 1193 1194 private final int columnIdx; 1195 1196 public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) { 1197 this.shellFolder = shellFolder; 1198 this.columnIdx = columnIdx; 1199 } 1200 1201 // compares 2 objects within this folder by the specified column 1202 public int compare(final File o, final File o1) { 1203 Integer result = invoke(new Callable<Integer>() { 1204 public Integer call() { 1205 if (o instanceof Win32ShellFolder2 1206 && o1 instanceof Win32ShellFolder2) { 1207 // delegates comparison to native method 1208 return compareIDsByColumn(shellFolder.getIShellFolder(), 1209 ((Win32ShellFolder2) o).getRelativePIDL(), 1210 ((Win32ShellFolder2) o1).getRelativePIDL(), 1211 columnIdx); 1212 } 1213 return 0; 1214 } 1215 }); 1216 1217 return result == null ? 0 : result; 1218 } 1219 } 1220 1221 // Extracts libraries and their default save locations from Known Folders list 1222 private static List<KnownFolderDefinition> getLibraries() { 1223 return invoke(new Callable<List<KnownFolderDefinition>>() { 1224 @Override 1225 public List<KnownFolderDefinition> call() throws Exception { 1226 KnownFolderDefinition[] all = loadKnownFolders(); 1227 List<KnownFolderDefinition> folders = new ArrayList<>(); 1228 if (all != null) { 1229 for (KnownFolderDefinition kf : all) { 1230 if (kf.relativePath == null || kf.parsingName == null || 1231 kf.saveLocation == null) { 1232 continue; 1233 } 1234 folders.add(kf); 1235 } 1236 } 1237 return folders; 1238 } 1239 }); 1240 } 1241 1242 }