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