1 /* 2 * Copyright (c) 2003, 2018, 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 import java.awt.image.BaseMultiResolutionImage; 31 32 import java.io.File; 33 import java.io.FileNotFoundException; 34 import java.io.IOException; 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 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 static File checkFile(File file) { 391 SecurityManager sm = System.getSecurityManager(); 392 return (sm == null || file == null) ? file : checkFile(file, sm); 393 } 394 395 private static File checkFile(File file, SecurityManager sm) { 396 try { 397 sm.checkRead(file.getPath()); 398 399 if (file instanceof Win32ShellFolder2) { 400 Win32ShellFolder2 f = (Win32ShellFolder2)file; 401 if (f.isLink()) { 402 Win32ShellFolder2 link = (Win32ShellFolder2)f.getLinkLocation(); 403 if (link != null) 404 sm.checkRead(link.getPath()); 405 } 406 } 407 return file; 408 } catch (SecurityException se) { 409 return null; 410 } 411 } 412 413 static 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 static 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 static 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 456 if (dir instanceof Win32ShellFolder2) { 457 Win32ShellFolder2 sf = (Win32ShellFolder2)dir; 458 459 //This includes all the drives under "My PC" or "My Computer. 460 // On windows 10, "External Drives" are listed under "Desktop" 461 // also 462 return (sf.isFileSystem() && sf.parent != null && 463 (sf.parent.equals (getDrives()) || 464 (sf.parent.equals (getDesktop()) && isDrive(dir)))); 465 } 466 return isDrive(dir); 467 } 468 return false; 469 } 470 471 private boolean isDrive(File dir) { 472 String path = dir.getPath(); 473 if (path.length() != 3 || path.charAt(1) != ':') { 474 return false; 475 } 476 File[] roots = Win32ShellFolder2.listRoots(); 477 return roots != null && Arrays.asList(roots).contains(dir); 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 /* This is important, since this thread running at lower priority 574 leads to memory consumption when listDrives() function is called 575 repeatedly. 576 */ 577 thread.setPriority(Thread.MAX_PRIORITY); 578 return thread; 579 }); 580 return comThread; 581 } 582 583 public <T> T invoke(Callable<T> task) throws Exception { 584 if (Thread.currentThread() == comThread) { 585 // if it's already called from the COM 586 // thread, we don't need to delegate the task 587 return task.call(); 588 } else { 589 final Future<T> future; 590 591 try { 592 future = submit(task); 593 } catch (RejectedExecutionException e) { 594 throw new InterruptedException(e.getMessage()); 595 } 596 597 try { 598 return future.get(); 599 } catch (InterruptedException e) { 600 AccessController.doPrivileged(new PrivilegedAction<Void>() { 601 public Void run() { 602 future.cancel(true); 603 604 return null; 605 } 606 }); 607 608 throw e; 609 } catch (ExecutionException e) { 610 Throwable cause = e.getCause(); 611 612 if (cause instanceof Exception) { 613 throw (Exception) cause; 614 } 615 616 if (cause instanceof Error) { 617 throw (Error) cause; 618 } 619 620 throw new RuntimeException("Unexpected error", cause); 621 } 622 } 623 } 624 } 625 626 static native void initializeCom(); 627 628 static native void uninitializeCom(); 629 }