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 ShellFolder getLinkLocation(final boolean resolve) { 855 return invoke(new Callable<ShellFolder>() { 856 public ShellFolder call() { 857 if (!isLink()) { 858 return null; 859 } 860 861 ShellFolder 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); 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 (isFileSystem()) { 1023 long parentIShellIcon = (parent != null) 1024 ? ((Win32ShellFolder2) parent).getIShellIcon() 1025 : 0L; 1026 long relativePIDL = getRelativePIDL(); 1027 1028 // These are cached per type (using the index in the system image list) 1029 int index = getIconIndex(parentIShellIcon, relativePIDL); 1030 if (index > 0) { 1031 Map<Integer, Image> imageCache; 1032 if (isLink()) { 1033 imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages; 1034 } else { 1035 imageCache = getLargeIcon ? largeSystemImages : smallSystemImages; 1036 } 1037 newIcon = imageCache.get(Integer.valueOf(index)); 1038 if (newIcon == null) { 1039 long hIcon = getIcon(getAbsolutePath(), getLargeIcon); 1040 newIcon = makeIcon(hIcon, getLargeIcon); 1041 disposeIcon(hIcon); 1042 if (newIcon != null) { 1043 imageCache.put(Integer.valueOf(index), newIcon); 1044 } 1045 } 1046 } 1047 } 1048 1049 if (newIcon == null) { 1050 // These are only cached per object 1051 long hIcon = extractIcon(getParentIShellFolder(), 1052 getRelativePIDL(), getLargeIcon); 1053 newIcon = makeIcon(hIcon, getLargeIcon); 1054 disposeIcon(hIcon); 1055 } 1056 1057 if (newIcon == null) { 1058 newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon); 1059 } 1060 return newIcon; 1061 } 1062 }); 1063 if (getLargeIcon) { 1064 largeIcon = icon; 1065 } else { 1066 smallIcon = icon; 1067 } 1068 } 1069 return icon; 1070 } 1071 1072 /** 1073 * Gets an icon from the Windows system icon list as an <code>Image</code> 1074 */ 1075 static Image getSystemIcon(SystemIcon iconType) { 1076 long hIcon = getSystemIcon(iconType.getIconID()); 1077 Image icon = makeIcon(hIcon, true); 1078 disposeIcon(hIcon); 1079 return icon; 1080 } 1081 1082 /** 1083 * Gets an icon from the Windows system icon list as an <code>Image</code> 1084 */ 1085 static Image getShell32Icon(int iconID, boolean getLargeIcon) { 1086 boolean useVGAColors = true; // Will be ignored on XP and later 1087 1088 int size = getLargeIcon ? 32 : 16; 1089 1090 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1091 String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP"); 1092 if (shellIconBPP != null) { 1093 useVGAColors = shellIconBPP.equals("4"); 1094 } 1095 1096 long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors); 1097 if (hIcon != 0) { 1098 Image icon = makeIcon(hIcon, getLargeIcon); 1099 disposeIcon(hIcon); 1100 return icon; 1101 } 1102 return null; 1103 } 1104 1105 /** 1106 * Returns the canonical form of this abstract pathname. Equivalent to 1107 * <code>new Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>. 1108 * 1109 * @see java.io.File#getCanonicalFile 1110 */ 1111 public File getCanonicalFile() throws IOException { 1112 return this; 1113 } 1114 1115 /* 1116 * Indicates whether this is a special folder (includes My Documents) 1117 */ 1118 public boolean isSpecial() { 1119 return isPersonal || !isFileSystem() || (this == getDesktop()); 1120 } 1121 1122 /** 1123 * Compares this object with the specified object for order. 1124 * 1125 * @see sun.awt.shell.ShellFolder#compareTo(File) 1126 */ 1127 public int compareTo(File file2) { 1128 if (!(file2 instanceof Win32ShellFolder2)) { 1129 if (isFileSystem() && !isSpecial()) { 1130 return super.compareTo(file2); 1131 } else { 1132 return -1; // Non-file shellfolders sort before files 1133 } 1134 } 1135 return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2); 1136 } 1137 1138 // native constants from commctrl.h 1139 private static final int LVCFMT_LEFT = 0; 1140 private static final int LVCFMT_RIGHT = 1; 1141 private static final int LVCFMT_CENTER = 2; 1142 1143 public ShellFolderColumnInfo[] getFolderColumns() { 1144 ShellFolder library = resolveLibrary(); 1145 if (library != null) return library.getFolderColumns(); 1146 return invoke(new Callable<ShellFolderColumnInfo[]>() { 1147 public ShellFolderColumnInfo[] call() { 1148 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder()); 1149 1150 if (columns != null) { 1151 List<ShellFolderColumnInfo> notNullColumns = 1152 new ArrayList<ShellFolderColumnInfo>(); 1153 for (int i = 0; i < columns.length; i++) { 1154 ShellFolderColumnInfo column = columns[i]; 1155 if (column != null) { 1156 column.setAlignment(column.getAlignment() == LVCFMT_RIGHT 1157 ? SwingConstants.RIGHT 1158 : column.getAlignment() == LVCFMT_CENTER 1159 ? SwingConstants.CENTER 1160 : SwingConstants.LEADING); 1161 1162 column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i)); 1163 1164 notNullColumns.add(column); 1165 } 1166 } 1167 columns = new ShellFolderColumnInfo[notNullColumns.size()]; 1168 notNullColumns.toArray(columns); 1169 } 1170 return columns; 1171 } 1172 }); 1173 } 1174 1175 public Object getFolderColumnValue(final int column) { 1176 if(!isLibrary()) { 1177 ShellFolder library = resolveLibrary(); 1178 if (library != null) return library.getFolderColumnValue(column); 1179 } 1180 return invoke(new Callable<Object>() { 1181 public Object call() { 1182 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column); 1183 } 1184 }); 1185 } 1186 1187 boolean isLibrary() { 1188 return isLib; 1189 } 1190 1191 private ShellFolder resolveLibrary() { 1192 for (ShellFolder f = this; f != null; f = f.parent) { 1193 if (!f.isFileSystem()) { 1194 if (f instanceof Win32ShellFolder2 && 1195 ((Win32ShellFolder2)f).isLibrary()) { 1196 try { 1197 return getShellFolder(new File(getPath())); 1198 } catch (FileNotFoundException e) { 1199 } 1200 } 1201 break; 1202 } 1203 } 1204 return null; 1205 } 1206 1207 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1208 private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2); 1209 1210 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1211 private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx); 1212 1213 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1214 private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx); 1215 1216 1217 public void sortChildren(final List<? extends File> files) { 1218 // To avoid loads of synchronizations with Invoker and improve performance we 1219 // synchronize the whole code of the sort method once 1220 invoke(new Callable<Void>() { 1221 public Void call() { 1222 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0)); 1223 1224 return null; 1225 } 1226 }); 1227 } 1228 1229 private static class ColumnComparator implements Comparator<File> { 1230 private final Win32ShellFolder2 shellFolder; 1231 1232 private final int columnIdx; 1233 1234 public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) { 1235 this.shellFolder = shellFolder; 1236 this.columnIdx = columnIdx; 1237 } 1238 1239 // compares 2 objects within this folder by the specified column 1240 public int compare(final File o, final File o1) { 1241 Integer result = invoke(new Callable<Integer>() { 1242 public Integer call() { 1243 if (o instanceof Win32ShellFolder2 1244 && o1 instanceof Win32ShellFolder2) { 1245 // delegates comparison to native method 1246 return compareIDsByColumn(shellFolder.getIShellFolder(), 1247 ((Win32ShellFolder2) o).getRelativePIDL(), 1248 ((Win32ShellFolder2) o1).getRelativePIDL(), 1249 columnIdx); 1250 } 1251 return 0; 1252 } 1253 }); 1254 1255 return result == null ? 0 : result; 1256 } 1257 } 1258 1259 // Extracts libraries and their default save locations from Known Folders list 1260 private static List<KnownFolderDefinition> getLibraries() { 1261 return invoke(new Callable<List<KnownFolderDefinition>>() { 1262 @Override 1263 public List<KnownFolderDefinition> call() throws Exception { 1264 KnownFolderDefinition[] all = loadKnownFolders(); 1265 List<KnownFolderDefinition> folders = new ArrayList<>(); 1266 if (all != null) { 1267 for (KnownFolderDefinition kf : all) { 1268 if (kf.relativePath == null || kf.parsingName == null || 1269 kf.saveLocation == null) { 1270 continue; 1271 } 1272 folders.add(kf); 1273 } 1274 } 1275 return folders; 1276 } 1277 }); 1278 } 1279 1280 }