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