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 
  40 import static sun.awt.shell.Win32ShellFolder2.*;
  41 import sun.awt.OSInfo;
  42 import sun.awt.util.ThreadGroupUtils;
  43 
  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 public 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 = new Win32ShellFolder2(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 (IOException e) {
 140                 // Ignore error
 141             } catch (InterruptedException e) {
 142                 // Ignore error
 143             }
 144         }
 145         return desktop;
 146     }
 147 
 148     static Win32ShellFolder2 getDrives() {
 149         if (drives == null) {
 150             try {
 151                 drives = new Win32ShellFolder2(DRIVES);
 152             } catch (IOException e) {
 153                 // Ignore error
 154             } catch (InterruptedException e) {
 155                 // Ignore error
 156             }
 157         }
 158         return drives;
 159     }
 160 
 161     static Win32ShellFolder2 getRecent() {
 162         if (recent == null) {
 163             try {
 164                 String path = Win32ShellFolder2.getFileSystemPath(RECENT);
 165                 if (path != null) {
 166                     recent = createShellFolder(getDesktop(), new File(path));
 167                 }
 168             } catch (InterruptedException e) {
 169                 // Ignore error
 170             } catch (IOException e) {
 171                 // Ignore error
 172             }
 173         }
 174         return recent;
 175     }
 176 
 177     static Win32ShellFolder2 getNetwork() {
 178         if (network == null) {
 179             try {
 180                 network = new Win32ShellFolder2(NETWORK);
 181             } catch (IOException e) {
 182                 // Ignore error
 183             } catch (InterruptedException e) {
 184                 // Ignore error
 185             }
 186         }
 187         return network;
 188     }
 189 
 190     static Win32ShellFolder2 getPersonal() {
 191         if (personal == null) {
 192             try {
 193                 String path = Win32ShellFolder2.getFileSystemPath(PERSONAL);
 194                 if (path != null) {
 195                     Win32ShellFolder2 desktop = getDesktop();
 196                     personal = desktop.getChildByPath(path);
 197                     if (personal == null) {
 198                         personal = createShellFolder(getDesktop(), new File(path));
 199                     }
 200                     if (personal != null) {
 201                         personal.setIsPersonal();
 202                     }
 203                 }
 204             } catch (InterruptedException e) {
 205                 // Ignore error
 206             } catch (IOException e) {
 207                 // Ignore error
 208             }
 209         }
 210         return personal;
 211     }
 212 
 213 
 214     private static File[] roots;
 215 
 216     /**
 217      * @param key a <code>String</code>
 218      *  "fileChooserDefaultFolder":
 219      *    Returns a <code>File</code> - the default shellfolder for a new filechooser
 220      *  "roots":
 221      *    Returns a <code>File[]</code> - containing the root(s) of the displayable hierarchy
 222      *  "fileChooserComboBoxFolders":
 223      *    Returns a <code>File[]</code> - an array of shellfolders representing the list to
 224      *    show by default in the file chooser's combobox
 225      *   "fileChooserShortcutPanelFolders":
 226      *    Returns a <code>File[]</code> - an array of shellfolders representing well-known
 227      *    folders, such as Desktop, Documents, History, Network, Home, etc.
 228      *    This is used in the shortcut panel of the filechooser on Windows 2000
 229      *    and Windows Me.
 230      *  "fileChooserIcon <icon>":
 231      *    Returns an <code>Image</code> - icon can be ListView, DetailsView, UpFolder, NewFolder or
 232      *    ViewMenu (Windows only).
 233      *  "optionPaneIcon iconName":
 234      *    Returns an <code>Image</code> - icon from the system icon list
 235      *
 236      * @return An Object matching the key string.
 237      */
 238     public Object get(String key) {
 239         if (key.equals("fileChooserDefaultFolder")) {
 240             File file = getPersonal();
 241             if (file == null) {
 242                 file = getDesktop();
 243             }
 244             return file;
 245         } else if (key.equals("roots")) {
 246             // Should be "History" and "Desktop" ?
 247             if (roots == null) {
 248                 File desktop = getDesktop();
 249                 if (desktop != null) {
 250                     roots = new File[] { desktop };
 251                 } else {
 252                     roots = (File[])super.get(key);
 253                 }
 254             }
 255             return roots;
 256         } else if (key.equals("fileChooserComboBoxFolders")) {
 257             Win32ShellFolder2 desktop = getDesktop();
 258 
 259             if (desktop != null) {
 260                 ArrayList<File> folders = new ArrayList<File>();
 261                 Win32ShellFolder2 drives = getDrives();
 262 
 263                 Win32ShellFolder2 recentFolder = getRecent();
 264                 if (recentFolder != null && OSInfo.getWindowsVersion().compareTo(OSInfo.WINDOWS_2000) >= 0) {
 265                     folders.add(recentFolder);
 266                 }
 267 
 268                 folders.add(desktop);
 269                 // Add all second level folders
 270                 File[] secondLevelFolders = desktop.listFiles();
 271                 Arrays.sort(secondLevelFolders);
 272                 for (File secondLevelFolder : secondLevelFolders) {
 273                     Win32ShellFolder2 folder = (Win32ShellFolder2) secondLevelFolder;
 274                     if (!folder.isFileSystem() || (folder.isDirectory() && !folder.isLink())) {
 275                         folders.add(folder);
 276                         // Add third level for "My Computer"
 277                         if (folder.equals(drives)) {
 278                             File[] thirdLevelFolders = folder.listFiles();
 279                             if (thirdLevelFolders != null && thirdLevelFolders.length > 0) {
 280                                 List<File> thirdLevelFoldersList = Arrays.asList(thirdLevelFolders);
 281 
 282                                 folder.sortChildren(thirdLevelFoldersList);
 283                                 folders.addAll(thirdLevelFoldersList);
 284                             }
 285                         }
 286                     }
 287                 }
 288                 return folders.toArray(new File[folders.size()]);
 289             } else {
 290                 return super.get(key);
 291             }
 292         } else if (key.equals("fileChooserShortcutPanelFolders")) {
 293             Toolkit toolkit = Toolkit.getDefaultToolkit();
 294             ArrayList<File> folders = new ArrayList<File>();
 295             int i = 0;
 296             Object value;
 297             do {
 298                 value = toolkit.getDesktopProperty("win.comdlg.placesBarPlace" + i++);
 299                 try {
 300                     if (value instanceof Integer) {
 301                         // A CSIDL
 302                         folders.add(new Win32ShellFolder2((Integer)value));
 303                     } else if (value instanceof String) {
 304                         // A path
 305                         folders.add(createShellFolder(new File((String)value)));
 306                     }
 307                 } catch (IOException e) {
 308                     // Skip this value
 309                 } catch (InterruptedException e) {
 310                     // Return empty result
 311                     return new File[0];
 312                 }
 313             } while (value != null);
 314 
 315             if (folders.size() == 0) {
 316                 // Use default list of places
 317                 for (File f : new File[] {
 318                     getRecent(), getDesktop(), getPersonal(), getDrives(), getNetwork()
 319                 }) {
 320                     if (f != null) {
 321                         folders.add(f);
 322                     }
 323                 }
 324             }
 325             return folders.toArray(new File[folders.size()]);
 326         } else if (key.startsWith("fileChooserIcon ")) {
 327             String name = key.substring(key.indexOf(" ") + 1);
 328 
 329             int iconIndex;
 330 
 331             if (name.equals("ListView") || name.equals("ViewMenu")) {
 332                 iconIndex = VIEW_LIST;
 333             } else if (name.equals("DetailsView")) {
 334                 iconIndex = VIEW_DETAILS;
 335             } else if (name.equals("UpFolder")) {
 336                 iconIndex = VIEW_PARENTFOLDER;
 337             } else if (name.equals("NewFolder")) {
 338                 iconIndex = VIEW_NEWFOLDER;
 339             } else {
 340                 return null;
 341             }
 342 
 343             return getStandardViewButton(iconIndex);
 344         } else if (key.startsWith("optionPaneIcon ")) {
 345             Win32ShellFolder2.SystemIcon iconType;
 346             if (key == "optionPaneIcon Error") {
 347                 iconType = Win32ShellFolder2.SystemIcon.IDI_ERROR;
 348             } else if (key == "optionPaneIcon Information") {
 349                 iconType = Win32ShellFolder2.SystemIcon.IDI_INFORMATION;
 350             } else if (key == "optionPaneIcon Question") {
 351                 iconType = Win32ShellFolder2.SystemIcon.IDI_QUESTION;
 352             } else if (key == "optionPaneIcon Warning") {
 353                 iconType = Win32ShellFolder2.SystemIcon.IDI_EXCLAMATION;
 354             } else {
 355                 return null;
 356             }
 357             return Win32ShellFolder2.getSystemIcon(iconType);
 358         } else if (key.startsWith("shell32Icon ") || key.startsWith("shell32LargeIcon ")) {
 359             String name = key.substring(key.indexOf(" ") + 1);
 360             try {
 361                 int i = Integer.parseInt(name);
 362                 if (i >= 0) {
 363                     return Win32ShellFolder2.getShell32Icon(i, key.startsWith("shell32LargeIcon "));
 364                 }
 365             } catch (NumberFormatException ex) {
 366             }
 367         }
 368         return null;
 369     }
 370 
 371     /**
 372      * Does <code>dir</code> represent a "computer" such as a node on the network, or
 373      * "My Computer" on the desktop.
 374      */
 375     public boolean isComputerNode(final File dir) {
 376         if (dir != null && dir == getDrives()) {
 377             return true;
 378         } else {
 379             String path = AccessController.doPrivileged(new PrivilegedAction<String>() {
 380                 public String run() {
 381                     return dir.getAbsolutePath();
 382                 }
 383             });
 384 
 385             return (path.startsWith("\\\\") && path.indexOf("\\", 2) < 0);      //Network path
 386         }
 387     }
 388 
 389     public boolean isFileSystemRoot(File dir) {
 390         //Note: Removable drives don't "exist" but are listed in "My Computer"
 391         if (dir != null) {
 392             Win32ShellFolder2 drives = getDrives();
 393             if (dir instanceof Win32ShellFolder2) {
 394                 Win32ShellFolder2 sf = (Win32ShellFolder2)dir;
 395                 if (sf.isFileSystem()) {
 396                     if (sf.parent != null) {
 397                         return sf.parent.equals(drives);
 398                     }
 399                     // else fall through ...
 400                 } else {
 401                     return false;
 402                 }
 403             }
 404             String path = dir.getPath();
 405 
 406             if (path.length() != 3 || path.charAt(1) != ':') {
 407                 return false;
 408             }
 409 
 410             File[] files = drives.listFiles();
 411 
 412             return files != null && Arrays.asList(files).contains(dir);
 413         }
 414         return false;
 415     }
 416 
 417     private static List topFolderList = null;
 418     static int compareShellFolders(Win32ShellFolder2 sf1, Win32ShellFolder2 sf2) {
 419         boolean special1 = sf1.isSpecial();
 420         boolean special2 = sf2.isSpecial();
 421 
 422         if (special1 || special2) {
 423             if (topFolderList == null) {
 424                 ArrayList tmpTopFolderList = new ArrayList();
 425                 tmpTopFolderList.add(Win32ShellFolderManager2.getPersonal());
 426                 tmpTopFolderList.add(Win32ShellFolderManager2.getDesktop());
 427                 tmpTopFolderList.add(Win32ShellFolderManager2.getDrives());
 428                 tmpTopFolderList.add(Win32ShellFolderManager2.getNetwork());
 429                 topFolderList = tmpTopFolderList;
 430             }
 431             int i1 = topFolderList.indexOf(sf1);
 432             int i2 = topFolderList.indexOf(sf2);
 433             if (i1 >= 0 && i2 >= 0) {
 434                 return (i1 - i2);
 435             } else if (i1 >= 0) {
 436                 return -1;
 437             } else if (i2 >= 0) {
 438                 return 1;
 439             }
 440         }
 441 
 442         // Non-file shellfolders sort before files
 443         if (special1 && !special2) {
 444             return -1;
 445         } else if (special2 && !special1) {
 446             return  1;
 447         }
 448 
 449         return compareNames(sf1.getAbsolutePath(), sf2.getAbsolutePath());
 450     }
 451 
 452     static int compareNames(String name1, String name2) {
 453         // First ignore case when comparing
 454         int diff = name1.compareToIgnoreCase(name2);
 455         if (diff != 0) {
 456             return diff;
 457         } else {
 458             // May differ in case (e.g. "mail" vs. "Mail")
 459             // We need this test for consistent sorting
 460             return name1.compareTo(name2);
 461         }
 462     }
 463 
 464     @Override
 465     protected Invoker createInvoker() {
 466         return new ComInvoker();
 467     }
 468 
 469     private static class ComInvoker extends ThreadPoolExecutor implements ThreadFactory, ShellFolder.Invoker {
 470         private static Thread comThread;
 471 
 472         private ComInvoker() {
 473             super(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
 474             allowCoreThreadTimeOut(false);
 475             setThreadFactory(this);
 476             final Runnable shutdownHook = new Runnable() {
 477                 public void run() {
 478                     AccessController.doPrivileged(new PrivilegedAction<Void>() {
 479                         public Void run() {
 480                             shutdownNow();
 481                             return null;
 482                         }
 483                     });
 484                 }
 485             };
 486             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 487                 public Void run() {
 488                     Runtime.getRuntime().addShutdownHook(
 489                         new Thread(shutdownHook)
 490                     );
 491                     return null;
 492                 }
 493             });
 494         }
 495 
 496         public synchronized Thread newThread(final Runnable task) {
 497             final Runnable comRun = new Runnable() {
 498                 public void run() {
 499                     try {
 500                         initializeCom();
 501                         task.run();
 502                     } finally {
 503                         uninitializeCom();
 504                     }
 505                 }
 506             };
 507             comThread =  AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
 508                             /* The thread must be a member of a thread group
 509                              * which will not get GCed before VM exit.
 510                              * Make its parent the top-level thread group.
 511                              */
 512                             ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup();
 513                             Thread thread = new Thread(rootTG, comRun, "Swing-Shell");
 514                             thread.setDaemon(true);
 515                             return thread;
 516                         }
 517                 );
 518             return comThread;
 519         }
 520 
 521         public <T> T invoke(Callable<T> task) throws Exception {
 522             if (Thread.currentThread() == comThread) {
 523                 // if it's already called from the COM
 524                 // thread, we don't need to delegate the task
 525                 return task.call();
 526             } else {
 527                 final Future<T> future;
 528 
 529                 try {
 530                     future = submit(task);
 531                 } catch (RejectedExecutionException e) {
 532                     throw new InterruptedException(e.getMessage());
 533                 }
 534 
 535                 try {
 536                     return future.get();
 537                 } catch (InterruptedException e) {
 538                     AccessController.doPrivileged(new PrivilegedAction<Void>() {
 539                         public Void run() {
 540                             future.cancel(true);
 541 
 542                             return null;
 543                         }
 544                     });
 545 
 546                     throw e;
 547                 } catch (ExecutionException e) {
 548                     Throwable cause = e.getCause();
 549 
 550                     if (cause instanceof Exception) {
 551                         throw (Exception) cause;
 552                     }
 553 
 554                     if (cause instanceof Error) {
 555                         throw (Error) cause;
 556                     }
 557 
 558                     throw new RuntimeException("Unexpected error", cause);
 559                 }
 560             }
 561         }
 562     }
 563 
 564     static native void initializeCom();
 565 
 566     static native void uninitializeCom();
 567 }