如何为Java File对象(mkdir、rename、delete)的失败调用获取有意义的消息



在使用File.mkdir和朋友时,我注意到他们不会在失败时抛出异常!谢天谢地,FindBugs指出了这一点,现在我的代码至少检查了返回值,但我仍然无法获得关于为什么调用失败的有意义的信息!

如何找出对这些File方法的调用失败的原因?有没有一个好的替代方案或库来处理这个问题?

我在SO和谷歌上做了一些搜索,发现了关于这个话题的令人惊讶的小信息。

[更新]我已经尝试过VFS,它的异常没有任何有用的信息。例如,试图移动最近删除的目录导致Could not rename file "D:pathtofileA" to "file:///D:/path/do/fileB".没有提及文件a已不存在

[update]业务需求限制我只能使用JDK 1.6解决方案,因此JDK 1.7已过时

您可以调用本机方法,并通过这种方式获得正确的错误代码。例如,c函数mkdir具有错误代码,如EEXIST和ENOSPC。您可以很容易地使用JNA来访问这些本机函数。如果您支持*nix和windows,则需要创建此代码的两个版本。

举一个linux上jna-mkdir的例子,你可以这样做,

import java.io.IOException;
import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
public class FileUtils {
  private static final int EACCES = 13;
  private static final int EEXIST = 17;
  private static final int EMLINK = 31;
  private static final int EROFS = 30;
  private static final int ENOSPC = 28;
  private static final int ENAMETOOLONG = 63;
  static void mkdir(String path) throws IOException {
    try {
      NativeLinkFileUtils.mkdir(path);
    } catch (LastErrorException e) {
      int errno = e.getErrorCode();
      if (errno == EACCES)
        throw new IOException(
            "Write permission is denied for the parent directory in which the new directory is to be added.");
      if (errno == EEXIST)
        throw new IOException("A file named " + path + " already exists.");
      if (errno == EMLINK)
        throw new IOException(
            "The parent directory has too many links (entries).  Well-designed file systems never report this error, because they permit more links than your disk could possibly hold. However, you must still take account of the possibility of this error, as it could result from network access to a file system on another machine.");
      if (errno == ENOSPC)
        throw new IOException(
            "The file system doesn't have enough room to create the new directory.");
      if (errno == EROFS)
        throw new IOException(
            "The parent directory of the directory being created is on a read-only file system and cannot be modified.");
      if (errno == EACCES)
        throw new IOException(
            "The process does not have search permission for a directory component of the file name.");
      if (errno == ENAMETOOLONG)
        throw new IOException(
            "This error is used when either the total length of a file name is greater than PATH_MAX, or when an individual file name component has a length greater than NAME_MAX. See section 31.6 Limits on File System Capacity.");
      else
        throw new IOException("unknown error:" + errno);
    }


  }
}
class NativeLinkFileUtils {
  static {
    try {
      Native.register("c");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  static native int mkdir(String dir) throws LastErrorException;
}

使用JDK7的新文件API。它具有更好的操作系统集成,并提供更详细的反馈。例如,请参阅有关移动/重命名的文档。

您可以创建一个包含以下内容的实用程序类:

public int mkdir(File dirToCreate) throws IOException
{
    if (dirToCreate.exists())
        throw new IOException("Folder already exists");
    if (!dirToCreate.getParent().canWrite())
        throw new IOException("No write access to create the folder");
    return dirToCreate.mkdir();
}

public int rename(File from, File to) throws IOException, FileNotFoundException
{
    if (from.equals(to))
        throw new IllegalArgumentException("Files are equal");
    if (!from.exists())
        throw new FileNotFoundException(from.getAbsolutePath() + " is not found");
    if (!to.getParent().exists())
        throw new IllegalAccessException("Parent of the destination doesn't exist");
    if (!to.getParent().canWrite())
        throw new IllegalAccessException("No write access to move the file/folder");
    return from.renameTo(to);
}

当然,这还不完全,但你可以想出这个主意。

您可以执行命令并捕获err输出,其中包含一条有意义的消息。

以下是一些最低限度的可执行代码(使用apachecommons-exec(,它演示了这是如何工作的:

import org.apache.commons.exec.*;
public static String getErrorMessage(String command) {
    CommandLine cmdLine = CommandLine.parse(command);
    DefaultExecutor executor = new DefaultExecutor();
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteArrayOutputStream err = new ByteArrayOutputStream();
    executor.setStreamHandler(new PumpStreamHandler(out, err));
    try {
        executor.execute(cmdLine);
    } catch (Exception e) {
        return err.toString().trim();
    }
    return null; // no error occurred
}

以下是对该代码的测试,显示了各种文件操作错误情况:

public static void main(String[] args) throws Exception {
    System.out.println(getErrorMessage("cp fake.file x"));
    System.out.println(getErrorMessage("cp /tmp /tmp"));
    System.out.println(getErrorMessage("mkdir /Volumes"));
    System.out.println(getErrorMessage("mv /tmp /"));
    System.out.println(getErrorMessage("mv fake.file /tmp"));
}

输出(在mac-osx上运行(:

cp: fake.file: No such file or directory
cp: /tmp is a directory (not copied).
mkdir: /Volumes: File exists
mv: /tmp and /tmp are identical
mv: rename fake.file to /tmp/fake.file: No such file or directory

您可以将上述方法封装在抛出IOException的方法中,IOException在获得消息后,可以解析关键参数,并使用正则表达式匹配或contains将消息映射到特定的IOException并抛出它们,例如:

if (message.endsWith("No such file or directory"))
    throw new FileNotFoundException();  // Use IOExceptions if you can
if (message.endsWith("are identical"))
    throw new IdenticalFileException(); // Create your own Exceptions that extend IOException

如果您想将其抽象化以用于多种操作系统,则必须为每个平台实现代码(windows和*nix对任何给定的文件操作/结果使用不同的shell命令/错误消息(。

如果这个答案得到奖励,我会发布一个完整整洁的工作代码版本,包括一个用于文件操作的时尚enum

您可以使用jakarta VFS。FileObject.createFolder()抛出保存错误代码的FileSystemException。这不是实现@Martijn Courteaux 提供的逻辑

最新更新