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