Search code examples
javawindowsjava-securityjava-17

Testing for local or remote path (without sun.awt.shell.ShellFolder class)


I am trying to move from Java 15 to Java 17 but because they now enscapulate the internals and have removed the flag –illegal-access I have a problem.

From https://www.baeldung.com/java-17-new-features

JEP 403 represents one more step toward strongly encapsulating JDK internals since it removes the flag –illegal-access. The platform will ignore the flag, and if the flag is present, the console will issue a message informing the discontinuation of the flag.

I have a class that fails because it references

sun.awt.shell.ShellFolder;
sun.awt.shell.ShellFolderColumnInfo;

The purpose of the class is to identify on a Windows system whether a file is on local fileystem

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 this a local drive, only works for Windows machine 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))
                {
                    //Mapped network drive
                    if(shellFolder.getFolderColumnValue(i)!=null && ((String)shellFolder.getFolderColumnValue(i)).startsWith("\\"))
                    {
                        return false;
                    }
                    //SONGKONG-2186:Can be null if unmapped network drive
                    else if(shellFolder.getFolderColumnValue(i)==null)
                    {
                        return false;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
            return false;
        }
        return true;
    }

}

Can this be achieved another way, or is there a way I can still access these sun classes ?

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 eon 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.

enter image description here


Solution

  • Note that ShellFolder is the backend of the FileSystemView API which provides most of the functionality to present file information in a UI. Testing whether a file is on a network drive isn’t provided though, but your original code looks more like a heuristic anyway.

    The simplest test I’ve found so far, based on NIO only, is for the Volume Serial Number, which is only available for local drives:

    public class WindowsFilesystemType {
        public static boolean isNTFSOrFAT32(String newPath) {
            try {
                return switch(Files.getFileStore(Paths.get(newPath)).type()) {
                    case "NTFS", "FAT", "FAT32", "EXFAT" -> true;
                    default -> false;
                };
            } catch (IOException ex) {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
                return false;
            }
        }
    
        public static boolean isRemote(String newPath) {
            try {
                FileStore fileStore = Files.getFileStore(Paths.get(newPath));
                return Integer.valueOf(0).equals(fileStore.getAttribute("volume:vsn"));
            } catch(Exception ex) {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
                return false;
            }
        }
    
        public static boolean isLocal(String newPath) {
            try {
                FileStore fileStore = Files.getFileStore(Paths.get(newPath));
                return !Integer.valueOf(0).equals(fileStore.getAttribute("volume:vsn"));
            } catch(Exception ex) {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
                return false;
            }
        }
    }
    

    This Windows file store attribute has not been documented, but it works since Java 7.