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