diff --git a/platform/lang-api/src/com/intellij/formatting/fileSet/FileSetEntry.java b/platform/lang-api/src/com/intellij/formatting/fileSet/FileSetEntry.java new file mode 100644 index 000000000000..28205246ae37 --- /dev/null +++ b/platform/lang-api/src/com/intellij/formatting/fileSet/FileSetEntry.java @@ -0,0 +1,139 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.formatting.fileSet; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Pattern; + +/** + * Describes a set of files specified by a directory and/or by a file mask. + *

+ * Examples: + *

+ */ +public class FileSetEntry { + + private @Nullable Pattern myPathPattern; + private @Nullable Pattern myFileNamePattern; + + public FileSetEntry(@NotNull String spec) { + compileSpec(spec); + } + + private void compileSpec(@NotNull String spec) { + String pathSpec = ""; + String fileSpec; + int lastSlashPos = spec.lastIndexOf('/'); + if (lastSlashPos >= 0) { + fileSpec = spec.substring(lastSlashPos + 1); + pathSpec = spec.substring(0, lastSlashPos + 1); + if (pathSpec.length() > 0 && pathSpec.charAt(0) != '/') { + pathSpec = "/**/" + pathSpec; + } + } + else { + fileSpec = spec; + } + if (!pathSpec.isEmpty()) { + myPathPattern = Pattern.compile(specToRegexp(pathSpec, true)); + } + if (!fileSpec.isEmpty()) { + myFileNamePattern = Pattern.compile(specToRegexp(fileSpec, false)); + } + } + + private static String specToRegexp(@NotNull String spec, boolean isPathSpec) { + StringBuilder sb = new StringBuilder(); + char[] chars = spec.toCharArray(); + int i = 0; + while (i < chars.length) { + char c = chars[i]; + switch (c) { + case '*': + if (isPathSpec && i < chars.length - 1 && chars[i + 1] == '*') { + sb.append("([^/]*/)*"); + i++; + if (i < chars.length - 1 && chars[i + 1] == '/') { + i ++; + } + } + else { + sb.append("[^/]*"); + } + break; + case '?': + sb.append("[^/]"); + break; + default: + if (isRegexSpecialChar(c)) { + sb.append('\\').append(c); + } + else { + sb.append(c); + } + } + i ++; + } + return sb.toString(); + } + + + private static boolean isRegexSpecialChar(char c) { + return "^${}[]().*+-&".indexOf(c) >= 0; + } + + /** + * Checks if the file set entry matches the given virtual file. + * + * @param project The current project. + * @param virtualFile The virtual file to check against. + * @return True if there is a match, false otherwise. + */ + public boolean matches(@NotNull Project project, @NotNull VirtualFile virtualFile) { + if (myFileNamePattern == null && myPathPattern == null) return false; // Empty spec matches nothing + String name = virtualFile.getName(); + VirtualFile parent = virtualFile.getParent(); + String path = getRelativePath(project, parent) + "/"; + return patternMatches(myPathPattern, path) && patternMatches(myFileNamePattern, name); + } + + @NotNull + private static String getRelativePath(@NotNull Project project, @Nullable VirtualFile parent) { + VirtualFile projectDir = project.getBaseDir(); + String projectPath = projectDir.getPath(); + if (parent != null) { + String parentPath = parent.getPath(); + if (parentPath.startsWith(projectPath)) { + return parentPath.substring(projectPath.length()); + } + else { + return parentPath; + } + } + return ""; + } + + private static boolean patternMatches(@Nullable Pattern pattern, @NotNull String str) { + return pattern == null || pattern.matcher(str).matches(); + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/formatting/fileSet/FileSetEntryTest.java b/platform/platform-tests/testSrc/com/intellij/formatting/fileSet/FileSetEntryTest.java new file mode 100644 index 000000000000..51f5014ca113 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/formatting/fileSet/FileSetEntryTest.java @@ -0,0 +1,66 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.formatting.fileSet; + +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PlatformTestCase; +import org.jetbrains.annotations.NotNull; + +public class FileSetEntryTest extends PlatformTestCase { + + public void testSimpleFileMask() { + FileSetEntry entry = new FileSetEntry("*.java"); + assertTrue(entry.matches(getProject(), createFile("src/test.java"))); + assertFalse(entry.matches(getProject(), createFile("src/test.txt"))); + assertTrue(entry.matches(getProject(), createFile("file3.java"))); + } + + public void testFileMaskWithDir() { + FileSetEntry entry = new FileSetEntry("/src/*.java"); + assertTrue(entry.matches(getProject(), createFile("src/test.java"))); + assertFalse(entry.matches(getProject(), createFile("src/test.txt"))); + assertFalse(entry.matches(getProject(), createFile("file3.java"))); + } + + public void testExtendedDirSpec() { + FileSetEntry entry = new FileSetEntry("/src/**/test/*.java"); + assertTrue(entry.matches(getProject(), createFile("src/test/file1.java"))); + assertTrue(entry.matches(getProject(), createFile("src/dir1/test/file1.java"))); + assertTrue(entry.matches(getProject(), createFile("src/dir1/dir2/test/file1.java"))); + assertFalse(entry.matches(getProject(), createFile("src/dir1/dir2/test/dir3/file1.java"))); + FileSetEntry entry2 = new FileSetEntry("/src/**/test/**/*.java"); + assertTrue(entry2.matches(getProject(), createFile("src/test/a/file1.java"))); + assertTrue(entry2.matches(getProject(), createFile("src/b/test/a/file1.java"))); + } + + public void testAnyCharMarks() { + FileSetEntry entry = new FileSetEntry("/test-??/**/test?.java"); + assertFalse(entry.matches(getProject(), createFile("test-1/test2.java"))); + assertTrue(entry.matches(getProject(), createFile("test-12/test3.java"))); + assertTrue(entry.matches(getProject(), createFile("test-12/test/testa.java"))); + } + + + public void testRelativeDirTest() { + FileSetEntry entry = new FileSetEntry("test/*.java"); + assertTrue(entry.matches(getProject(), createFile("src/test/file1.java"))); + assertTrue(entry.matches(getProject(), createFile("test/file2.java"))); + assertTrue(entry.matches(getProject(), createFile("a/b/c/d/test/file3.java"))); + } + + private VirtualFile createFile(@NotNull String path) { + String[] dirNames = path.split("/"); + VirtualFile baseDir = getProject().getBaseDir(); + for (int i = 0; i < dirNames.length - 1; i ++) { + VirtualFile existing = VfsUtilCore.findRelativeFile(dirNames[i], baseDir); + if (existing == null) { + baseDir = createChildDirectory(baseDir, dirNames[i]); + } + else { + baseDir = existing; + } + } + return createChildData(baseDir, dirNames[dirNames.length - 1]); + } + +}