描述
AJFileChooser在 Java Swing 應用程式中使用。用戶可以輸入作業系統允許的任何檔案名,但有時他們會輸入錯誤的檔案名,例如包含無效字符的名稱。
如果用戶輸入以空格結尾的名稱,例如
SomeName
顯示錯誤訊息。這是通過覆寫JFileChooser#approveSelection,將檔案名與不需要的正則運算式匹配,然后顯示錯誤對話框來完成的。
但是,當用戶只輸入一個空格時,就會引發例外:
2022-10-18 12:34:54 SEVERE (CustomExceptionHandler::uncaughtException) Uncaught exception: java.nio.file.InvalidPathException: Trailing char < > at index 22: C:\X\Y\Z\ on [AWT-EventQueue-0]
Exception Stacktrace:
java.nio.file.InvalidPathException: Trailing char < > at index 22: C:\X\Y\Z\
at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:191)
at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
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.plaf.basic.BasicFileChooserUI.changeDirectory(BasicFileChooserUI.java:1353)
at java.desktop/javax.swing.plaf.basic.BasicFileChooserUI$ApproveSelectionAction.actionPerformed(BasicFileChooserUI.java:1142)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:297)
at java.desktop/java.awt.Component.processMouseEvent(Component.java:6635)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
at java.desktop/java.awt.Component.processEvent(Component.java:6400)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5011)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4843)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2772)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4843)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:117)
at java.desktop/java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
at java.desktop/java.awt.Dialog.show(Dialog.java:1070)
at java.desktop/javax.swing.JFileChooser.showDialog(JFileChooser.java:769)
at java.desktop/javax.swing.JFileChooser.showSaveDialog(JFileChooser.java:691)
at core.RetainerExportController.exportRetainer(RetainerExportController.java:156)
at core.RetainerExportController.lambda$1(RetainerExportController.java:64)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:297)
at java.desktop/java.awt.Component.processMouseEvent(Component.java:6635)
at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
at java.desktop/java.awt.Component.processEvent(Component.java:6400)
at java.desktop/java.awt.Container.processEvent(Container.java:2263)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5011)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4843)
at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2772)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4843)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
這個例外一直在我們的錯誤日志中彈出,因為雖然用戶被指示不要輸入空格作為檔案名,但他們仍然會不時地輸入。
調查
對此進行調查后,問題似乎來自BasicFileChooserUI. 類中有一個用于缺失或空字串的過濾器:
if (filename == null || filename.length() == 0) {
// no file selected, multiple selection off, therefore cancel the approve action
resetGlobFilter();
return;
}
但是,沒有針對空白檔案名的過濾器。所以這個名字沒有被 過濾BasicFileChooserUI,但是被 識別為以空格結尾WindowsPathParser,這會導致錯誤。
還有更多奇怪的行為。例如輸入一個完全由禁止字符組成的名稱,例如
???
結果什么也沒發生,即
- 不拋出例外
JFileChooser#approveSelection也從未達到
怎么解決?
目前尚不清楚如何解決它,因為所有錯誤行為都發生在內部 Swing 和sun.nio.fs代碼中。例如,我們的實作中有正則運算式過濾器JFileChooser,它觸發approveSelection并檢查由空格和無效字符組成的名稱 - 但是approveSelection在這種情況下從未呼叫過。如果WindowsPathParser#parse和WindowsPathParser#normalize被傳遞則呼叫它,否則錯誤處理是無用的,因為一些奇怪的內部錯誤處理是預先在內部完成的。
我已經研究過擴展 theWindowsFileChooserUI或BasicFileChooserUI軟體設計,因為這兩個類都使用對手頭問題至關重要的私有成員。例如,將用戶輸入作為WindowsFileChooserUI私有JTextField成員處理并設定它并以相同的方法使用它,因此在任何嘗試擴展的類中訪問它甚至修改它WindowsFileChooserUI都是一個問題。
長話短說 -InvalidPathException當用戶打開JFileChooser對話框并只輸入一個空格時,如何防止拋出?
筆記
因為內部 API 受到影響,所以使用的 JDK 可能是相關的:11.0.14_9
對話創建本身并不壯觀,它是這樣創建的:
JFileChooser dialog = new CustomFileChooser( file );
dialog.setDialogType( JFileChooser.SAVE_DIALOG );
dialog.setAcceptAllFileFilterUsed( false );
唯一被覆寫的方法CustomFileChooser是
createDialogapproveSelection
最小的可重現示例
因為請求了一個例子,錯誤可以重現如下:
- 設定一個搖擺應用程式(例如在 Eclipse 中)
- 像這樣創建一個
JFileChooser對話框:
JFileChooser dialog = new JFileChooser( file );
dialog.setDialogType( JFileChooser.SAVE_DIALOG );
dialog.setAcceptAllFileFilterUsed( false );
- 運行應用程式,確保對話框創建被觸發
- 在對話框中輸入一個空格作為檔案名:
- 確認
- 一個
InvalidPathException被拋出
請注意,此錯誤需要在 Windows 上運行 JVM,而我的測驗發生在 Java 11 上。
uj5u.com熱心網友回復:
雖然較舊的檔案選擇器版本從輸入的檔案名中去除了空格,但他們從不關心空格之外的名稱的有效性。例如,輸入類似\<\>\"的內容會被接受。無效的路徑名不會導致例外,因為java.io.File也不檢查語法。
在較新的版本中,FileSystem在某些地方使用了 NIO API,并且空間并不總是被剝離。發生特定例外是因為出于某種原因new File(" ").getAbsoluteFile().isDirectory()評估為(而沒有),因此檔案選擇器嘗試將目錄更改為無效路徑,而不是呼叫應用程式可以覆寫的路徑。truenew File(" ").isDirectory()approveSelection()
由于檔案選擇器的代碼無法處理無效檔案的例外,我做了這個解決方法,它使用了一個特殊的File子類,它報告不是一個目錄,可以在以下位置檢測到approveSelection():
public class FileChooserTest {
static final class Invalid extends File {
final String originalName;
public Invalid(String pathname) {
super(pathname);
originalName = pathname;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public String getName() {
return originalName;
}
}
public static void main(String... args) {
if(!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(FileChooserTest::main);
return;
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ReflectiveOperationException | UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
}
JFileChooser fc = new JFileChooser() {
@Override
public void approveSelection() {
if(getSelectedFile() instanceof Invalid) {
setSelectedFile(null);
return;
}
super.approveSelection();
}
};
fc.setFileSystemView(new FileSystemView() {
@Override
public File createFileObject(String path) {
try {
Paths.get(path);
} catch(InvalidPathException ex) {
return new Invalid(path);
}
return super.createFileObject(path);
}
@Override
public File createFileObject(File dir, String filename) {
try {
Paths.get(filename);
} catch(InvalidPathException ex) {
return new Invalid(filename);
}
return super.createFileObject(dir, filename);
}
@Override
public File createNewFolder(File containingDir) throws IOException {
return FileSystemView.getFileSystemView().createNewFolder(containingDir);
}
});
if(fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
System.out.println(fc.getSelectedFile());
}
else System.out.println("Not approved");
}
}
這不僅可以消除例外,還可以防止無效檔案獲得批準。當然,它可以被改進,例如通過向用戶提供反饋。但是,如果像這樣的錯誤無論如何都能得到修復,那就更好JDK-8196673了。
*注意:名稱包含或不被拒絕的原因?是它們被轉換為檔案名過濾器。因此,當您輸入時,例如*.txt,它應該顯示在過濾器組合框中。
uj5u.com熱心網友回復:
如果您不想使用檔案選擇器或檔案系統的內部結構,您可以在呼叫檔案選擇器處理之前簡單地從檔案名中洗掉前導/尾隨空格:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main
{
public static void main(String[] args) throws Exception
{
JFileChooser fc = new JFileChooser( );
JTextField tf = SwingUtils.getDescendantsOfType(JTextField.class, fc).get(0);
tf.addFocusListener( new FocusAdapter()
{
@Override
public void focusLost(FocusEvent e)
{
JTextField tf = (JTextField)e.getSource();
tf.setText( tf.getText().trim() );
}
});
fc.showOpenDialog(null);
}
}
上面的代碼需要Swing Utils類。
uj5u.com熱心網友回復:
JFileChooser如果我要保存的檔案與現有檔案同名,我擴展為添加一個確認對話框。也許您可以使用它來啟動更強大的JFileChooser.
import java.io.File;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
public class OSFileChooser extends JFileChooser {
private static final long serialVersionUID = 1L;
@Override
public void approveSelection() {
File f = getSelectedFile();
if (f.exists() && getDialogType() == SAVE_DIALOG) {
int result = JOptionPane.showConfirmDialog(this, f.getName()
" exists, overwrite?",
"Existing file", JOptionPane.YES_NO_CANCEL_OPTION);
switch (result) {
case JOptionPane.YES_OPTION:
super.approveSelection();
return;
case JOptionPane.NO_OPTION:
return;
case JOptionPane.CLOSED_OPTION:
return;
case JOptionPane.CANCEL_OPTION:
cancelSelection();
return;
}
}
super.approveSelection();
}
@Override
public File getSelectedFile() {
File file = super.getSelectedFile();
if (file != null && getDialogType() == SAVE_DIALOG) {
String extension = getExtension(file);
if (extension.isEmpty()) {
FileTypeFilter filter = (FileTypeFilter) getFileFilter();
if (filter != null) {
extension = filter.getExtension();
String fileName = file.getPath();
fileName = "." extension;
file = new File(fileName);
}
}
}
return file;
}
public String getExtension(File file) {
String extension = "";
String s = file.getName();
int i = s.lastIndexOf('.');
if (i > 0 && i < (s.length() - 1)) {
extension = s.substring(i 1).toLowerCase();
}
return extension;
}
}
以及相關的FileTypeFilter類。
import java.io.File;
import javax.swing.filechooser.FileFilter;
public class FileTypeFilter extends FileFilter {
private String extension;
private String description;
public FileTypeFilter(String description, String extension) {
this.extension = extension;
this.description = description;
}
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
}
return file.getName().endsWith("." extension);
}
@Override
public String getDescription() {
return description String.format(" (*.%s)", extension);
}
public String getExtension() {
return extension;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/518374.html
標籤:爪哇摇摆java-11
