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