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