1 /*
   2  * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.shell;
  27 
  28 import java.awt.Image;
  29 import java.awt.Toolkit;
  30 import java.awt.image.BufferedImage;
  31 import java.io.File;
  32 import java.io.FileNotFoundException;
  33 import java.io.IOException;
  34 import java.security.AccessController;
  35 import java.security.PrivilegedAction;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.List;
  39 import java.util.concurrent.Callable;
  40 import java.util.concurrent.ExecutionException;
  41 import java.util.concurrent.Future;
  42 import java.util.concurrent.LinkedBlockingQueue;
  43 import java.util.concurrent.RejectedExecutionException;
  44 import java.util.concurrent.ThreadFactory;
  45 import java.util.concurrent.ThreadPoolExecutor;
  46 import java.util.concurrent.TimeUnit;
  47 import java.util.stream.Stream;
  48 
  49 import sun.awt.OSInfo;
  50 import sun.awt.util.ThreadGroupUtils;
  51 import sun.util.logging.PlatformLogger;
  52 
  53 import static sun.awt.shell.Win32ShellFolder2.DESKTOP;
  54 import static sun.awt.shell.Win32ShellFolder2.DRIVES;
  55 import static sun.awt.shell.Win32ShellFolder2.Invoker;
  56 import static sun.awt.shell.Win32ShellFolder2.LARGE_ICON_SIZE;
  57 import static sun.awt.shell.Win32ShellFolder2.MultiResolutionIconImage;
  58 import static sun.awt.shell.Win32ShellFolder2.NETWORK;
  59 import static sun.awt.shell.Win32ShellFolder2.PERSONAL;
  60 import static sun.awt.shell.Win32ShellFolder2.RECENT;
  61 import static sun.awt.shell.Win32ShellFolder2.SMALL_ICON_SIZE;
  62 // NOTE: This class supersedes Win32ShellFolderManager, which was removed
  63 //       from distribution after version 1.4.2.
  64 
  65 /**
  66  * @author Michael Martak
  67  * @author Leif Samuelsson
  68  * @author Kenneth Russell
  69  * @since 1.4
  70  */
  71 
  72 final class Win32ShellFolderManager2 extends ShellFolderManager {
  73 
  74     private static final PlatformLogger
  75             log = PlatformLogger.getLogger("sun.awt.shell.Win32ShellFolderManager2");
  76 
  77     static {
  78         // Load library here
  79         sun.awt.windows.WToolkit.loadLibraries();
  80     }
  81 
  82     public ShellFolder createShellFolder(File file) throws FileNotFoundException {
  83         try {
  84             return createShellFolder(getDesktop(), file);
  85         } catch (InterruptedException e) {
  86             throw new FileNotFoundException("Execution was interrupted");
  87         }
  88     }
  89 
  90     static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, File file)
  91             throws FileNotFoundException, InterruptedException {
  92         long pIDL;
  93         try {
  94             pIDL = parent.parseDisplayName(file.getCanonicalPath());
  95         } catch (IOException ex) {
  96             pIDL = 0;
  97         }
  98         if (pIDL == 0) {
  99             // Shouldn't happen but watch for it anyway
 100             throw new FileNotFoundException("File " + file.getAbsolutePath() + " not found");
 101         }
 102 
 103         try {
 104             return createShellFolderFromRelativePIDL(parent, pIDL);
 105         } finally {
 106             Win32ShellFolder2.releasePIDL(pIDL);
 107         }
 108     }
 109 
 110     static Win32ShellFolder2 createShellFolderFromRelativePIDL(Win32ShellFolder2 parent, long pIDL)
 111             throws InterruptedException {
 112         // Walk down this relative pIDL, creating new nodes for each of the entries
 113         while (pIDL != 0) {
 114             long curPIDL = Win32ShellFolder2.copyFirstPIDLEntry(pIDL);
 115             if (curPIDL != 0) {
 116                 parent = Win32ShellFolder2.createShellFolder(parent, curPIDL);
 117                 pIDL = Win32ShellFolder2.getNextPIDLEntry(pIDL);
 118             } else {
 119                 // The list is empty if the parent is Desktop and pIDL is a shortcut to Desktop
 120                 break;
 121             }
 122         }
 123         return parent;
 124     }
 125 
 126     private static final int VIEW_LIST = 2;
 127     private static final int VIEW_DETAILS = 3;
 128     private static final int VIEW_PARENTFOLDER = 8;
 129     private static final int VIEW_NEWFOLDER = 11;
 130 
 131     private static final Image[] STANDARD_VIEW_BUTTONS = new Image[12];
 132 
 133     private static Image getStandardViewButton(int iconIndex) {
 134         Image result = STANDARD_VIEW_BUTTONS[iconIndex];
 135 
 136         if (result != null) {
 137             return result;
 138         }
 139 
 140         final int[] iconBits = Win32ShellFolder2
 141                 .getStandardViewButton0(iconIndex, true);
 142         if (iconBits != null) {
 143             // icons are always square
 144             final int size = (int) Math.sqrt(iconBits.length);
 145             final BufferedImage img =
 146                     new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
 147             img.setRGB(0, 0, size, size, iconBits, 0, size);
 148 
 149             STANDARD_VIEW_BUTTONS[iconIndex] = (size == SMALL_ICON_SIZE)
 150                     ? img
 151                     : new MultiResolutionIconImage(SMALL_ICON_SIZE, img);
 152         }
 153 
 154         return STANDARD_VIEW_BUTTONS[iconIndex];
 155     }
 156 
 157     // Special folders
 158     private static Win32ShellFolder2 desktop;
 159     private static Win32ShellFolder2 drives;
 160     private static Win32ShellFolder2 recent;
 161     private static Win32ShellFolder2 network;
 162     private static Win32ShellFolder2 personal;
 163 
 164     static Win32ShellFolder2 getDesktop() {
 165         if (desktop == null) {
 166             try {
 167                 desktop = new Win32ShellFolder2(DESKTOP);
 168             } catch (final SecurityException ignored) {
 169                 // Ignore, the message may have sensitive information, not
 170                 // accessible other ways
 171             } catch (IOException | InterruptedException e) {
 172                 if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 173                     log.warning("Cannot access 'Desktop'", e);
 174                 }
 175             }
 176         }
 177         return desktop;
 178     }
 179 
 180     static Win32ShellFolder2 getDrives() {
 181         if (drives == null) {
 182             try {
 183                 drives = new Win32ShellFolder2(DRIVES);
 184             } catch (final SecurityException ignored) {
 185                 // Ignore, the message may have sensitive information, not
 186                 // accessible other ways
 187             } catch (IOException | InterruptedException e) {
 188                 if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 189                     log.warning("Cannot access 'Drives'", e);
 190                 }
 191             }
 192         }
 193         return drives;
 194     }
 195 
 196     static Win32ShellFolder2 getRecent() {
 197         if (recent == null) {
 198             try {
 199                 String path = Win32ShellFolder2.getFileSystemPath(RECENT);
 200                 if (path != null) {
 201                     recent = createShellFolder(getDesktop(), new File(path));
 202                 }
 203             } catch (final SecurityException ignored) {
 204                 // Ignore, the message may have sensitive information, not
 205                 // accessible other ways
 206             } catch (InterruptedException | IOException e) {
 207                 if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 208                     log.warning("Cannot access 'Recent'", e);
 209                 }
 210             }
 211         }
 212         return recent;
 213     }
 214 
 215     static Win32ShellFolder2 getNetwork() {
 216         if (network == null) {
 217             try {
 218                 network = new Win32ShellFolder2(NETWORK);
 219             } catch (final SecurityException ignored) {
 220                 // Ignore, the message may have sensitive information, not
 221                 // accessible other ways
 222             } catch (IOException | InterruptedException e) {
 223                 if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 224                     log.warning("Cannot access 'Network'", e);
 225                 }
 226             }
 227         }
 228         return network;
 229     }
 230 
 231     static Win32ShellFolder2 getPersonal() {
 232         if (personal == null) {
 233             try {
 234                 String path = Win32ShellFolder2.getFileSystemPath(PERSONAL);
 235                 if (path != null) {
 236                     Win32ShellFolder2 desktop = getDesktop();
 237                     personal = desktop.getChildByPath(path);
 238                     if (personal == null) {
 239                         personal = createShellFolder(getDesktop(), new File(path));
 240                     }
 241                     if (personal != null) {
 242                         personal.setIsPersonal();
 243                     }
 244                 }
 245             } catch (final SecurityException ignored) {
 246                 // Ignore, the message may have sensitive information, not
 247                 // accessible other ways
 248             } catch (InterruptedException | IOException e) {
 249                 if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 250                     log.warning("Cannot access 'Personal'", e);
 251                 }
 252             }
 253         }
 254         return personal;
 255     }
 256 
 257 
 258     private static File[] roots;
 259 
 260     /**
 261      * @param key a {@code String}
 262      *  "fileChooserDefaultFolder":
 263      *    Returns a {@code File} - the default shellfolder for a new filechooser
 264      *  "roots":
 265      *    Returns a {@code File[]} - containing the root(s) of the displayable hierarchy
 266      *  "fileChooserComboBoxFolders":
 267      *    Returns a {@code File[]} - an array of shellfolders representing the list to
 268      *    show by default in the file chooser's combobox
 269      *   "fileChooserShortcutPanelFolders":
 270      *    Returns a {@code File[]} - an array of shellfolders representing well-known
 271      *    folders, such as Desktop, Documents, History, Network, Home, etc.
 272      *    This is used in the shortcut panel of the filechooser on Windows 2000
 273      *    and Windows Me.
 274      *  "fileChooserIcon <icon>":
 275      *    Returns an {@code Image} - icon can be ListView, DetailsView, UpFolder, NewFolder or
 276      *    ViewMenu (Windows only).
 277      *  "optionPaneIcon iconName":
 278      *    Returns an {@code Image} - icon from the system icon list
 279      *
 280      * @return An Object matching the key string.
 281      */
 282     public Object get(String key) {
 283         if (key.equals("fileChooserDefaultFolder")) {
 284             File file = getPersonal();
 285             if (file == null) {
 286                 file = getDesktop();
 287             }
 288             return checkFile(file);
 289         } else if (key.equals("roots")) {
 290             // Should be "History" and "Desktop" ?
 291             if (roots == null) {
 292                 File desktop = getDesktop();
 293                 if (desktop != null) {
 294                     roots = new File[] { desktop };
 295                 } else {
 296                     roots = (File[])super.get(key);
 297                 }
 298             }
 299             return checkFiles(roots);
 300         } else if (key.equals("fileChooserComboBoxFolders")) {
 301             Win32ShellFolder2 desktop = getDesktop();
 302 
 303             if (desktop != null && checkFile(desktop) != null) {
 304                 ArrayList<File> folders = new ArrayList<File>();
 305                 Win32ShellFolder2 drives = getDrives();
 306 
 307                 Win32ShellFolder2 recentFolder = getRecent();
 308                 if (recentFolder != null && OSInfo.getWindowsVersion().compareTo(OSInfo.WINDOWS_2000) >= 0) {
 309                     folders.add(recentFolder);
 310                 }
 311 
 312                 folders.add(desktop);
 313                 // Add all second level folders
 314                 File[] secondLevelFolders = checkFiles(desktop.listFiles());
 315                 Arrays.sort(secondLevelFolders);
 316                 for (File secondLevelFolder : secondLevelFolders) {
 317                     Win32ShellFolder2 folder = (Win32ShellFolder2) secondLevelFolder;
 318                     if (!folder.isFileSystem() || (folder.isDirectory() && !folder.isLink())) {
 319                         folders.add(folder);
 320                         // Add third level for "My Computer"
 321                         if (folder.equals(drives)) {
 322                             File[] thirdLevelFolders = checkFiles(folder.listFiles());
 323                             if (thirdLevelFolders != null && thirdLevelFolders.length > 0) {
 324                                 List<File> thirdLevelFoldersList = Arrays.asList(thirdLevelFolders);
 325 
 326                                 folder.sortChildren(thirdLevelFoldersList);
 327                                 folders.addAll(thirdLevelFoldersList);
 328                             }
 329                         }
 330                     }
 331                 }
 332                 return checkFiles(folders);
 333             } else {
 334                 return super.get(key);
 335             }
 336         } else if (key.equals("fileChooserShortcutPanelFolders")) {
 337             Toolkit toolkit = Toolkit.getDefaultToolkit();
 338             ArrayList<File> folders = new ArrayList<File>();
 339             int i = 0;
 340             Object value;
 341             do {
 342                 value = toolkit.getDesktopProperty("win.comdlg.placesBarPlace" + i++);
 343                 try {
 344                     if (value instanceof Integer) {
 345                         // A CSIDL
 346                         folders.add(new Win32ShellFolder2((Integer)value));
 347                     } else if (value instanceof String) {
 348                         // A path
 349                         folders.add(createShellFolder(new File((String)value)));
 350                     }
 351                 } catch (IOException e) {
 352                     if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 353                         log.warning("Cannot read value = " + value, e);
 354                     }
 355                     // Skip this value
 356                 } catch (InterruptedException e) {
 357                     if (log.isLoggable(PlatformLogger.Level.WARNING)) {
 358                         log.warning("Cannot read value = " + value, e);
 359                     }
 360                     // Return empty result
 361                     return new File[0];
 362                 }
 363             } while (value != null);
 364 
 365             if (folders.size() == 0) {
 366                 // Use default list of places
 367                 for (File f : new File[] {
 368                     getRecent(), getDesktop(), getPersonal(), getDrives(), getNetwork()
 369                 }) {
 370                     if (f != null) {
 371                         folders.add(f);
 372                     }
 373                 }
 374             }
 375             return checkFiles(folders);
 376         } else if (key.startsWith("fileChooserIcon ")) {
 377             String name = key.substring(key.indexOf(" ") + 1);
 378 
 379             int iconIndex;
 380 
 381             if (name.equals("ListView") || name.equals("ViewMenu")) {
 382                 iconIndex = VIEW_LIST;
 383             } else if (name.equals("DetailsView")) {
 384                 iconIndex = VIEW_DETAILS;
 385             } else if (name.equals("UpFolder")) {
 386                 iconIndex = VIEW_PARENTFOLDER;
 387             } else if (name.equals("NewFolder")) {
 388                 iconIndex = VIEW_NEWFOLDER;
 389             } else {
 390                 return null;
 391             }
 392 
 393             return getStandardViewButton(iconIndex);
 394         } else if (key.startsWith("optionPaneIcon ")) {
 395             Win32ShellFolder2.SystemIcon iconType;
 396             if (key == "optionPaneIcon Error") {
 397                 iconType = Win32ShellFolder2.SystemIcon.IDI_ERROR;
 398             } else if (key == "optionPaneIcon Information") {
 399                 iconType = Win32ShellFolder2.SystemIcon.IDI_INFORMATION;
 400             } else if (key == "optionPaneIcon Question") {
 401                 iconType = Win32ShellFolder2.SystemIcon.IDI_QUESTION;
 402             } else if (key == "optionPaneIcon Warning") {
 403                 iconType = Win32ShellFolder2.SystemIcon.IDI_EXCLAMATION;
 404             } else {
 405                 return null;
 406             }
 407             return Win32ShellFolder2.getSystemIcon(iconType);
 408         } else if (key.startsWith("shell32Icon ") || key.startsWith("shell32LargeIcon ")) {
 409             String name = key.substring(key.indexOf(" ") + 1);
 410             try {
 411                 int i = Integer.parseInt(name);
 412                 if (i >= 0) {
 413                     return Win32ShellFolder2.getShell32Icon(i,
 414                          key.startsWith("shell32LargeIcon ")?LARGE_ICON_SIZE : SMALL_ICON_SIZE);
 415                 }
 416             } catch (NumberFormatException ex) {
 417             }
 418         }
 419         return null;
 420     }
 421 
 422     private static File checkFile(File file) {
 423         SecurityManager sm = System.getSecurityManager();
 424         return (sm == null || file == null) ? file : checkFile(file, sm);
 425     }
 426 
 427     private static File checkFile(File file, SecurityManager sm) {
 428         try {
 429             sm.checkRead(file.getPath());
 430 
 431             if (file instanceof Win32ShellFolder2) {
 432                 Win32ShellFolder2 f = (Win32ShellFolder2)file;
 433                 if (f.isLink()) {
 434                     Win32ShellFolder2 link = (Win32ShellFolder2)f.getLinkLocation();
 435                     if (link != null)
 436                         sm.checkRead(link.getPath());
 437                 }
 438             }
 439             return file;
 440         } catch (SecurityException se) {
 441             return null;
 442         }
 443     }
 444 
 445     static File[] checkFiles(File[] files) {
 446         SecurityManager sm = System.getSecurityManager();
 447         if (sm == null || files == null || files.length == 0) {
 448             return files;
 449         }
 450         return checkFiles(Arrays.stream(files), sm);
 451     }
 452 
 453     private static File[] checkFiles(List<File> files) {
 454         SecurityManager sm = System.getSecurityManager();
 455         if (sm == null || files.isEmpty()) {
 456             return files.toArray(new File[files.size()]);
 457         }
 458         return checkFiles(files.stream(), sm);
 459     }
 460 
 461     private static File[] checkFiles(Stream<File> filesStream, SecurityManager sm) {
 462         return filesStream.filter((file) -> checkFile(file, sm) != null)
 463                 .toArray(File[]::new);
 464     }
 465 
 466     /**
 467      * Does {@code dir} represent a "computer" such as a node on the network, or
 468      * "My Computer" on the desktop.
 469      */
 470     public boolean isComputerNode(final File dir) {
 471         if (dir != null && dir == getDrives()) {
 472             return true;
 473         } else {
 474             String path = AccessController.doPrivileged(new PrivilegedAction<String>() {
 475                 public String run() {
 476                     return dir.getAbsolutePath();
 477                 }
 478             });
 479 
 480             return (path.startsWith("\\\\") && path.indexOf("\\", 2) < 0);      //Network path
 481         }
 482     }
 483 
 484     public boolean isFileSystemRoot(File dir) {
 485         //Note: Removable drives don't "exist" but are listed in "My Computer"
 486         if (dir != null) {
 487 
 488             if (dir instanceof Win32ShellFolder2) {
 489                 Win32ShellFolder2 sf = (Win32ShellFolder2)dir;
 490 
 491                 //This includes all the drives under "My PC" or "My Computer.
 492                 // On windows 10, "External Drives" are listed under "Desktop"
 493                 // also
 494                 return  (sf.isFileSystem() && sf.parent != null &&
 495                         (sf.parent.equals (getDrives()) ||
 496                         (sf.parent.equals (getDesktop()) && isDrive(dir))));
 497             }
 498             return isDrive(dir);
 499         }
 500         return false;
 501     }
 502 
 503     private boolean isDrive(File dir) {
 504         String path = dir.getPath();
 505         if (path.length() != 3 || path.charAt(1) != ':') {
 506             return false;
 507         }
 508         File[] roots = Win32ShellFolder2.listRoots();
 509         return roots != null && Arrays.asList(roots).contains(dir);
 510     }
 511 
 512     private static List<Win32ShellFolder2> topFolderList = null;
 513     static int compareShellFolders(Win32ShellFolder2 sf1, Win32ShellFolder2 sf2) {
 514         boolean special1 = sf1.isSpecial();
 515         boolean special2 = sf2.isSpecial();
 516 
 517         if (special1 || special2) {
 518             if (topFolderList == null) {
 519                 ArrayList<Win32ShellFolder2> tmpTopFolderList = new ArrayList<>();
 520                 tmpTopFolderList.add(Win32ShellFolderManager2.getPersonal());
 521                 tmpTopFolderList.add(Win32ShellFolderManager2.getDesktop());
 522                 tmpTopFolderList.add(Win32ShellFolderManager2.getDrives());
 523                 tmpTopFolderList.add(Win32ShellFolderManager2.getNetwork());
 524                 topFolderList = tmpTopFolderList;
 525             }
 526             int i1 = topFolderList.indexOf(sf1);
 527             int i2 = topFolderList.indexOf(sf2);
 528             if (i1 >= 0 && i2 >= 0) {
 529                 return (i1 - i2);
 530             } else if (i1 >= 0) {
 531                 return -1;
 532             } else if (i2 >= 0) {
 533                 return 1;
 534             }
 535         }
 536 
 537         // Non-file shellfolders sort before files
 538         if (special1 && !special2) {
 539             return -1;
 540         } else if (special2 && !special1) {
 541             return  1;
 542         }
 543 
 544         return compareNames(sf1.getAbsolutePath(), sf2.getAbsolutePath());
 545     }
 546 
 547     static int compareNames(String name1, String name2) {
 548         // First ignore case when comparing
 549         int diff = name1.compareToIgnoreCase(name2);
 550         if (diff != 0) {
 551             return diff;
 552         } else {
 553             // May differ in case (e.g. "mail" vs. "Mail")
 554             // We need this test for consistent sorting
 555             return name1.compareTo(name2);
 556         }
 557     }
 558 
 559     @Override
 560     protected Invoker createInvoker() {
 561         return new ComInvoker();
 562     }
 563 
 564     private static class ComInvoker extends ThreadPoolExecutor implements ThreadFactory, ShellFolder.Invoker {
 565         private static Thread comThread;
 566 
 567         private ComInvoker() {
 568             super(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<>());
 569             allowCoreThreadTimeOut(false);
 570             setThreadFactory(this);
 571             final Runnable shutdownHook = () -> AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 572                 shutdownNow();
 573                 return null;
 574             });
 575             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 576                 Thread t = new Thread(
 577                         ThreadGroupUtils.getRootThreadGroup(), shutdownHook,
 578                         "ShellFolder", 0, false);
 579                 Runtime.getRuntime().addShutdownHook(t);
 580                 return null;
 581             });
 582         }
 583 
 584         public synchronized Thread newThread(final Runnable task) {
 585             final Runnable comRun = new Runnable() {
 586                 public void run() {
 587                     try {
 588                         initializeCom();
 589                         task.run();
 590                     } finally {
 591                         uninitializeCom();
 592                     }
 593                 }
 594             };
 595             comThread = AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
 596                 String name = "Swing-Shell";
 597                  /* The thread must be a member of a thread group
 598                   * which will not get GCed before VM exit.
 599                   * Make its parent the top-level thread group.
 600                   */
 601                 Thread thread = new Thread(
 602                         ThreadGroupUtils.getRootThreadGroup(), comRun, name,
 603                         0, false);
 604                 thread.setDaemon(true);
 605                 /* This is important, since this thread running at lower priority
 606                    leads to memory consumption when listDrives() function is called
 607                    repeatedly.
 608                  */
 609                 thread.setPriority(Thread.MAX_PRIORITY);
 610                 return thread;
 611             });
 612             return comThread;
 613         }
 614 
 615         public <T> T invoke(Callable<T> task) throws Exception {
 616             if (Thread.currentThread() == comThread) {
 617                 // if it's already called from the COM
 618                 // thread, we don't need to delegate the task
 619                 return task.call();
 620             } else {
 621                 final Future<T> future;
 622 
 623                 try {
 624                     future = submit(task);
 625                 } catch (RejectedExecutionException e) {
 626                     throw new InterruptedException(e.getMessage());
 627                 }
 628 
 629                 try {
 630                     return future.get();
 631                 } catch (InterruptedException e) {
 632                     AccessController.doPrivileged(new PrivilegedAction<Void>() {
 633                         public Void run() {
 634                             future.cancel(true);
 635 
 636                             return null;
 637                         }
 638                     });
 639 
 640                     throw e;
 641                 } catch (ExecutionException e) {
 642                     Throwable cause = e.getCause();
 643 
 644                     if (cause instanceof Exception) {
 645                         throw (Exception) cause;
 646                     }
 647 
 648                     if (cause instanceof Error) {
 649                         throw (Error) cause;
 650                     }
 651 
 652                     throw new RuntimeException("Unexpected error", cause);
 653                 }
 654             }
 655         }
 656     }
 657 
 658     static native void initializeCom();
 659 
 660     static native void uninitializeCom();
 661 }