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