1 /*
   2  * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.shell;
  27 
  28 import java.awt.Image;
  29 import java.awt.Toolkit;
  30 import java.awt.image.BufferedImage;
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.util.*;
  34 import java.util.concurrent.*;
  35 import javax.swing.SwingConstants;
  36 
  37 // NOTE: This class supersedes Win32ShellFolder, which was removed from
  38 //       distribution after version 1.4.2.
  39 
  40 /**
  41  * Win32 Shell Folders
  42  * <P>
  43  * <BR>
  44  * There are two fundamental types of shell folders : file system folders
  45  * and non-file system folders.  File system folders are relatively easy
  46  * to deal with.  Non-file system folders are items such as My Computer,
  47  * Network Neighborhood, and the desktop.  Some of these non-file system
  48  * folders have special values and properties.
  49  * <P>
  50  * <BR>
  51  * Win32 keeps two basic data structures for shell folders.  The first
  52  * of these is called an ITEMIDLIST.  Usually a pointer, called an
  53  * LPITEMIDLIST, or more frequently just "PIDL".  This structure holds
  54  * a series of identifiers and can be either relative to the desktop
  55  * (an absolute PIDL), or relative to the shell folder that contains them.
  56  * Some Win32 functions can take absolute or relative PIDL values, and
  57  * others can only accept relative values.
  58  * <BR>
  59  * The second data structure is an IShellFolder COM interface.  Using
  60  * this interface, one can enumerate the relative PIDLs in a shell
  61  * folder, get attributes, etc.
  62  * <BR>
  63  * All Win32ShellFolder2 objects which are folder types (even non-file
  64  * system folders) contain an IShellFolder object. Files are named in
  65  * directories via relative PIDLs.
  66  *
  67  * @author Michael Martak
  68  * @author Leif Samuelsson
  69  * @author Kenneth Russell
  70  * @since 1.4 */
  71 @SuppressWarnings("serial") // JDK-implementation class
  72 final class Win32ShellFolder2 extends ShellFolder {
  73 
  74     private static native void initIDs();
  75 
  76     static {
  77         initIDs();
  78     }
  79 
  80     // Win32 Shell Folder Constants
  81     public static final int DESKTOP = 0x0000;
  82     public static final int INTERNET = 0x0001;
  83     public static final int PROGRAMS = 0x0002;
  84     public static final int CONTROLS = 0x0003;
  85     public static final int PRINTERS = 0x0004;
  86     public static final int PERSONAL = 0x0005;
  87     public static final int FAVORITES = 0x0006;
  88     public static final int STARTUP = 0x0007;
  89     public static final int RECENT = 0x0008;
  90     public static final int SENDTO = 0x0009;
  91     public static final int BITBUCKET = 0x000a;
  92     public static final int STARTMENU = 0x000b;
  93     public static final int DESKTOPDIRECTORY = 0x0010;
  94     public static final int DRIVES = 0x0011;
  95     public static final int NETWORK = 0x0012;
  96     public static final int NETHOOD = 0x0013;
  97     public static final int FONTS = 0x0014;
  98     public static final int TEMPLATES = 0x0015;
  99     public static final int COMMON_STARTMENU = 0x0016;
 100     public static final int COMMON_PROGRAMS = 0X0017;
 101     public static final int COMMON_STARTUP = 0x0018;
 102     public static final int COMMON_DESKTOPDIRECTORY = 0x0019;
 103     public static final int APPDATA = 0x001a;
 104     public static final int PRINTHOOD = 0x001b;
 105     public static final int ALTSTARTUP = 0x001d;
 106     public static final int COMMON_ALTSTARTUP = 0x001e;
 107     public static final int COMMON_FAVORITES = 0x001f;
 108     public static final int INTERNET_CACHE = 0x0020;
 109     public static final int COOKIES = 0x0021;
 110     public static final int HISTORY = 0x0022;
 111 
 112     // Win32 shell folder attributes
 113     public static final int ATTRIB_CANCOPY          = 0x00000001;
 114     public static final int ATTRIB_CANMOVE          = 0x00000002;
 115     public static final int ATTRIB_CANLINK          = 0x00000004;
 116     public static final int ATTRIB_CANRENAME        = 0x00000010;
 117     public static final int ATTRIB_CANDELETE        = 0x00000020;
 118     public static final int ATTRIB_HASPROPSHEET     = 0x00000040;
 119     public static final int ATTRIB_DROPTARGET       = 0x00000100;
 120     public static final int ATTRIB_LINK             = 0x00010000;
 121     public static final int ATTRIB_SHARE            = 0x00020000;
 122     public static final int ATTRIB_READONLY         = 0x00040000;
 123     public static final int ATTRIB_GHOSTED          = 0x00080000;
 124     public static final int ATTRIB_HIDDEN           = 0x00080000;
 125     public static final int ATTRIB_FILESYSANCESTOR  = 0x10000000;
 126     public static final int ATTRIB_FOLDER           = 0x20000000;
 127     public static final int ATTRIB_FILESYSTEM       = 0x40000000;
 128     public static final int ATTRIB_HASSUBFOLDER     = 0x80000000;
 129     public static final int ATTRIB_VALIDATE         = 0x01000000;
 130     public static final int ATTRIB_REMOVABLE        = 0x02000000;
 131     public static final int ATTRIB_COMPRESSED       = 0x04000000;
 132     public static final int ATTRIB_BROWSABLE        = 0x08000000;
 133     public static final int ATTRIB_NONENUMERATED    = 0x00100000;
 134     public static final int ATTRIB_NEWCONTENT       = 0x00200000;
 135 
 136     // IShellFolder::GetDisplayNameOf constants
 137     public static final int SHGDN_NORMAL            = 0;
 138     public static final int SHGDN_INFOLDER          = 1;
 139     public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000;
 140     public static final int SHGDN_FORADDRESSBAR     = 0x4000;
 141     public static final int SHGDN_FORPARSING        = 0x8000;
 142 
 143     // Values for system call LoadIcon()
 144     public enum SystemIcon {
 145         IDI_APPLICATION(32512),
 146         IDI_HAND(32513),
 147         IDI_ERROR(32513),
 148         IDI_QUESTION(32514),
 149         IDI_EXCLAMATION(32515),
 150         IDI_WARNING(32515),
 151         IDI_ASTERISK(32516),
 152         IDI_INFORMATION(32516),
 153         IDI_WINLOGO(32517);
 154 
 155         private final int iconID;
 156 
 157         SystemIcon(int iconID) {
 158             this.iconID = iconID;
 159         }
 160 
 161         public int getIconID() {
 162             return iconID;
 163         }
 164     }
 165 
 166     // Known Folder data
 167     static class KnownFolderDefinition {
 168         String guid;
 169         int category;
 170         String name;
 171         String description;
 172         String parent;
 173         String relativePath;
 174         String parsingName;
 175         String tooltip;
 176         String localizedName;
 177         String icon;
 178         String security;
 179         long attributes;
 180         int defenitionFlags;
 181         String ftidType;
 182         String path;
 183         String saveLocation;
 184         static final List<KnownFolderDefinition> libraries = getLibraries();
 185     }
 186 
 187     static class FolderDisposer implements sun.java2d.DisposerRecord {
 188         /*
 189          * This is cached as a concession to getFolderType(), which needs
 190          * an absolute PIDL.
 191          */
 192         long absolutePIDL;
 193         /*
 194          * We keep track of shell folders through the IShellFolder
 195          * interface of their parents plus their relative PIDL.
 196          */
 197         long pIShellFolder;
 198         long relativePIDL;
 199 
 200         boolean disposed;
 201         public void dispose() {
 202             if (disposed) return;
 203             invoke(new Callable<Void>() {
 204                 public Void call() {
 205                     if (relativePIDL != 0) {
 206                         releasePIDL(relativePIDL);
 207                     }
 208                     if (absolutePIDL != 0) {
 209                         releasePIDL(absolutePIDL);
 210                     }
 211                     if (pIShellFolder != 0) {
 212                         releaseIShellFolder(pIShellFolder);
 213                     }
 214                     return null;
 215                 }
 216             });
 217             disposed = true;
 218         }
 219     }
 220     FolderDisposer disposer = new FolderDisposer();
 221     private void setIShellFolder(long pIShellFolder) {
 222         disposer.pIShellFolder = pIShellFolder;
 223     }
 224     private void setRelativePIDL(long relativePIDL) {
 225         disposer.relativePIDL = relativePIDL;
 226     }
 227     /*
 228      * The following are for caching various shell folder properties.
 229      */
 230     private long pIShellIcon = -1L;
 231     private String folderType = null;
 232     private String displayName = null;
 233     private Image smallIcon = null;
 234     private Image largeIcon = null;
 235     private Boolean isDir = null;
 236 
 237     /*
 238      * The following is to identify the My Documents folder as being special
 239      */
 240     private boolean isPersonal;
 241 
 242     private static String composePathForCsidl(int csidl) throws IOException, InterruptedException {
 243         String path = getFileSystemPath(csidl);
 244         return path == null
 245                 ? ("ShellFolder: 0x" + Integer.toHexString(csidl))
 246                 : path;
 247     }
 248 
 249     /**
 250      * Create a system special shell folder, such as the
 251      * desktop or Network Neighborhood.
 252      */
 253     Win32ShellFolder2(final int csidl) throws IOException, InterruptedException {
 254         // Desktop is parent of DRIVES and NETWORK, not necessarily
 255         // other special shell folders.
 256         super(null, composePathForCsidl(csidl));
 257 
 258         invoke(new Callable<Void>() {
 259             public Void call() throws InterruptedException {
 260                 if (csidl == DESKTOP) {
 261                     initDesktop();
 262                 } else {
 263                     initSpecial(getDesktop().getIShellFolder(), csidl);
 264                     // At this point, the native method initSpecial() has set our relativePIDL
 265                     // relative to the Desktop, which may not be our immediate parent. We need
 266                     // to traverse this ID list and break it into a chain of shell folders from
 267                     // the top, with each one having an immediate parent and a relativePIDL
 268                     // relative to that parent.
 269                     long pIDL = disposer.relativePIDL;
 270                     parent = getDesktop();
 271                     while (pIDL != 0) {
 272                         // Get a child pidl relative to 'parent'
 273                         long childPIDL = copyFirstPIDLEntry(pIDL);
 274                         if (childPIDL != 0) {
 275                             // Get a handle to the rest of the ID list
 276                             // i,e, parent's grandchilren and down
 277                             pIDL = getNextPIDLEntry(pIDL);
 278                             if (pIDL != 0) {
 279                                 // Now we know that parent isn't immediate to 'this' because it
 280                                 // has a continued ID list. Create a shell folder for this child
 281                                 // pidl and make it the new 'parent'.
 282                                 parent = new Win32ShellFolder2((Win32ShellFolder2) parent, childPIDL);
 283                             } else {
 284                                 // No grandchildren means we have arrived at the parent of 'this',
 285                                 // and childPIDL is directly relative to parent.
 286                                 disposer.relativePIDL = childPIDL;
 287                             }
 288                         } else {
 289                             break;
 290                         }
 291                     }
 292                 }
 293                 return null;
 294             }
 295         }, InterruptedException.class);
 296 
 297         sun.java2d.Disposer.addRecord(this, disposer);
 298     }
 299 
 300 
 301     /**
 302      * Create a system shell folder
 303      */
 304     Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path) {
 305         super(parent, (path != null) ? path : "ShellFolder: ");
 306         this.disposer.pIShellFolder = pIShellFolder;
 307         this.disposer.relativePIDL = relativePIDL;
 308         sun.java2d.Disposer.addRecord(this, disposer);
 309     }
 310 
 311 
 312     /**
 313      * Creates a shell folder with a parent and relative PIDL
 314      */
 315     Win32ShellFolder2(final Win32ShellFolder2 parent, final long relativePIDL) throws InterruptedException {
 316         super(parent,
 317             invoke(new Callable<String>() {
 318                 public String call() {
 319                     return getFileSystemPath(parent.getIShellFolder(), relativePIDL);
 320                 }
 321             }, RuntimeException.class)
 322         );
 323         this.disposer.relativePIDL = relativePIDL;
 324         sun.java2d.Disposer.addRecord(this, disposer);
 325     }
 326 
 327     // Initializes the desktop shell folder
 328     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 329     private native void initDesktop();
 330 
 331     // Initializes a special, non-file system shell folder
 332     // from one of the above constants
 333     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 334     private native void initSpecial(long desktopIShellFolder, int csidl);
 335 
 336     /** Marks this folder as being the My Documents (Personal) folder */
 337     public void setIsPersonal() {
 338         isPersonal = true;
 339     }
 340 
 341     /**
 342      * This method is implemented to make sure that no instances
 343      * of <code>ShellFolder</code> are ever serialized. If <code>isFileSystem()</code> returns
 344      * <code>true</code>, then the object is representable with an instance of
 345      * <code>java.io.File</code> instead. If not, then the object depends
 346      * on native PIDL state and should not be serialized.
 347      *
 348      * @return a <code>java.io.File</code> replacement object. If the folder
 349      * is a not a normal directory, then returns the first non-removable
 350      * drive (normally "C:\").
 351      */
 352     protected Object writeReplace() throws java.io.ObjectStreamException {
 353         return invoke(new Callable<File>() {
 354             public File call() {
 355                 if (isFileSystem()) {
 356                     return new File(getPath());
 357                 } else {
 358                     Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives();
 359                     if (drives != null) {
 360                         File[] driveRoots = drives.listFiles();
 361                         if (driveRoots != null) {
 362                             for (int i = 0; i < driveRoots.length; i++) {
 363                                 if (driveRoots[i] instanceof Win32ShellFolder2) {
 364                                     Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i];
 365                                     if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) {
 366                                         return new File(sf.getPath());
 367                                     }
 368                                 }
 369                             }
 370                         }
 371                     }
 372                     // Ouch, we have no hard drives. Return something "valid" anyway.
 373                     return new File("C:\\");
 374                 }
 375             }
 376         });
 377     }
 378 
 379 
 380     /**
 381      * Finalizer to clean up any COM objects or PIDLs used by this object.
 382      */
 383     protected void dispose() {
 384         disposer.dispose();
 385     }
 386 
 387 
 388     // Given a (possibly multi-level) relative PIDL (with respect to
 389     // the desktop, at least in all of the usage cases in this code),
 390     // return a pointer to the next entry. Does not mutate the PIDL in
 391     // any way. Returns 0 if the null terminator is reached.
 392     // Needs to be accessible to Win32ShellFolderManager2
 393     static native long getNextPIDLEntry(long pIDL);
 394 
 395     // Given a (possibly multi-level) relative PIDL (with respect to
 396     // the desktop, at least in all of the usage cases in this code),
 397     // copy the first entry into a newly-allocated PIDL. Returns 0 if
 398     // the PIDL is at the end of the list.
 399     // Needs to be accessible to Win32ShellFolderManager2
 400     static native long copyFirstPIDLEntry(long pIDL);
 401 
 402     // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL
 403     private static native long combinePIDLs(long ppIDL, long pIDL);
 404 
 405     // Release a PIDL object
 406     // Needs to be accessible to Win32ShellFolderManager2
 407     static native void releasePIDL(long pIDL);
 408 
 409     // Release an IShellFolder object
 410     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 411     private static native void releaseIShellFolder(long pIShellFolder);
 412 
 413     /**
 414      * Accessor for IShellFolder
 415      */
 416     private long getIShellFolder() {
 417         if (disposer.pIShellFolder == 0) {
 418             try {
 419                 disposer.pIShellFolder = invoke(new Callable<Long>() {
 420                     public Long call() {
 421                         assert(isDirectory());
 422                         assert(parent != null);
 423                         long parentIShellFolder = getParentIShellFolder();
 424                         if (parentIShellFolder == 0) {
 425                             throw new InternalError("Parent IShellFolder was null for "
 426                                     + getAbsolutePath());
 427                         }
 428                         // We are a directory with a parent and a relative PIDL.
 429                         // We want to bind to the parent so we get an
 430                         // IShellFolder instance associated with us.
 431                         long pIShellFolder = bindToObject(parentIShellFolder,
 432                                 disposer.relativePIDL);
 433                         if (pIShellFolder == 0) {
 434                             throw new InternalError("Unable to bind "
 435                                     + getAbsolutePath() + " to parent");
 436                         }
 437                         return pIShellFolder;
 438                     }
 439                 }, RuntimeException.class);
 440             } catch (InterruptedException e) {
 441                 // Ignore error
 442             }
 443         }
 444         return disposer.pIShellFolder;
 445     }
 446 
 447     /**
 448      * Get the parent ShellFolder's IShellFolder interface
 449      */
 450     public long getParentIShellFolder() {
 451         Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile();
 452         if (parent == null) {
 453             // Parent should only be null if this is the desktop, whose
 454             // relativePIDL is relative to its own IShellFolder.
 455             return getIShellFolder();
 456         }
 457         return parent.getIShellFolder();
 458     }
 459 
 460     /**
 461      * Accessor for relative PIDL
 462      */
 463     public long getRelativePIDL() {
 464         if (disposer.relativePIDL == 0) {
 465             throw new InternalError("Should always have a relative PIDL");
 466         }
 467         return disposer.relativePIDL;
 468     }
 469 
 470     private long getAbsolutePIDL() {
 471         if (parent == null) {
 472             // This is the desktop
 473             return getRelativePIDL();
 474         } else {
 475             if (disposer.absolutePIDL == 0) {
 476                 disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL());
 477             }
 478 
 479             return disposer.absolutePIDL;
 480         }
 481     }
 482 
 483     /**
 484      * Helper function to return the desktop
 485      */
 486     public Win32ShellFolder2 getDesktop() {
 487         return Win32ShellFolderManager2.getDesktop();
 488     }
 489 
 490     /**
 491      * Helper function to return the desktop IShellFolder interface
 492      */
 493     public long getDesktopIShellFolder() {
 494         return getDesktop().getIShellFolder();
 495     }
 496 
 497     private static boolean pathsEqual(String path1, String path2) {
 498         // Same effective implementation as Win32FileSystem
 499         return path1.equalsIgnoreCase(path2);
 500     }
 501 
 502     /**
 503      * Check to see if two ShellFolder objects are the same
 504      */
 505     public boolean equals(Object o) {
 506         if (o == null || !(o instanceof Win32ShellFolder2)) {
 507             // Short-circuit circuitous delegation path
 508             if (!(o instanceof File)) {
 509                 return super.equals(o);
 510             }
 511             return pathsEqual(getPath(), ((File) o).getPath());
 512         }
 513         Win32ShellFolder2 rhs = (Win32ShellFolder2) o;
 514         if ((parent == null && rhs.parent != null) ||
 515             (parent != null && rhs.parent == null)) {
 516             return false;
 517         }
 518 
 519         if (isFileSystem() && rhs.isFileSystem()) {
 520             // Only folders with identical parents can be equal
 521             return (pathsEqual(getPath(), rhs.getPath()) &&
 522                     (parent == rhs.parent || parent.equals(rhs.parent)));
 523         }
 524 
 525         if (parent == rhs.parent || parent.equals(rhs.parent)) {
 526             try {
 527                 return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL);
 528             } catch (InterruptedException e) {
 529                 return false;
 530             }
 531         }
 532 
 533         return false;
 534     }
 535 
 536     private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2)
 537             throws InterruptedException {
 538         return invoke(new Callable<Boolean>() {
 539             public Boolean call() {
 540                 return compareIDs(pIShellFolder, pidl1, pidl2) == 0;
 541             }
 542         }, RuntimeException.class);
 543     }
 544 
 545     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 546     private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2);
 547 
 548     private volatile Boolean cachedIsFileSystem;
 549 
 550     /**
 551      * @return Whether this is a file system shell folder
 552      */
 553     public boolean isFileSystem() {
 554         if (cachedIsFileSystem == null) {
 555             cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM);
 556         }
 557 
 558         return cachedIsFileSystem;
 559     }
 560 
 561     /**
 562      * Return whether the given attribute flag is set for this object
 563      */
 564     public boolean hasAttribute(final int attribute) {
 565         Boolean result = invoke(new Callable<Boolean>() {
 566             public Boolean call() {
 567                 // Caching at this point doesn't seem to be cost efficient
 568                 return (getAttributes0(getParentIShellFolder(),
 569                     getRelativePIDL(), attribute)
 570                     & attribute) != 0;
 571             }
 572         });
 573 
 574         return result != null && result;
 575     }
 576 
 577     /**
 578      * Returns the queried attributes specified in attrsMask.
 579      *
 580      * Could plausibly be used for attribute caching but have to be
 581      * very careful not to touch network drives and file system roots
 582      * with a full attrsMask
 583      * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 584      */
 585 
 586     private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask);
 587 
 588     // Return the path to the underlying file system object
 589     // Should be called from the COM thread
 590     private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) {
 591         int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER;
 592         if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() &&
 593                 getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) {
 594 
 595             String s =
 596                     getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(),
 597                             getLinkLocation(parentIShellFolder, relativePIDL, false));
 598             if (s != null && s.startsWith("\\\\")) {
 599                 return s;
 600             }
 601         }
 602         String path = getDisplayNameOf(parentIShellFolder, relativePIDL,
 603                         SHGDN_FORPARSING);
 604         // if this is a library its default save location is taken as a path
 605         // this is a temp fix until java.io starts support Libraries
 606         if( path != null && path.startsWith("::{") &&
 607                 path.toLowerCase().endsWith(".library-ms")) {
 608             for (KnownFolderDefinition kf : KnownFolderDefinition.libraries) {
 609                 if( path.toLowerCase().endsWith(
 610                             kf.relativePath.toLowerCase()) &&
 611                             path.toUpperCase().startsWith(
 612                             kf.parsingName.substring(0, 40).toUpperCase()) ) {
 613                     return kf.saveLocation;
 614                 }
 615             }
 616         }
 617         return path;
 618     }
 619 
 620     // Needs to be accessible to Win32ShellFolderManager2
 621     static String getFileSystemPath(final int csidl) throws IOException, InterruptedException {
 622         String path = invoke(new Callable<String>() {
 623             public String call() throws IOException {
 624                 return getFileSystemPath0(csidl);
 625             }
 626         }, IOException.class);
 627         if (path != null) {
 628             SecurityManager security = System.getSecurityManager();
 629             if (security != null) {
 630                 security.checkRead(path);
 631             }
 632         }
 633         return path;
 634     }
 635 
 636     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 637     private static native String getFileSystemPath0(int csidl) throws IOException;
 638 
 639     // Return whether the path is a network root.
 640     // Path is assumed to be non-null
 641     private static boolean isNetworkRoot(String path) {
 642         return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/"));
 643     }
 644 
 645     /**
 646      * @return The parent shell folder of this shell folder, null if
 647      * there is no parent
 648      */
 649     public File getParentFile() {
 650         return parent;
 651     }
 652 
 653     public boolean isDirectory() {
 654         if (isDir == null) {
 655             // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are
 656             // not traversable in JFileChooser.
 657             if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) {
 658                 isDir = Boolean.TRUE;
 659             } else if (isLink()) {
 660                 ShellFolder linkLocation = getLinkLocation(false);
 661                 isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory());
 662             } else {
 663                 isDir = Boolean.FALSE;
 664             }
 665         }
 666         return isDir.booleanValue();
 667     }
 668 
 669     /*
 670      * Functions for enumerating an IShellFolder's children
 671      */
 672     // Returns an IEnumIDList interface for an IShellFolder.  The value
 673     // returned must be released using releaseEnumObjects().
 674     private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException {
 675         return invoke(new Callable<Long>() {
 676             public Long call() {
 677                 boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder();
 678 
 679                 return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles);
 680             }
 681         }, RuntimeException.class);
 682     }
 683 
 684     // Returns an IEnumIDList interface for an IShellFolder.  The value
 685     // returned must be released using releaseEnumObjects().
 686     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 687     private native long getEnumObjects(long pIShellFolder, boolean isDesktop,
 688                                        boolean includeHiddenFiles);
 689     // Returns the next sequential child as a relative PIDL
 690     // from an IEnumIDList interface.  The value returned must
 691     // be released using releasePIDL().
 692     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 693     private native long getNextChild(long pEnumObjects);
 694     // Releases the IEnumIDList interface
 695     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 696     private native void releaseEnumObjects(long pEnumObjects);
 697 
 698     // Returns the IShellFolder of a child from a parent IShellFolder
 699     // and a relative PIDL.  The value returned must be released
 700     // using releaseIShellFolder().
 701     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 702     private static native long bindToObject(long parentIShellFolder, long pIDL);
 703 
 704     /**
 705      * @return An array of shell folders that are children of this shell folder
 706      *         object. The array will be empty if the folder is empty.  Returns
 707      *         <code>null</code> if this shellfolder does not denote a directory.
 708      */
 709     public File[] listFiles(final boolean includeHiddenFiles) {
 710         SecurityManager security = System.getSecurityManager();
 711         if (security != null) {
 712             security.checkRead(getPath());
 713         }
 714 
 715         try {
 716             return invoke(new Callable<File[]>() {
 717                 public File[] call() throws InterruptedException {
 718                     if (!isDirectory()) {
 719                         return null;
 720                     }
 721                     // Links to directories are not directories and cannot be parents.
 722                     // This does not apply to folders in My Network Places (NetHood)
 723                     // because they are both links and real directories!
 724                     if (isLink() && !hasAttribute(ATTRIB_FOLDER)) {
 725                         return new File[0];
 726                     }
 727 
 728                     Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop();
 729                     Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal();
 730 
 731                     // If we are a directory, we have a parent and (at least) a
 732                     // relative PIDL. We must first ensure we are bound to the
 733                     // parent so we have an IShellFolder to query.
 734                     long pIShellFolder = getIShellFolder();
 735                     // Now we can enumerate the objects in this folder.
 736                     ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>();
 737                     long pEnumObjects = getEnumObjects(includeHiddenFiles);
 738                     if (pEnumObjects != 0) {
 739                         try {
 740                             long childPIDL;
 741                             int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR;
 742                             do {
 743                                 childPIDL = getNextChild(pEnumObjects);
 744                                 boolean releasePIDL = true;
 745                                 if (childPIDL != 0 &&
 746                                         (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) {
 747                                     Win32ShellFolder2 childFolder;
 748                                     if (Win32ShellFolder2.this.equals(desktop)
 749                                             && personal != null
 750                                             && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) {
 751                                         childFolder = personal;
 752                                     } else {
 753                                         childFolder = new Win32ShellFolder2(Win32ShellFolder2.this, childPIDL);
 754                                         releasePIDL = false;
 755                                     }
 756                                     list.add(childFolder);
 757                                 }
 758                                 if (releasePIDL) {
 759                                     releasePIDL(childPIDL);
 760                                 }
 761                             } while (childPIDL != 0 && !Thread.currentThread().isInterrupted());
 762                         } finally {
 763                             releaseEnumObjects(pEnumObjects);
 764                         }
 765                     }
 766                     return Thread.currentThread().isInterrupted()
 767                         ? new File[0]
 768                         : list.toArray(new ShellFolder[list.size()]);
 769                 }
 770             }, InterruptedException.class);
 771         } catch (InterruptedException e) {
 772             return new File[0];
 773         }
 774     }
 775 
 776 
 777     /**
 778      * Look for (possibly special) child folder by it's path
 779      *
 780      * @return The child shellfolder, or null if not found.
 781      */
 782     Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException {
 783         return invoke(new Callable<Win32ShellFolder2>() {
 784             public Win32ShellFolder2 call() throws InterruptedException {
 785                 long pIShellFolder = getIShellFolder();
 786                 long pEnumObjects = getEnumObjects(true);
 787                 Win32ShellFolder2 child = null;
 788                 long childPIDL;
 789 
 790                 while ((childPIDL = getNextChild(pEnumObjects)) != 0) {
 791                     if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) {
 792                         String path = getFileSystemPath(pIShellFolder, childPIDL);
 793                         if (path != null && path.equalsIgnoreCase(filePath)) {
 794                             long childIShellFolder = bindToObject(pIShellFolder, childPIDL);
 795                             child = new Win32ShellFolder2(Win32ShellFolder2.this,
 796                                     childIShellFolder, childPIDL, path);
 797                             break;
 798                         }
 799                     }
 800                     releasePIDL(childPIDL);
 801                 }
 802                 releaseEnumObjects(pEnumObjects);
 803                 return child;
 804             }
 805         }, InterruptedException.class);
 806     }
 807 
 808     private volatile Boolean cachedIsLink;
 809 
 810     /**
 811      * @return Whether this shell folder is a link
 812      */
 813     public boolean isLink() {
 814         if (cachedIsLink == null) {
 815             cachedIsLink = hasAttribute(ATTRIB_LINK);
 816         }
 817 
 818         return cachedIsLink;
 819     }
 820 
 821     /**
 822      * @return Whether this shell folder is marked as hidden
 823      */
 824     public boolean isHidden() {
 825         return hasAttribute(ATTRIB_HIDDEN);
 826     }
 827 
 828 
 829     // Return the link location of a shell folder
 830     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 831     private static native long getLinkLocation(long parentIShellFolder,
 832                                         long relativePIDL, boolean resolve);
 833 
 834     /**
 835      * @return The shell folder linked to by this shell folder, or null
 836      * if this shell folder is not a link or is a broken or invalid link
 837      */
 838     public ShellFolder getLinkLocation()  {
 839         return getLinkLocation(true);
 840     }
 841 
 842     private ShellFolder getLinkLocation(final boolean resolve) {
 843         return invoke(new Callable<ShellFolder>() {
 844             public ShellFolder call() {
 845                 if (!isLink()) {
 846                     return null;
 847                 }
 848 
 849                 ShellFolder location = null;
 850                 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(),
 851                         getRelativePIDL(), resolve);
 852                 if (linkLocationPIDL != 0) {
 853                     try {
 854                         location =
 855                                 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(),
 856                                         linkLocationPIDL);
 857                     } catch (InterruptedException e) {
 858                         // Return null
 859                     } catch (InternalError e) {
 860                         // Could be a link to a non-bindable object, such as a network connection
 861                         // TODO: getIShellFolder() should throw FileNotFoundException instead
 862                     }
 863                 }
 864                 return location;
 865             }
 866         });
 867     }
 868 
 869     // Parse a display name into a PIDL relative to the current IShellFolder.
 870     long parseDisplayName(final String name) throws IOException, InterruptedException {
 871         return invoke(new Callable<Long>() {
 872             public Long call() throws IOException {
 873                 return parseDisplayName0(getIShellFolder(), name);
 874             }
 875         }, IOException.class);
 876     }
 877 
 878     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 879     private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException;
 880 
 881     // Return the display name of a shell folder
 882     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 883     private static native String getDisplayNameOf(long parentIShellFolder,
 884                                                   long relativePIDL,
 885                                                   int attrs);
 886 
 887     // Returns data of all Known Folders registered in the system
 888     private static native KnownFolderDefinition[] loadKnownFolders();
 889 
 890     /**
 891      * @return The name used to display this shell folder
 892      */
 893     public String getDisplayName() {
 894         if (displayName == null) {
 895             displayName =
 896                 invoke(new Callable<String>() {
 897                     public String call() {
 898                         return getDisplayNameOf(getParentIShellFolder(),
 899                                 getRelativePIDL(), SHGDN_NORMAL);
 900                     }
 901                 });
 902         }
 903         return displayName;
 904     }
 905 
 906     // Return the folder type of a shell folder
 907     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 908     private static native String getFolderType(long pIDL);
 909 
 910     /**
 911      * @return The type of shell folder as a string
 912      */
 913     public String getFolderType() {
 914         if (folderType == null) {
 915             final long absolutePIDL = getAbsolutePIDL();
 916             folderType =
 917                 invoke(new Callable<String>() {
 918                     public String call() {
 919                         return getFolderType(absolutePIDL);
 920                     }
 921                 });
 922         }
 923         return folderType;
 924     }
 925 
 926     // Return the executable type of a file system shell folder
 927     private native String getExecutableType(String path);
 928 
 929     /**
 930      * @return The executable type as a string
 931      */
 932     public String getExecutableType() {
 933         if (!isFileSystem()) {
 934             return null;
 935         }
 936         return getExecutableType(getAbsolutePath());
 937     }
 938 
 939 
 940 
 941     // Icons
 942 
 943     private static Map<Integer, Image> smallSystemImages = new HashMap<>();
 944     private static Map<Integer, Image> largeSystemImages = new HashMap<>();
 945     private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>();
 946     private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>();
 947 
 948     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 949     private static native long getIShellIcon(long pIShellFolder);
 950 
 951     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 952     private static native int getIconIndex(long parentIShellIcon, long relativePIDL);
 953 
 954     // Return the icon of a file system shell folder in the form of an HICON
 955     private static native long getIcon(String absolutePath, boolean getLargeIcon);
 956 
 957     // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
 958     private static native long extractIcon(long parentIShellFolder, long relativePIDL,
 959                                            boolean getLargeIcon);
 960 
 961     // Returns an icon from the Windows system icon list in the form of an HICON
 962     private static native long getSystemIcon(int iconID);
 963     private static native long getIconResource(String libName, int iconID,
 964                                                int cxDesired, int cyDesired,
 965                                                boolean useVGAColors);
 966                                                // Note: useVGAColors is ignored on XP and later
 967 
 968     // Return the bits from an HICON.  This has a side effect of setting
 969     // the imageHash variable for efficient caching / comparing.
 970     private static native int[] getIconBits(long hIcon, int iconSize);
 971     // Dispose the HICON
 972     private static native void disposeIcon(long hIcon);
 973 
 974     static native int[] getStandardViewButton0(int iconIndex);
 975 
 976     // Should be called from the COM thread
 977     private long getIShellIcon() {
 978         if (pIShellIcon == -1L) {
 979             pIShellIcon = getIShellIcon(getIShellFolder());
 980         }
 981 
 982         return pIShellIcon;
 983     }
 984 
 985     private static Image makeIcon(long hIcon, boolean getLargeIcon) {
 986         if (hIcon != 0L && hIcon != -1L) {
 987             // Get the bits.  This has the side effect of setting the imageHash value for this object.
 988             int size = getLargeIcon ? 32 : 16;
 989             int[] iconBits = getIconBits(hIcon, size);
 990             if (iconBits != null) {
 991                 BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
 992                 img.setRGB(0, 0, size, size, iconBits, 0, size);
 993                 return img;
 994             }
 995         }
 996         return null;
 997     }
 998 
 999 
1000     /**
1001      * @return The icon image used to display this shell folder
1002      */
1003     public Image getIcon(final boolean getLargeIcon) {
1004         Image icon = getLargeIcon ? largeIcon : smallIcon;
1005         if (icon == null) {
1006             icon =
1007                 invoke(new Callable<Image>() {
1008                     public Image call() {
1009                         Image newIcon = null;
1010                         if (isFileSystem()) {







1011                             long parentIShellIcon = (parent != null)
1012                                 ? ((Win32ShellFolder2) parent).getIShellIcon()
1013                                 : 0L;
1014                             long relativePIDL = getRelativePIDL();
1015 
1016                             // These are cached per type (using the index in the system image list)
1017                             int index = getIconIndex(parentIShellIcon, relativePIDL);
1018                             if (index > 0) {
1019                                 Map<Integer, Image> imageCache;
1020                                 if (isLink()) {
1021                                     imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages;
1022                                 } else {
1023                                     imageCache = getLargeIcon ? largeSystemImages : smallSystemImages;
1024                                 }
1025                                 newIcon = imageCache.get(Integer.valueOf(index));
1026                                 if (newIcon == null) {
1027                                     long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
1028                                     newIcon = makeIcon(hIcon, getLargeIcon);
1029                                     disposeIcon(hIcon);
1030                                     if (newIcon != null) {
1031                                         imageCache.put(Integer.valueOf(index), newIcon);
1032                                     }
1033                                 }
1034                             }
1035                         }
1036 
1037                         if (newIcon == null) {
1038                             // These are only cached per object
1039                             long hIcon = extractIcon(getParentIShellFolder(),
1040                                 getRelativePIDL(), getLargeIcon);












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