Path: blob/master/src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolderManager2.java
41159 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt.shell;2627import java.awt.Image;28import java.awt.Toolkit;29import java.awt.image.BufferedImage;30import java.io.File;31import java.io.FileNotFoundException;32import java.io.IOException;33import java.security.AccessController;34import java.security.PrivilegedAction;35import java.util.ArrayList;36import java.util.Arrays;37import java.util.List;38import java.util.concurrent.Callable;39import java.util.concurrent.ExecutionException;40import java.util.concurrent.Future;41import java.util.concurrent.LinkedBlockingQueue;42import java.util.concurrent.RejectedExecutionException;43import java.util.concurrent.ThreadFactory;44import java.util.concurrent.ThreadPoolExecutor;45import java.util.concurrent.TimeUnit;46import java.util.stream.Stream;4748import sun.awt.OSInfo;49import sun.awt.util.ThreadGroupUtils;50import sun.util.logging.PlatformLogger;5152import static sun.awt.shell.Win32ShellFolder2.DESKTOP;53import static sun.awt.shell.Win32ShellFolder2.DRIVES;54import static sun.awt.shell.Win32ShellFolder2.Invoker;55import static sun.awt.shell.Win32ShellFolder2.LARGE_ICON_SIZE;56import static sun.awt.shell.Win32ShellFolder2.MultiResolutionIconImage;57import static sun.awt.shell.Win32ShellFolder2.NETWORK;58import static sun.awt.shell.Win32ShellFolder2.PERSONAL;59import static sun.awt.shell.Win32ShellFolder2.RECENT;60import static sun.awt.shell.Win32ShellFolder2.SMALL_ICON_SIZE;61// NOTE: This class supersedes Win32ShellFolderManager, which was removed62// from distribution after version 1.4.2.6364/**65* @author Michael Martak66* @author Leif Samuelsson67* @author Kenneth Russell68* @since 1.469*/7071final class Win32ShellFolderManager2 extends ShellFolderManager {7273private static final PlatformLogger74log = PlatformLogger.getLogger("sun.awt.shell.Win32ShellFolderManager2");7576static {77// Load library here78sun.awt.windows.WToolkit.loadLibraries();79}8081public ShellFolder createShellFolder(File file) throws FileNotFoundException {82try {83return createShellFolder(getDesktop(), file);84} catch (InterruptedException e) {85throw new FileNotFoundException("Execution was interrupted");86}87}8889static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, File file)90throws FileNotFoundException, InterruptedException {91long pIDL;92try {93pIDL = parent.parseDisplayName(file.getCanonicalPath());94} catch (IOException ex) {95pIDL = 0;96}97if (pIDL == 0) {98// Shouldn't happen but watch for it anyway99throw new FileNotFoundException("File " + file.getAbsolutePath() + " not found");100}101102try {103return createShellFolderFromRelativePIDL(parent, pIDL);104} finally {105Win32ShellFolder2.releasePIDL(pIDL);106}107}108109static Win32ShellFolder2 createShellFolderFromRelativePIDL(Win32ShellFolder2 parent, long pIDL)110throws InterruptedException {111// Walk down this relative pIDL, creating new nodes for each of the entries112while (pIDL != 0) {113long curPIDL = Win32ShellFolder2.copyFirstPIDLEntry(pIDL);114if (curPIDL != 0) {115parent = Win32ShellFolder2.createShellFolder(parent, curPIDL);116pIDL = Win32ShellFolder2.getNextPIDLEntry(pIDL);117} else {118// The list is empty if the parent is Desktop and pIDL is a shortcut to Desktop119break;120}121}122return parent;123}124125private static final int VIEW_LIST = 2;126private static final int VIEW_DETAILS = 3;127private static final int VIEW_PARENTFOLDER = 8;128private static final int VIEW_NEWFOLDER = 11;129130private static final Image[] STANDARD_VIEW_BUTTONS = new Image[12];131132private static Image getStandardViewButton(int iconIndex) {133Image result = STANDARD_VIEW_BUTTONS[iconIndex];134135if (result != null) {136return result;137}138139final int[] iconBits = Win32ShellFolder2140.getStandardViewButton0(iconIndex, true);141if (iconBits != null) {142// icons are always square143final int size = (int) Math.sqrt(iconBits.length);144final BufferedImage img =145new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);146img.setRGB(0, 0, size, size, iconBits, 0, size);147148STANDARD_VIEW_BUTTONS[iconIndex] = (size == SMALL_ICON_SIZE)149? img150: new MultiResolutionIconImage(SMALL_ICON_SIZE, img);151}152153return STANDARD_VIEW_BUTTONS[iconIndex];154}155156// Special folders157private static Win32ShellFolder2 desktop;158private static Win32ShellFolder2 drives;159private static Win32ShellFolder2 recent;160private static Win32ShellFolder2 network;161private static Win32ShellFolder2 personal;162163static Win32ShellFolder2 getDesktop() {164if (desktop == null) {165try {166desktop = new Win32ShellFolder2(DESKTOP);167} catch (final SecurityException ignored) {168// Ignore, the message may have sensitive information, not169// accessible other ways170} catch (IOException | InterruptedException e) {171if (log.isLoggable(PlatformLogger.Level.WARNING)) {172log.warning("Cannot access 'Desktop'", e);173}174}175}176return desktop;177}178179static Win32ShellFolder2 getDrives() {180if (drives == null) {181try {182drives = new Win32ShellFolder2(DRIVES);183} catch (final SecurityException ignored) {184// Ignore, the message may have sensitive information, not185// accessible other ways186} catch (IOException | InterruptedException e) {187if (log.isLoggable(PlatformLogger.Level.WARNING)) {188log.warning("Cannot access 'Drives'", e);189}190}191}192return drives;193}194195static Win32ShellFolder2 getRecent() {196if (recent == null) {197try {198String path = Win32ShellFolder2.getFileSystemPath(RECENT);199if (path != null) {200recent = createShellFolder(getDesktop(), new File(path));201}202} catch (final SecurityException ignored) {203// Ignore, the message may have sensitive information, not204// accessible other ways205} catch (InterruptedException | IOException e) {206if (log.isLoggable(PlatformLogger.Level.WARNING)) {207log.warning("Cannot access 'Recent'", e);208}209}210}211return recent;212}213214static Win32ShellFolder2 getNetwork() {215if (network == null) {216try {217network = new Win32ShellFolder2(NETWORK);218} catch (final SecurityException ignored) {219// Ignore, the message may have sensitive information, not220// accessible other ways221} catch (IOException | InterruptedException e) {222if (log.isLoggable(PlatformLogger.Level.WARNING)) {223log.warning("Cannot access 'Network'", e);224}225}226}227return network;228}229230static Win32ShellFolder2 getPersonal() {231if (personal == null) {232try {233String path = Win32ShellFolder2.getFileSystemPath(PERSONAL);234if (path != null) {235Win32ShellFolder2 desktop = getDesktop();236personal = desktop.getChildByPath(path);237if (personal == null) {238personal = createShellFolder(getDesktop(), new File(path));239}240if (personal != null) {241personal.setIsPersonal();242}243}244} catch (final SecurityException ignored) {245// Ignore, the message may have sensitive information, not246// accessible other ways247} catch (InterruptedException | IOException e) {248if (log.isLoggable(PlatformLogger.Level.WARNING)) {249log.warning("Cannot access 'Personal'", e);250}251}252}253return personal;254}255256257private static File[] roots;258259/**260* @param key a {@code String}261* "fileChooserDefaultFolder":262* Returns a {@code File} - the default shellfolder for a new filechooser263* "roots":264* Returns a {@code File[]} - containing the root(s) of the displayable hierarchy265* "fileChooserComboBoxFolders":266* Returns a {@code File[]} - an array of shellfolders representing the list to267* show by default in the file chooser's combobox268* "fileChooserShortcutPanelFolders":269* Returns a {@code File[]} - an array of shellfolders representing well-known270* folders, such as Desktop, Documents, History, Network, Home, etc.271* This is used in the shortcut panel of the filechooser on Windows 2000272* and Windows Me.273* "fileChooserIcon <icon>":274* Returns an {@code Image} - icon can be ListView, DetailsView, UpFolder, NewFolder or275* ViewMenu (Windows only).276* "optionPaneIcon iconName":277* Returns an {@code Image} - icon from the system icon list278*279* @return An Object matching the key string.280*/281public Object get(String key) {282if (key.equals("fileChooserDefaultFolder")) {283File file = getPersonal();284if (file == null) {285file = getDesktop();286}287return checkFile(file);288} else if (key.equals("roots")) {289// Should be "History" and "Desktop" ?290if (roots == null) {291File desktop = getDesktop();292if (desktop != null) {293roots = new File[] { desktop };294} else {295roots = (File[])super.get(key);296}297}298return checkFiles(roots);299} else if (key.equals("fileChooserComboBoxFolders")) {300Win32ShellFolder2 desktop = getDesktop();301302if (desktop != null && checkFile(desktop) != null) {303ArrayList<File> folders = new ArrayList<File>();304Win32ShellFolder2 drives = getDrives();305306Win32ShellFolder2 recentFolder = getRecent();307if (recentFolder != null && OSInfo.getWindowsVersion().compareTo(OSInfo.WINDOWS_2000) >= 0) {308folders.add(recentFolder);309}310311folders.add(desktop);312// Add all second level folders313File[] secondLevelFolders = checkFiles(desktop.listFiles());314Arrays.sort(secondLevelFolders);315for (File secondLevelFolder : secondLevelFolders) {316Win32ShellFolder2 folder = (Win32ShellFolder2) secondLevelFolder;317if (!folder.isFileSystem() || (folder.isDirectory() && !folder.isLink())) {318folders.add(folder);319// Add third level for "My Computer"320if (folder.equals(drives)) {321File[] thirdLevelFolders = checkFiles(folder.listFiles());322if (thirdLevelFolders != null && thirdLevelFolders.length > 0) {323List<File> thirdLevelFoldersList = Arrays.asList(thirdLevelFolders);324325folder.sortChildren(thirdLevelFoldersList);326folders.addAll(thirdLevelFoldersList);327}328}329}330}331return checkFiles(folders);332} else {333return super.get(key);334}335} else if (key.equals("fileChooserShortcutPanelFolders")) {336Toolkit toolkit = Toolkit.getDefaultToolkit();337ArrayList<File> folders = new ArrayList<File>();338int i = 0;339Object value;340do {341value = toolkit.getDesktopProperty("win.comdlg.placesBarPlace" + i++);342try {343if (value instanceof Integer) {344// A CSIDL345folders.add(new Win32ShellFolder2((Integer)value));346} else if (value instanceof String) {347// A path348folders.add(createShellFolder(new File((String)value)));349}350} catch (IOException e) {351if (log.isLoggable(PlatformLogger.Level.WARNING)) {352log.warning("Cannot read value = " + value, e);353}354// Skip this value355} catch (InterruptedException e) {356if (log.isLoggable(PlatformLogger.Level.WARNING)) {357log.warning("Cannot read value = " + value, e);358}359// Return empty result360return new File[0];361}362} while (value != null);363364if (folders.size() == 0) {365// Use default list of places366for (File f : new File[] {367getRecent(), getDesktop(), getPersonal(), getDrives(), getNetwork()368}) {369if (f != null) {370folders.add(f);371}372}373}374return checkFiles(folders);375} else if (key.startsWith("fileChooserIcon ")) {376String name = key.substring(key.indexOf(" ") + 1);377378int iconIndex;379380if (name.equals("ListView") || name.equals("ViewMenu")) {381iconIndex = VIEW_LIST;382} else if (name.equals("DetailsView")) {383iconIndex = VIEW_DETAILS;384} else if (name.equals("UpFolder")) {385iconIndex = VIEW_PARENTFOLDER;386} else if (name.equals("NewFolder")) {387iconIndex = VIEW_NEWFOLDER;388} else {389return null;390}391392return getStandardViewButton(iconIndex);393} else if (key.startsWith("optionPaneIcon ")) {394Win32ShellFolder2.SystemIcon iconType;395if (key == "optionPaneIcon Error") {396iconType = Win32ShellFolder2.SystemIcon.IDI_ERROR;397} else if (key == "optionPaneIcon Information") {398iconType = Win32ShellFolder2.SystemIcon.IDI_INFORMATION;399} else if (key == "optionPaneIcon Question") {400iconType = Win32ShellFolder2.SystemIcon.IDI_QUESTION;401} else if (key == "optionPaneIcon Warning") {402iconType = Win32ShellFolder2.SystemIcon.IDI_EXCLAMATION;403} else {404return null;405}406return Win32ShellFolder2.getSystemIcon(iconType);407} else if (key.startsWith("shell32Icon ") || key.startsWith("shell32LargeIcon ")) {408String name = key.substring(key.indexOf(" ") + 1);409try {410int i = Integer.parseInt(name);411if (i >= 0) {412return Win32ShellFolder2.getShell32Icon(i,413key.startsWith("shell32LargeIcon ") ? LARGE_ICON_SIZE : SMALL_ICON_SIZE);414}415} catch (NumberFormatException ex) {416}417}418return null;419}420421private static File checkFile(File file) {422@SuppressWarnings("removal")423SecurityManager sm = System.getSecurityManager();424return (sm == null || file == null) ? file : checkFile(file, sm);425}426427private static File checkFile(File file, @SuppressWarnings("removal") SecurityManager sm) {428try {429sm.checkRead(file.getPath());430431if (file instanceof Win32ShellFolder2) {432Win32ShellFolder2 f = (Win32ShellFolder2)file;433if (f.isLink()) {434Win32ShellFolder2 link = (Win32ShellFolder2)f.getLinkLocation();435if (link != null)436sm.checkRead(link.getPath());437}438}439return file;440} catch (SecurityException se) {441return null;442}443}444445static File[] checkFiles(File[] files) {446@SuppressWarnings("removal")447SecurityManager sm = System.getSecurityManager();448if (sm == null || files == null || files.length == 0) {449return files;450}451return checkFiles(Arrays.stream(files), sm);452}453454private static File[] checkFiles(List<File> files) {455@SuppressWarnings("removal")456SecurityManager sm = System.getSecurityManager();457if (sm == null || files.isEmpty()) {458return files.toArray(new File[files.size()]);459}460return checkFiles(files.stream(), sm);461}462463private static File[] checkFiles(Stream<File> filesStream, @SuppressWarnings("removal") SecurityManager sm) {464return filesStream.filter((file) -> checkFile(file, sm) != null)465.toArray(File[]::new);466}467468/**469* Does {@code dir} represent a "computer" such as a node on the network, or470* "My Computer" on the desktop.471*/472public boolean isComputerNode(final File dir) {473if (dir != null && dir == getDrives()) {474return true;475} else {476@SuppressWarnings("removal")477String path = AccessController.doPrivileged(new PrivilegedAction<String>() {478public String run() {479return dir.getAbsolutePath();480}481});482483return (path.startsWith("\\\\") && path.indexOf("\\", 2) < 0); //Network path484}485}486487public boolean isFileSystemRoot(File dir) {488//Note: Removable drives don't "exist" but are listed in "My Computer"489if (dir != null) {490491if (dir instanceof Win32ShellFolder2) {492Win32ShellFolder2 sf = (Win32ShellFolder2)dir;493494//This includes all the drives under "My PC" or "My Computer.495// On windows 10, "External Drives" are listed under "Desktop"496// also497return (sf.isFileSystem() && sf.parent != null &&498(sf.parent.equals (getDrives()) ||499(sf.parent.equals (getDesktop()) && isDrive(dir))));500}501return isDrive(dir);502}503return false;504}505506private boolean isDrive(File dir) {507String path = dir.getPath();508if (path.length() != 3 || path.charAt(1) != ':') {509return false;510}511File[] roots = Win32ShellFolder2.listRoots();512return roots != null && Arrays.asList(roots).contains(dir);513}514515private static List<Win32ShellFolder2> topFolderList = null;516static int compareShellFolders(Win32ShellFolder2 sf1, Win32ShellFolder2 sf2) {517boolean special1 = sf1.isSpecial();518boolean special2 = sf2.isSpecial();519520if (special1 || special2) {521if (topFolderList == null) {522ArrayList<Win32ShellFolder2> tmpTopFolderList = new ArrayList<>();523tmpTopFolderList.add(Win32ShellFolderManager2.getPersonal());524tmpTopFolderList.add(Win32ShellFolderManager2.getDesktop());525tmpTopFolderList.add(Win32ShellFolderManager2.getDrives());526tmpTopFolderList.add(Win32ShellFolderManager2.getNetwork());527topFolderList = tmpTopFolderList;528}529int i1 = topFolderList.indexOf(sf1);530int i2 = topFolderList.indexOf(sf2);531if (i1 >= 0 && i2 >= 0) {532return (i1 - i2);533} else if (i1 >= 0) {534return -1;535} else if (i2 >= 0) {536return 1;537}538}539540// Non-file shellfolders sort before files541if (special1 && !special2) {542return -1;543} else if (special2 && !special1) {544return 1;545}546547return compareNames(sf1.getAbsolutePath(), sf2.getAbsolutePath());548}549550static int compareNames(String name1, String name2) {551// First ignore case when comparing552int diff = name1.compareToIgnoreCase(name2);553if (diff != 0) {554return diff;555} else {556// May differ in case (e.g. "mail" vs. "Mail")557// We need this test for consistent sorting558return name1.compareTo(name2);559}560}561562@Override563protected Invoker createInvoker() {564return new ComInvoker();565}566567private static class ComInvoker extends ThreadPoolExecutor implements ThreadFactory, ShellFolder.Invoker {568private static Thread comThread;569570@SuppressWarnings("removal")571private ComInvoker() {572super(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<>());573allowCoreThreadTimeOut(false);574setThreadFactory(this);575final Runnable shutdownHook = () -> AccessController.doPrivileged((PrivilegedAction<Void>) () -> {576shutdownNow();577return null;578});579AccessController.doPrivileged((PrivilegedAction<Void>) () -> {580Thread t = new Thread(581ThreadGroupUtils.getRootThreadGroup(), shutdownHook,582"ShellFolder", 0, false);583Runtime.getRuntime().addShutdownHook(t);584return null;585});586}587588@SuppressWarnings("removal")589public synchronized Thread newThread(final Runnable task) {590final Runnable comRun = new Runnable() {591public void run() {592try {593initializeCom();594task.run();595} finally {596uninitializeCom();597}598}599};600comThread = AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {601String name = "Swing-Shell";602/* The thread must be a member of a thread group603* which will not get GCed before VM exit.604* Make its parent the top-level thread group.605*/606Thread thread = new Thread(607ThreadGroupUtils.getRootThreadGroup(), comRun, name,6080, false);609thread.setDaemon(true);610/* This is important, since this thread running at lower priority611leads to memory consumption when listDrives() function is called612repeatedly.613*/614thread.setPriority(Thread.MAX_PRIORITY);615return thread;616});617return comThread;618}619620@SuppressWarnings("removal")621public <T> T invoke(Callable<T> task) throws Exception {622if (Thread.currentThread() == comThread) {623// if it's already called from the COM624// thread, we don't need to delegate the task625return task.call();626} else {627final Future<T> future;628629try {630future = submit(task);631} catch (RejectedExecutionException e) {632throw new InterruptedException(e.getMessage());633}634635try {636return future.get();637} catch (InterruptedException e) {638AccessController.doPrivileged(new PrivilegedAction<Void>() {639public Void run() {640future.cancel(true);641642return null;643}644});645646throw e;647} catch (ExecutionException e) {648Throwable cause = e.getCause();649650if (cause instanceof Exception) {651throw (Exception) cause;652}653654if (cause instanceof Error) {655throw (Error) cause;656}657658throw new RuntimeException("Unexpected error", cause);659}660}661}662}663664static native void initializeCom();665666static native void uninitializeCom();667}668669670