英文:
Replacement or way of using ShellFolder.getShellFolder() on Java 11
问题
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:
- Is there way a way to set src to 11 and allow compile by utilising some module option
- 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.
<details>
<summary>英文:</summary>
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.
</details>
# 答案1
**得分**: 1
```java
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 a 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 a higher drive letter
return false;
}
}
英文:
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;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论