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