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