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