From 7d866320671ef9a68bea829fd5eab9dc462d0ed0 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Fri, 13 Feb 2015 17:58:28 +0100 Subject: [PATCH] use NIO when available for recursive file deletion --- .../intellij/openapi/util/io/FileUtilRt.java | 115 +++++++++++++++++- .../intellij/openapi/util/io/FileUtil.java | 9 +- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java index e24561626b8d..28ae363de396 100644 --- a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java +++ b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java @@ -24,6 +24,10 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.io.*; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.ArrayList; @@ -66,6 +70,81 @@ public class FileUtilRt { private static String ourCanonicalTempPathCache = null; + protected static final boolean NIO_FILE_API_AVAILABLE; + + // todo: replace reflection with normal code after migration to JDK 1.8 + private static Method ourFilesDeleteIfExistsMethod; + private static Method ourFilesWalkMethod; + private static Method ourFileToPathMethod; + private static Object ourDeletionVisitor; + static { + boolean initSuccess = false; + try { + final Class pathClass = Class.forName("java.nio.file.Path"); + final Class visitorClass = Class.forName("java.nio.file.FileVisitor"); + final Class filesClass = Class.forName("java.nio.file.Files"); + + ourFileToPathMethod = Class.forName("java.io.File").getMethod("toPath"); + ourFilesWalkMethod = filesClass.getMethod("walkFileTree", pathClass, visitorClass); + ourFilesDeleteIfExistsMethod = filesClass.getMethod("deleteIfExists", pathClass); + final Class fileVisitResultClass = Class.forName("java.nio.file.FileVisitResult"); + final Object Result_Continue = fileVisitResultClass.getDeclaredField("CONTINUE").get(null); + final Object Result_Terminate = fileVisitResultClass.getDeclaredField("TERMINATE").get(null); + ourDeletionVisitor = Proxy.newProxyInstance(FileUtilRt.class.getClassLoader(), new Class[]{visitorClass}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (args.length == 2) { + final Object second = args[1]; + if (second instanceof Throwable) { + throw (Throwable)second; + } + final String methodName = method.getName(); + if ("visitFile".equals(methodName) || "postVisitDirectory".equals(methodName)) { + if (!performDelete(args[0])) { + return Result_Terminate; + } + } + } + return Result_Continue; + } + + private boolean performDelete(@NotNull final Object fileObject) { + Boolean result = doIOOperation(new RepeatableIOOperation() { + public Boolean execute(boolean lastAttempt) { + try { + //Files.deleteIfExists(file); + ourFilesDeleteIfExistsMethod.invoke(null, fileObject); + return Boolean.TRUE; + } + catch (InvocationTargetException e) { + if (!(e.getCause() instanceof IOException)) { + return Boolean.FALSE; + } + } + catch (IllegalAccessException e) { + return Boolean.FALSE; + } + return lastAttempt? Boolean.FALSE : null; + } + }); + return Boolean.TRUE.equals(result); + } + + }); + initSuccess = true; + LOG.info("Using NIO-based file deletion"); + } + catch (Throwable ignored) { + LOG.info("Was not able to detect NIO API"); + ourFileToPathMethod = null; + ourFilesWalkMethod = null; + ourFilesDeleteIfExistsMethod = null; + ourDeletionVisitor = null; + } + NIO_FILE_API_AVAILABLE = initSuccess; + } + + @NotNull public static String getExtension(@NotNull String fileName) { int index = fileName.lastIndexOf('.'); @@ -538,10 +617,44 @@ public class FileUtilRt { * @return true if the file did not exist or was successfully deleted */ public static boolean delete(@NotNull File file) { + if (NIO_FILE_API_AVAILABLE) { + return deleteRecursivelyNIO(file); + } + return deleteRecursively(file); + } + + protected static boolean deleteRecursivelyNIO(File file) { + try { + /* + Files.walkFileTree(file.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.deleteIfExists(dir); + return FileVisitResult.CONTINUE; + } + }); + */ + final Object pathObject = ourFileToPathMethod.invoke(file); + ourFilesWalkMethod.invoke(null, pathObject, ourDeletionVisitor); + } + catch (Exception e) { + LOG.info(e); + return false; + } + return true; + } + + private static boolean deleteRecursively(@NotNull File file) { File[] files = file.listFiles(); if (files != null) { for (File child : files) { - if (!delete(child)) return false; + if (!deleteRecursively(child)) return false; } } diff --git a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java index 57e86a9fe3a4..1d0c01806927 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java @@ -421,6 +421,13 @@ public class FileUtil extends FileUtilRt { } public static boolean delete(@NotNull File file) { + if (NIO_FILE_API_AVAILABLE) { + return deleteRecursivelyNIO(file); + } + return deleteRecursively(file); + } + + private static boolean deleteRecursively(@NotNull File file) { FileAttributes attributes = FileSystemUtil.getAttributes(file); if (attributes == null) return true; @@ -428,7 +435,7 @@ public class FileUtil extends FileUtilRt { File[] files = file.listFiles(); if (files != null) { for (File child : files) { - if (!delete(child)) return false; + if (!deleteRecursively(child)) return false; } } }