Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display network shares using JFileChooser in Java 9+?

The users of our software need to browse network shares on Windows 10 in our Java swing application, however swing's JFileChooser does not have this capability by default.

In this related post How to navigate to a network host in JFileChooser? A decent solution is presented using a ShellFolder (sun private API) to set the JFileChooser's current directory, and we have been using this approach with a few modifications for that last couple years with no issues.

public static File getNetworkShareFolder( final File folder ) throws IllegalArgumentException {
  final File file = new NonCanonicalizingFile( folder.getPath() );
  if( isNetworkShareFolder( file ) ) { // assume Win32ShellFolderManager2 will be present
     try {
        // JRE-13272 -PRIVATE API that must eventually be swapped for Java 9 alternative
        // Using reflection because Win32ShellFolderManager2 may not exist in rt.jar on Linux build server
        final Class win32ShellMgr = Class.forName( "sun.awt.shell.Win32ShellFolderManager2" );
        // get static creation method from class, execute it
        final Method cfMethod = win32ShellMgr.getMethod( "createShellFolder", File.class );
        return (ShellFolder) cfMethod.invoke( win32ShellMgr.newInstance(), file );
     } catch( final Exception xx ) {
        xx.printStackTrace();
     }
  }
  throw new IllegalArgumentException( "Given path is not a Windows network share folder." ); 
} 

However, we are moving to Java 11, and in Java 9 on, private APIs are encapsulated and we have been mandated to no longer use them. No worries, the replacement APIs in OpenJDK have gone into FileSystemView, in a subpackage of swing filechooser.

sun.awt.shell.ShellFolder.isComputerNode( File ) -> javax.swing.filechooser.FileSystemView.getFileSystemView().isComputerNode( File ) sun.awt.shell.ShellFolder.getShellFolder( File ) -> javax.swing.filechooser.FileSystemView.getFileSystemView().getLinkLocation( File )

so the previous code now becomes

public static File getNetworkShareFolder( final File folder ) throws IllegalArgumentException {
   final File file = new NonCanonicalizingFile( folder.getPath() );
   if( isNetworkShareFolder( file ) ) { 
      try {
         return FileSystemView.getFileSystemView().getLinkLocation( file );
      } catch( final Exception xx ) {
         xx.printStackTrace();
      }
   }
   throw new IllegalArgumentException( "Given path is not a Windows network share folder." );
}

public static boolean isNetworkShareFolder( final File folder ) {
   return FileSystemView.getFileSystemView().isComputerNode( new NonCanonicalizingFile( folder.getPath() ) );
}

This would be great, but unfortunately BOTH getShellFolder() and getLinkLocation() throw an Exception under Java 11 that was not thrown under Java 8.

java.nio.file.InvalidPathException: UNC path is missing sharename: \100.212.51.37 at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:118) at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92) at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:229) at java.base/java.nio.file.Path.of(Path.java:147) at java.base/java.nio.file.Paths.get(Paths.java:69) at java.desktop/sun.awt.shell.ShellFolder.getShellFolder(ShellFolder.java:247) at java.desktop/javax.swing.filechooser.FileSystemView.getLinkLocation(FileSystemView.java:641)

It appears that it now considers UNC paths invalid unless it has the actual share name, ie "\\100.212.51.37\" is invalid, but "\\100.212.51.37\myShare" is ok.

Now if you get the shell folder for the UNC path "\\100.212.51.37\myShare" and then getParent(), you get the shell folder for "\\100.212.51.37\" that we wanted in the first place. Unfortunately, this workaround isn't viable for our customers because of a chicken & egg problem - the users often don't yet know any of the actual share names, this is what they wanted to browse in the first place!

Argh - this worked well under Java 8, but in Java 11, even if you break encapsulation to use the original ShellFolder private API using

'--add-exports', 'java.desktop/sun.awt.shell=ALL-UNNAMED'

It doesn't help because the previous solution now throws the same Exception under Java 11 (9+).

Another solution we have seen on StackOverflow is to use the SmbFile class in JCIFS, but due to security restrictions it is very difficult for us to use 3rd party code. Especially if it is not updated for Java 11 JPMS with NO use of private APIs.

Interestingly, the DirectoryChooser in JavaFX does NOT have this problem. If the user manually types in the network host, it will gladly display all of the share names for that host. We will go this way if we have to, but dealing with modality between a FX Stage over a swing app is ugly and potentially an awful lot of work.

Still hopeful for a simpler workaround to get JFileChooser to display network shares in Java 11 (Java 9+)! Maybe someone knows the trick the FX DirectoryChooser is using and it can be applied to JFileChooser?

like image 679
ToddWebb Avatar asked Nov 06 '18 22:11

ToddWebb


1 Answers

Still looking for a better solution, but in the mean time, we decided to replace JFileChooser with JavaFX's DirectoryChooser in Java 11 because of network shares. DirectoryChooser is more than happy to allow the user to type in a root network share and it will display all the network share names. The UI that is launching the DirectoryChooser is still a swing UI, but we aren't experiencing modality or focus problems, at least under Windows 10.

However, there are significant drawbacks for us beyond having to carefully manage threads between JavaFX Platform.runLater() and SwingUtilities.invokeLater()

1) DirectoryChooser does not allow multiple selection. JFileChooser does.

2) DirectoryChooser ONLY allows selection of directories and not files. Shrugs Who would need to allow selection of EITHER a directory or a file? Silly. Only that is exactly what our customers need to do in more than one instance. JFileChooser supports this.

3) DirectoryChooser does not allow entry of a non valid path. Huh? Yes, in fact, one of our customers has a specific requirement to enter a path that does not yet exist (pre-configure), but will as soon as the mission drive is plugged in to the network. JFileChooser allows this and can be very handy to navigate to the root directory you need, then simply type in the last portion of the path (directory or share name).

4) DirectoryChooser does not match the application style. In JavaFX, you can set your own CSS on the root node of your Scene, but not DirectoryChooser. It seems DirectoryChooser is actually invoking the native file chooser, which should be using the system color scheme. Unfortunately for our pilots operating at night with "dark mode", Windows 10 File Explorer does NOT respect "dark mode" settings until version 1809, which almost none of our customers have.

like image 167
ToddWebb Avatar answered Sep 21 '22 09:09

ToddWebb