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