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 }