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