Java 11 上 ShellFolder.getShellFolder() 的替换或使用方式

Replacement or way of using ShellFolder.getShellFolder() on Java 11

我目前正在使用 ShellFolder.getShellFolder() 来确定特定路径是否在本地驱动器上(直接连接到 Windows 机器)或远程驱动器。

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;
    }

}

这在 Java 8

上工作正常

我现在转到 Java 11,我正在使用 maven 编译项目,如果我从 增加编译器的 source 参数89(或以上)

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

由于模块系统的引入导致编译失败(保持8,目标11可以)

[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

所以我正在寻找替代方法,或者:

  1. 有没有办法将 src 设置为 11 并允许使用某些模块选项进行编译
  2. 最好我可以使用标准 java 库
  3. 可靠地检测本地或远程驱动器

我需要 isLocal() 的原因是我的程序重命名文件,用户可以选择将路径长度限制为 259 个字符,因为更长的长度会给 Windows Explorer 带来问题,但如果它们是修改远程驱动器通常不会强制执行此要求,我将向问题添加更多详细信息。

例如,如果音乐文件在本地驱动器上供 Windows 使用,则应用程序会重命名音乐文件,然后他们可能希望强制执行该限制。但如果它是一个网络驱动器,他们可能不会,因为文件经常存储在 Nas 上,他们只能通过 Windows 访问文件,因为我的应用程序可以 运行 on Windows 但是不在 Nas 上。

目前似乎没有替代我在标准 Java API 中所需的功能(在 Windows 上识别路径是远程路径还是本地路径).

因此,选择是继续使用将来可能不会出现的非 public class 还是编写 hacky 代码与操作系统命令交互 (net)将来可能会改变。

所以务实的解决办法是我继续使用非publicclass,希望能给publicapi加点东西,如果它不是并且非 public class 被删除然后我将不得不编写代码来与网络对话。

为了允许在编译时使用 Maven 访问这个非public class,我将以下内容添加到编译器插件

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

例如

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

不幸的是,这个解决方案失败了 java 17 开始我看着太阳 shell classes 看看我是否可以复制它们,sun.awt.shell 没问题,但是 windows 实现依赖于非 java 库

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

所以这是个问题。

在下面创建了一些东西,但仅靠猜测是不可靠的

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;
    }
}