/*
 * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.shell;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import sun.awt.OSInfo;
import sun.misc.ThreadGroupUtils;
import sun.util.logging.PlatformLogger;

import static sun.awt.shell.Win32ShellFolder2.DESKTOP;
import static sun.awt.shell.Win32ShellFolder2.DRIVES;
import static sun.awt.shell.Win32ShellFolder2.Invoker;
import static sun.awt.shell.Win32ShellFolder2.NETWORK;
import static sun.awt.shell.Win32ShellFolder2.PERSONAL;
import static sun.awt.shell.Win32ShellFolder2.RECENT;

// NOTE: This class supersedes Win32ShellFolderManager, which was removed
//       from distribution after version 1.4.2.

Author:Michael Martak, Leif Samuelsson, Kenneth Russell
Since:1.4
/** * @author Michael Martak * @author Leif Samuelsson * @author Kenneth Russell * @since 1.4 */
public class Win32ShellFolderManager2 extends ShellFolderManager { private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.shell.Win32ShellFolderManager2"); static { // Load library here sun.awt.windows.WToolkit.loadLibraries(); } public ShellFolder createShellFolder(File file) throws FileNotFoundException { try { return createShellFolder(getDesktop(), file); } catch (InterruptedException e) { throw new FileNotFoundException("Execution was interrupted"); } } static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, File file) throws FileNotFoundException, InterruptedException { long pIDL; try { pIDL = parent.parseDisplayName(file.getCanonicalPath()); } catch (IOException ex) { pIDL = 0; } if (pIDL == 0) { // Shouldn't happen but watch for it anyway throw new FileNotFoundException("File " + file.getAbsolutePath() + " not found"); } try { return createShellFolderFromRelativePIDL(parent, pIDL); } finally { Win32ShellFolder2.releasePIDL(pIDL); } } static Win32ShellFolder2 createShellFolderFromRelativePIDL(Win32ShellFolder2 parent, long pIDL) throws InterruptedException { // Walk down this relative pIDL, creating new nodes for each of the entries while (pIDL != 0) { long curPIDL = Win32ShellFolder2.copyFirstPIDLEntry(pIDL); if (curPIDL != 0) { parent = new Win32ShellFolder2(parent, curPIDL); pIDL = Win32ShellFolder2.getNextPIDLEntry(pIDL); } else { // The list is empty if the parent is Desktop and pIDL is a shortcut to Desktop break; } } return parent; } private static final int VIEW_LIST = 2; private static final int VIEW_DETAILS = 3; private static final int VIEW_PARENTFOLDER = 8; private static final int VIEW_NEWFOLDER = 11; private static final Image[] STANDARD_VIEW_BUTTONS = new Image[12]; private static Image getStandardViewButton(int iconIndex) { Image result = STANDARD_VIEW_BUTTONS[iconIndex]; if (result != null) { return result; } BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); img.setRGB(0, 0, 16, 16, Win32ShellFolder2.getStandardViewButton0(iconIndex), 0, 16); STANDARD_VIEW_BUTTONS[iconIndex] = img; return img; } // Special folders private static Win32ShellFolder2 desktop; private static Win32ShellFolder2 drives; private static Win32ShellFolder2 recent; private static Win32ShellFolder2 network; private static Win32ShellFolder2 personal; static Win32ShellFolder2 getDesktop() { if (desktop == null) { try { desktop = new Win32ShellFolder2(DESKTOP); } catch (final SecurityException ignored) { // Ignore, the message may have sensitive information, not // accessible other ways } catch (IOException | InterruptedException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot access 'Desktop'", e); } } } return desktop; } static Win32ShellFolder2 getDrives() { if (drives == null) { try { drives = new Win32ShellFolder2(DRIVES); } catch (final SecurityException ignored) { // Ignore, the message may have sensitive information, not // accessible other ways } catch (IOException | InterruptedException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot access 'Drives'", e); } } } return drives; } static Win32ShellFolder2 getRecent() { if (recent == null) { try { String path = Win32ShellFolder2.getFileSystemPath(RECENT); if (path != null) { recent = createShellFolder(getDesktop(), new File(path)); } } catch (final SecurityException ignored) { // Ignore, the message may have sensitive information, not // accessible other ways } catch (InterruptedException | IOException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot access 'Recent'", e); } } } return recent; } static Win32ShellFolder2 getNetwork() { if (network == null) { try { network = new Win32ShellFolder2(NETWORK); } catch (final SecurityException ignored) { // Ignore, the message may have sensitive information, not // accessible other ways } catch (IOException | InterruptedException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot access 'Network'", e); } } } return network; } static Win32ShellFolder2 getPersonal() { if (personal == null) { try { String path = Win32ShellFolder2.getFileSystemPath(PERSONAL); if (path != null) { Win32ShellFolder2 desktop = getDesktop(); personal = desktop.getChildByPath(path); if (personal == null) { personal = createShellFolder(getDesktop(), new File(path)); } if (personal != null) { personal.setIsPersonal(); } } } catch (final SecurityException ignored) { // Ignore, the message may have sensitive information, not // accessible other ways } catch (InterruptedException | IOException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot access 'Personal'", e); } } } return personal; } private static File[] roots;
Params:
  • key – a String "fileChooserDefaultFolder": Returns a File - the default shellfolder for a new filechooser "roots": Returns a File[] - containing the root(s) of the displayable hierarchy "fileChooserComboBoxFolders": Returns a File[] - an array of shellfolders representing the list to show by default in the file chooser's combobox "fileChooserShortcutPanelFolders": Returns a File[] - an array of shellfolders representing well-known folders, such as Desktop, Documents, History, Network, Home, etc. This is used in the shortcut panel of the filechooser on Windows 2000 and Windows Me. "fileChooserIcon ": Returns an Image - icon can be ListView, DetailsView, UpFolder, NewFolder or ViewMenu (Windows only). "optionPaneIcon iconName": Returns an Image - icon from the system icon list
Returns:An Object matching the key string.
/** * @param key a <code>String</code> * "fileChooserDefaultFolder": * Returns a <code>File</code> - the default shellfolder for a new filechooser * "roots": * Returns a <code>File[]</code> - containing the root(s) of the displayable hierarchy * "fileChooserComboBoxFolders": * Returns a <code>File[]</code> - an array of shellfolders representing the list to * show by default in the file chooser's combobox * "fileChooserShortcutPanelFolders": * Returns a <code>File[]</code> - an array of shellfolders representing well-known * folders, such as Desktop, Documents, History, Network, Home, etc. * This is used in the shortcut panel of the filechooser on Windows 2000 * and Windows Me. * "fileChooserIcon <icon>": * Returns an <code>Image</code> - icon can be ListView, DetailsView, UpFolder, NewFolder or * ViewMenu (Windows only). * "optionPaneIcon iconName": * Returns an <code>Image</code> - icon from the system icon list * * @return An Object matching the key string. */
public Object get(String key) { if (key.equals("fileChooserDefaultFolder")) { File file = getPersonal(); if (file == null) { file = getDesktop(); } return checkFile(file); } else if (key.equals("roots")) { // Should be "History" and "Desktop" ? if (roots == null) { File desktop = getDesktop(); if (desktop != null) { roots = new File[] { desktop }; } else { roots = (File[])super.get(key); } } return checkFiles(roots); } else if (key.equals("fileChooserComboBoxFolders")) { Win32ShellFolder2 desktop = getDesktop(); if (desktop != null && checkFile(desktop) != null) { ArrayList<File> folders = new ArrayList<File>(); Win32ShellFolder2 drives = getDrives(); Win32ShellFolder2 recentFolder = getRecent(); if (recentFolder != null && OSInfo.getWindowsVersion().compareTo(OSInfo.WINDOWS_2000) >= 0) { folders.add(recentFolder); } folders.add(desktop); // Add all second level folders File[] secondLevelFolders = checkFiles(desktop.listFiles()); Arrays.sort(secondLevelFolders); for (File secondLevelFolder : secondLevelFolders) { Win32ShellFolder2 folder = (Win32ShellFolder2) secondLevelFolder; if (!folder.isFileSystem() || (folder.isDirectory() && !folder.isLink())) { folders.add(folder); // Add third level for "My Computer" if (folder.equals(drives)) { File[] thirdLevelFolders = checkFiles(folder.listFiles()); if (thirdLevelFolders != null && thirdLevelFolders.length > 0) { List<File> thirdLevelFoldersList = Arrays.asList(thirdLevelFolders); folder.sortChildren(thirdLevelFoldersList); folders.addAll(thirdLevelFoldersList); } } } } return checkFiles(folders); } else { return super.get(key); } } else if (key.equals("fileChooserShortcutPanelFolders")) { Toolkit toolkit = Toolkit.getDefaultToolkit(); ArrayList<File> folders = new ArrayList<File>(); int i = 0; Object value; do { value = toolkit.getDesktopProperty("win.comdlg.placesBarPlace" + i++); try { if (value instanceof Integer) { // A CSIDL folders.add(new Win32ShellFolder2((Integer)value)); } else if (value instanceof String) { // A path folders.add(createShellFolder(new File((String)value))); } } catch (IOException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot read value = " + value, e); } // Skip this value } catch (InterruptedException e) { if (log.isLoggable(PlatformLogger.Level.WARNING)) { log.warning("Cannot read value = " + value, e); } // Return empty result return new File[0]; } } while (value != null); if (folders.size() == 0) { // Use default list of places for (File f : new File[] { getRecent(), getDesktop(), getPersonal(), getDrives(), getNetwork() }) { if (f != null) { folders.add(f); } } } return checkFiles(folders); } else if (key.startsWith("fileChooserIcon ")) { String name = key.substring(key.indexOf(" ") + 1); int iconIndex; if (name.equals("ListView") || name.equals("ViewMenu")) { iconIndex = VIEW_LIST; } else if (name.equals("DetailsView")) { iconIndex = VIEW_DETAILS; } else if (name.equals("UpFolder")) { iconIndex = VIEW_PARENTFOLDER; } else if (name.equals("NewFolder")) { iconIndex = VIEW_NEWFOLDER; } else { return null; } return getStandardViewButton(iconIndex); } else if (key.startsWith("optionPaneIcon ")) { Win32ShellFolder2.SystemIcon iconType; if (key == "optionPaneIcon Error") { iconType = Win32ShellFolder2.SystemIcon.IDI_ERROR; } else if (key == "optionPaneIcon Information") { iconType = Win32ShellFolder2.SystemIcon.IDI_INFORMATION; } else if (key == "optionPaneIcon Question") { iconType = Win32ShellFolder2.SystemIcon.IDI_QUESTION; } else if (key == "optionPaneIcon Warning") { iconType = Win32ShellFolder2.SystemIcon.IDI_EXCLAMATION; } else { return null; } return Win32ShellFolder2.getSystemIcon(iconType); } else if (key.startsWith("shell32Icon ") || key.startsWith("shell32LargeIcon ")) { String name = key.substring(key.indexOf(" ") + 1); try { int i = Integer.parseInt(name); if (i >= 0) { return Win32ShellFolder2.getShell32Icon(i, key.startsWith("shell32LargeIcon ")); } } catch (NumberFormatException ex) { } } return null; } private static File checkFile(File file) { SecurityManager sm = System.getSecurityManager(); return (sm == null || file == null) ? file : checkFile(file, sm); } private static File checkFile(File file, SecurityManager sm) { try { sm.checkRead(file.getPath()); if (file instanceof Win32ShellFolder2) { Win32ShellFolder2 f = (Win32ShellFolder2)file; if (f.isLink()) { Win32ShellFolder2 link = (Win32ShellFolder2)f.getLinkLocation(); if (link != null) sm.checkRead(link.getPath()); } } return file; } catch (SecurityException se) { return null; } } static File[] checkFiles(File[] files) { SecurityManager sm = System.getSecurityManager(); if (sm == null || files == null || files.length == 0) { return files; } return checkFiles(Arrays.stream(files), sm); } private static File[] checkFiles(List<File> files) { SecurityManager sm = System.getSecurityManager(); if (sm == null || files.isEmpty()) { return files.toArray(new File[files.size()]); } return checkFiles(files.stream(), sm); } private static File[] checkFiles(Stream<File> filesStream, SecurityManager sm) { return filesStream.filter((file) -> checkFile(file, sm) != null) .toArray(File[]::new); }
Does dir represent a "computer" such as a node on the network, or "My Computer" on the desktop.
/** * Does <code>dir</code> represent a "computer" such as a node on the network, or * "My Computer" on the desktop. */
public boolean isComputerNode(final File dir) { if (dir != null && dir == getDrives()) { return true; } else { String path = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return dir.getAbsolutePath(); } }); return (path.startsWith("\\\\") && path.indexOf("\\", 2) < 0); //Network path } } public boolean isFileSystemRoot(File dir) { //Note: Removable drives don't "exist" but are listed in "My Computer" if (dir != null) { Win32ShellFolder2 drives = getDrives(); if (dir instanceof Win32ShellFolder2) { Win32ShellFolder2 sf = (Win32ShellFolder2)dir; if (sf.isFileSystem()) { if (sf.parent != null) { return sf.parent.equals(drives); } // else fall through ... } else { return false; } } String path = dir.getPath(); if (path.length() != 3 || path.charAt(1) != ':') { return false; } File[] files = drives.listFiles(); return files != null && Arrays.asList(files).contains(dir); } return false; } private static List topFolderList = null; static int compareShellFolders(Win32ShellFolder2 sf1, Win32ShellFolder2 sf2) { boolean special1 = sf1.isSpecial(); boolean special2 = sf2.isSpecial(); if (special1 || special2) { if (topFolderList == null) { ArrayList tmpTopFolderList = new ArrayList(); tmpTopFolderList.add(Win32ShellFolderManager2.getPersonal()); tmpTopFolderList.add(Win32ShellFolderManager2.getDesktop()); tmpTopFolderList.add(Win32ShellFolderManager2.getDrives()); tmpTopFolderList.add(Win32ShellFolderManager2.getNetwork()); topFolderList = tmpTopFolderList; } int i1 = topFolderList.indexOf(sf1); int i2 = topFolderList.indexOf(sf2); if (i1 >= 0 && i2 >= 0) { return (i1 - i2); } else if (i1 >= 0) { return -1; } else if (i2 >= 0) { return 1; } } // Non-file shellfolders sort before files if (special1 && !special2) { return -1; } else if (special2 && !special1) { return 1; } return compareNames(sf1.getAbsolutePath(), sf2.getAbsolutePath()); } static int compareNames(String name1, String name2) { // First ignore case when comparing int diff = name1.compareToIgnoreCase(name2); if (diff != 0) { return diff; } else { // May differ in case (e.g. "mail" vs. "Mail") // We need this test for consistent sorting return name1.compareTo(name2); } } @Override protected Invoker createInvoker() { return new ComInvoker(); } private static class ComInvoker extends ThreadPoolExecutor implements ThreadFactory, ShellFolder.Invoker { private static Thread comThread; private ComInvoker() { super(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>()); allowCoreThreadTimeOut(false); setThreadFactory(this); final Runnable shutdownHook = new Runnable() { public void run() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { shutdownNow(); return null; } }); } }; AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { Runtime.getRuntime().addShutdownHook( new Thread(shutdownHook) ); return null; } }); } public synchronized Thread newThread(final Runnable task) { final Runnable comRun = new Runnable() { public void run() { try { initializeCom(); task.run(); } finally { uninitializeCom(); } } }; comThread = AccessController.doPrivileged((PrivilegedAction<Thread>) () -> { /* The thread must be a member of a thread group * which will not get GCed before VM exit. * Make its parent the top-level thread group. */ ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); Thread thread = new Thread(rootTG, comRun, "Swing-Shell"); thread.setDaemon(true); return thread; } ); return comThread; } public <T> T invoke(Callable<T> task) throws Exception { if (Thread.currentThread() == comThread) { // if it's already called from the COM // thread, we don't need to delegate the task return task.call(); } else { final Future<T> future; try { future = submit(task); } catch (RejectedExecutionException e) { throw new InterruptedException(e.getMessage()); } try { return future.get(); } catch (InterruptedException e) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { future.cancel(true); return null; } }); throw e; } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Exception) { throw (Exception) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unexpected error", cause); } } } } static native void initializeCom(); static native void uninitializeCom(); }