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