0

I'm currently using ShellFolder.getShellFolder() to determine if a particular path is on a local drive (directly connected to Windows machine) or a remote drive.

package com.jthink.songkong.analyse.analyser;

import com.jthink.songkong.analyse.filename.WindowsFileSystem;
import com.jthink.songkong.ui.MainWindow;
import sun.awt.shell.ShellFolder;
import sun.awt.shell.ShellFolderColumnInfo;

import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;

/**
 * Only Windows can load these methods because of reliance on sun classes
 *
 */
public class WindowsFilesystemType
{

    public static final String WINDOWS_SHELL_ATTRIBUTES = "Attributes";
    public static final String WINDOWS_SHELL_ITEM_TYPE = "Item type";
    public static final String WINDOWS_SHELL_SIZE = "Size";
    /**
     * Is Windows NTFS or FAT32
     *
     * @param newPath
     * @return
     */
    public static boolean isNTFSOrFAT32(String newPath)
    {
        Path root = Paths.get(newPath).getRoot();
        if (root == null)
        {
            return false;
        }
        try
        {
            FileStore fs = Files.getFileStore(root);
            if (fs.type().equals(WindowsFileSystem.NTFS)
                    || fs.type().equals(WindowsFileSystem.FAT)
                    || fs.type().equals(WindowsFileSystem.FAT32)
                    || fs.type().equals(WindowsFileSystem.EX_FAT))
            {
                return true;
            }
            return false;
        }
        catch (IOException ex)
        {
            MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
            return false;
        }
    }

    /**
     * Is this a remote drive, only works for Windows because relies on underlying Windows code
     *
     * @param newPath
     *
     * @return true if this a remote (Network) drive
     */
    public static boolean isRemote(String newPath)
    {
        try
        {
            Path root = Paths.get(newPath).getRoot();
            ShellFolder shellFolder = ShellFolder.getShellFolder(root.toFile());
            ShellFolderColumnInfo[] cols = shellFolder.getFolderColumns();
            for (int i = 0; i < cols.length; i++)
            {
                if (cols[i].getTitle().equals(WINDOWS_SHELL_SIZE)
                        &&  ((String) shellFolder.getFolderColumnValue(i)).startsWith(WindowsShellFileSystemType.NETWORK_DRIVE))
                {
                    return true;
                }
                else if (cols[i].getTitle().equals(WINDOWS_SHELL_ATTRIBUTES)
                        &&  ((String) shellFolder.getFolderColumnValue(i)).startsWith("\\"))
                {
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            return false;
        }
        return false;
    }

    /**
     * Is this a local drive, only works for Windows because relies on underlying Windows code
     *
     * @param newPath
     *
     * @return true if this a local drive
     */
    public static boolean isLocal(String newPath)
    {
        try
        {
            Path root = Paths.get(newPath).getRoot();
            ShellFolder shellFolder = ShellFolder.getShellFolder(root.toFile());
            ShellFolderColumnInfo[] cols = shellFolder.getFolderColumns();
            for (int i = 0; i < cols.length; i++)
            {
                if (cols[i].getTitle().equals(WINDOWS_SHELL_SIZE)
                        &&  ((String) shellFolder.getFolderColumnValue(i)).startsWith(WindowsShellFileSystemType.LOCAL_DISK))
                {
                    return true;
                }
                else if (cols[i].getTitle().equals(WINDOWS_SHELL_ATTRIBUTES)
                        &&  ((String) shellFolder.getFolderColumnValue(i)).startsWith("\\"))
                {
                    return false;
                }
            }
        }
        catch (Exception ex)
        {
            return true;
        }
        return true;
    }

}

This works fine on Java 8

I am now moving to Java 11, I am using maven to compile the project and if I increase the source parameter for compiler from 8 to 9 (or above)

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <compilerVersion>11</compilerVersion>
                    <source>8</source>
                    <target>11</target>
                    <verbose>true</verbose>
                    <fork>true</fork>
                </configuration>
            </plugin>

I get compile failure (keeping at 8 and setting target to 11 is okay) because of the introduction of the module system

[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] c:\Code\jthink\SongKong\src\main\java\com\jthink\songkong\analyse\analyser\WindowsFilesystemType.java:[5,14] error: package sun.awt.shell is not visible
  (package sun.awt.shell is declared in module java.desktop, which does not export it)
[ERROR] c:\Code\jthink\SongKong\src\main\java\com\jthink\songkong\analyse\analyser\WindowsFilesystemType.java:[6,14] error: package sun.awt.shell is not visible
  (package sun.awt.shell is declared in module java.desktop, which does not export it)
[INFO] 2 errors

So I am looking for an alternative way to this, either:

  1. Is there way a way to set src to 11 and allow compile by utilising some module option
  2. Preferably can I reliably detect if local or remote drive using standard java libs

The reason I need is isLocal() is my program renames files and there is an option for the user to restrict path length to 259 characters because longer lengths cause problems for Windows Explorer, but if they are modifying a remote drive then not usual to enforce this requirement, I will add more detail to question.

For example the application renames music files if they are on local drive to be used by Windows then they may want to enforce that limit. But if it is a networked drive they probably will not because quite often the files are stored on a Nas and they are only accessing the files via Windows because my application can run on Windows but not on the Nas.

Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
  • I’m not a Windows expert. Would it be accurate to say that any absolute path which either starts with two backslashes or starts with a drive letter mapped to a remote share meets your definition of a remote path? – VGR Apr 10 '20 at 14:07
  • On the other side of the things, you can evaluate [adding a `--add-exports` args](https://stackoverflow.com/questions/53801778/java-is-sun-awt-image-package-deprecated) to avoid the compilation failure. – Naman Apr 10 '20 at 14:14
  • @VGR but how do you know if drive letter is mapped to a remote share ? – Paul Taylor Apr 11 '20 at 08:42
  • 1
    Personally, I would opt to parse the output of `net use` rather than relying on a third party library. (You are seeing the risk now: a lot of third party libraries blithely rely on inappropriate hacks like using undocumented, unsupported `sun.*` packages which Sun/Oracle have been saying for many years may be unavailable in future Java releases. The compiler even emitted warnings about it in Java 8.) – VGR Apr 11 '20 at 12:47
  • @VGR of course I would prefer not to use these classes but why would relying on net use be any more reliable ? – Paul Taylor Apr 12 '20 at 08:06
  • 1
    I suppose there is no guarantee that the `net use` syntax or the format of its output is stable, but it has been around for a pretty long time. So it’s been stable for quite a while, if nothing else. – VGR Apr 12 '20 at 15:02
  • 1
    It’s hard to understand which information you are extracting as you are referring to constants you didn’t include in your code (and the titles seems to be subject to localization anyway), but apparently, one of the items can be extracted much easier and more reliable using the official API, i.e. `FileSystemView.getFileSystemView().getSystemTypeDescription(path.getRoot().toFile())`. (Referring to `javax.swing.filechooser.FileSystemView`, the actual front-end of the `ShellFolder` stuff) – Holger Apr 15 '20 at 16:23

1 Answers1

1

There doesn't seem to be a replacement for the functionality I require in the standard Java API at the moment (identifying on Windows if a path is remote or local).

So the choice is between continuing to use a non public class that may not be around in the future or writing hacky code to interact with operating system command (net) that may change in the future.

So the pragmatic solution is for me to continue to use the non public class, and hope that something will be added to the public api, if it is not and the non public class is removed then I will have to write code to talk to net at that point.

To allow this nonpublic class to be accessed at compile time using Maven I added the following to the compiler plugin

 <compilerArgs>
     <arg>--add-exports=java.desktop/sun.awt.shell=ALL-UNNAMED</arg>
 </compilerArgs>

e.g

   <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
            <encoding>UTF-8</encoding>
            <compilerVersion>11</compilerVersion>
            <source>11</source>
            <target>11</target>
            <verbose>true</verbose>
            <fork>true</fork>
            <compilerArgs>
                <arg>--add-exports=java.desktop/sun.awt.shell=ALL-UNNAMED</arg>
            </compilerArgs>
        </configuration>
    </plugin>

Unfortunately this solution fails with java 17 onwards I looked at the sun shell classes to see if I could just copy them, the sun.awt.shell are okay but the windows implemenation relies on a non java library

static {
        // Load library here
        sun.awt.windows.WToolkit.loadLibraries();
    }

so that is a problem.

Created something below, but just relies on guesswork is unreliable

public class WindowsFilesystemType
{

    public static final String WINDOWS_SHELL_ATTRIBUTES = "Attributes";
    public static final String   WINDOWS_SHELL_SIZE = "Size";

    /**
     * Is Windows NTFS or FAT32
     *
     * @param newPath
     * @return
     */
    public static boolean isNTFSOrFAT32(String newPath)
    {
        Path root = Paths.get(newPath).getRoot();
        if (root == null)
        {
            return false;
        }
        try
        {
            FileStore fs = Files.getFileStore(root);
            if (fs.type().equals(WindowsFileSystem.NTFS)
                    || fs.type().equals(WindowsFileSystem.FAT)
                    || fs.type().equals(WindowsFileSystem.FAT32)
                    || fs.type().equals(WindowsFileSystem.EX_FAT))
            {
                return true;
            }
            return false;
        }
        catch (IOException ex)
        {
            MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
            return false;
        }
    }



    /**
     * Is this a local drive, only works for Windows machine
     *
     * TODO unreliable since moved to Java 17
     *
     * @param newPath
     *
     * @return true if this a local drive
     */
    public static boolean isLocal(String newPath)
    {
        //If not a windows fs unlikely to be local drive
        if(!isNTFSOrFAT32(newPath))
        {
            return false;
        }

        //Mapped \\ must be network drive ?
        Path root = Paths.get(newPath).getRoot();
        if (root.toString().startsWith("\\"))
        {
            return false;
        }

        //Low drive letter assume local
        root = Paths.get(newPath).getRoot();
        if (
                (root.toString().equals("C:\\"))||
                (root.toString().equals("D:\\"))||
                (root.toString().equals("E:\\"))||
                (root.toString().equals("F:\\"))
            )
        {
            return true;
        }

        //Assume network then if higher drive letter
        return false;
    }
}
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351