View Javadoc
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  
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 }