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