From e50c234299ebc2586ee5cc6a939505de2b354037 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 30 Aug 2012 21:55:40 +0400 Subject: [PATCH] FileUtil.isAncestor() performance --- .../util/io/FileUtilPerformanceTest.java | 24 +++++++++-- .../intellij/openapi/util/io/FileUtil.java | 41 ++++++++++--------- .../openapi/util/io/FileUtilTest.java | 19 +++++++++ 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/util/io/FileUtilPerformanceTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/util/io/FileUtilPerformanceTest.java index 30311478cb6b..a0e12a858478 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/util/io/FileUtilPerformanceTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/util/io/FileUtilPerformanceTest.java @@ -20,21 +20,39 @@ import com.intellij.util.ThrowableRunnable; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class FileUtilPerformanceTest { + private String myTestPath = "/a/b/c/./d///e/../f/g/h/i/j/"; + private String myCanonicalPath = "/a/b/c/d/f/g/h/i/j"; + @Test public void toCanonicalPath() throws Exception { - final String testPath = "/a/b/c/./d///e/../f/g/h/i/j/"; - assertEquals("/a/b/c/d/f/g/h/i/j", FileUtil.toCanonicalPath(testPath)); + assertEquals(myCanonicalPath, FileUtil.toCanonicalPath(myTestPath)); PlatformTestUtil.startPerformanceTest("", 1000, new ThrowableRunnable() { @Override public void run() throws Throwable { for (int i = 0; i < 1000000; ++i) { - final String canonicalPath = FileUtil.toCanonicalPath(testPath, '/'); + final String canonicalPath = FileUtil.toCanonicalPath(myTestPath, '/'); assert canonicalPath != null && canonicalPath.length() == 18 : canonicalPath; } } }).cpuBound().assertTiming(); } + + @Test + public void isAncestor() throws Exception { + assertTrue(FileUtil.isAncestor(myTestPath, myCanonicalPath, false)); + + PlatformTestUtil.startPerformanceTest("", 4000, new ThrowableRunnable() { + @Override + public void run() throws Throwable { + for (int i = 0; i < 1000000; ++i) { + assert FileUtil.isAncestor(myTestPath, myCanonicalPath, false); + assert !FileUtil.isAncestor(myTestPath, myCanonicalPath, true); + } + } + }).cpuBound().assertTiming(); + } } 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 098ff4ecc995..f90fac6fcfd2 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java @@ -99,31 +99,34 @@ public class FileUtil extends FileUtilRt { return new File(path).isAbsolute(); } - public static boolean isAncestor(@NotNull final String ancestor, @NotNull final String descendant, final boolean strict) { - return isAncestor(new File(toSystemDependentName(ancestor)), new File(toSystemDependentName(descendant)), strict); - } - /** * Check if the {@code ancestor} is an ancestor of {@code file}. * - * @param ancestor the file - * @param file the file - * @param strict if {@code false} then this method returns {@code true} if {@code ancestor} - * and {@code file} are equal - * @return {@code true} if {@code ancestor} is parent of {@code file}; {@code false} otherwise + * @param ancestor supposed ancestor. + * @param file supposed descendant. + * @param strict if {@code false} then this method returns {@code true} if {@code ancestor} equals to {@code file}. + * @return {@code true} if {@code ancestor} is parent of {@code file}; {@code false} otherwise. */ public static boolean isAncestor(@NotNull File ancestor, @NotNull File file, boolean strict) { - File parent = strict ? getParentFile(file) : file; - while (true) { - if (parent == null) { - return false; - } - // Do not user file.equals as it incorrectly works on MacOS - if (pathsEqual(parent.getPath(), ancestor.getPath())) { - return true; - } - parent = getParentFile(parent); + return isAncestor(ancestor.getPath(), file.getPath(), strict); + } + + public static boolean isAncestor(@NotNull String ancestor, @NotNull String descendant, boolean strict) { + String ancestorPath = toCanonicalPath(ancestor); + String filePath = toCanonicalPath(descendant); + + if (ancestorPath == null || filePath == null) { + return false; } + + boolean startsWith = SystemInfo.isFileSystemCaseSensitive ? StringUtil.startsWith(filePath, ancestorPath) + : StringUtil.startsWithIgnoreCase(filePath, ancestorPath); + if (!startsWith) { + return false; + } + + return filePath.length() > ancestorPath.length() && filePath.charAt(ancestorPath.length()) == '/' || + !strict && filePath.length() == ancestorPath.length(); } /** diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java index 9b869a0b6d4b..6560b6eff73a 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java @@ -15,9 +15,12 @@ */ package com.intellij.openapi.util.io; +import com.intellij.openapi.util.SystemInfo; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author Roman Shevchenko @@ -57,4 +60,20 @@ public class FileUtilTest { assertEquals("c:/", FileUtil.toCanonicalPath("c:\\a\\..\\..", WINDOWS_SEPARATOR)); assertEquals("c:/b", FileUtil.toCanonicalPath("c:\\a\\..\\..\\b", WINDOWS_SEPARATOR)); } + + @Test + public void isAncestor() throws Exception { + assertTrue(FileUtil.isAncestor("/a/b/c", "/a/b/c/d/e/f", true)); + assertTrue(FileUtil.isAncestor("/a/b/c/", "/a/b/c/d/e/f", true)); + assertFalse(FileUtil.isAncestor("/a/b/c/1", "/a/b/c/2", true)); + assertFalse(FileUtil.isAncestor("/a/b/c/1", "/a/b/c/2", false)); + assertTrue(FileUtil.isAncestor("/a/b/c/", "/a/b/c", false)); + assertTrue(FileUtil.isAncestor("/a///b/c", "/a/b/c/", false)); + assertFalse(FileUtil.isAncestor("/a/b/c/", "/a/./b/c", true)); + assertFalse(FileUtil.isAncestor("/a/b/c", "/a/b/c/", true)); + assertFalse(FileUtil.isAncestor("/a/b/c", "/a/b/cde", true)); + assertFalse(FileUtil.isAncestor("/a/b/c", "/a/b/cde", false)); + + assertEquals(!SystemInfo.isFileSystemCaseSensitive, FileUtil.isAncestor("/a/b/c", "/a/B/c/d", true)); + } }