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