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