Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -449,18 +449,21 @@ private static String[] normalizePatterns(final Collection<String> patterns, fin
pattern = pattern.substring(3);
}
pattern = pattern.replace("/**/**/", "/**/");

// Escape special characters, including braces
// Braces from user input must be literals; we'll inject our own braces for expansion below
pattern = pattern.replace("\\", "\\\\")
.replace("[", "\\[")
.replace("]", "\\]")
.replace("{", "\\{")
.replace("}", "\\}");

// Transform ** patterns to use brace expansion for POSIX behavior
// This replaces the complex addPatternsWithOneDirRemoved logic
// We perform this after escaping so that only these injected braces participate in expansion
pattern = pattern.replace("**/", "{**/,}");

normalized.add(DEFAULT_SYNTAX + pattern);
/*
* If the pattern starts or ends with "**", Java GLOB expects a directory level at
* that location while Maven seems to consider that "**" can mean "no directory".
* Add another pattern for reproducing this effect.
*/
addPatternsWithOneDirRemoved(normalized, pattern, 0);
} else {
normalized.add(pattern);
}
Expand All @@ -469,37 +472,6 @@ private static String[] normalizePatterns(final Collection<String> patterns, fin
return simplify(normalized, excludes);
}

/**
* Adds all variants of the given pattern with {@code **} removed.
* This is used for simulating the Maven behavior where {@code "**} may match zero directory.
* Tests suggest that we need an explicit GLOB pattern with no {@code "**"} for matching an absence of directory.
*
* @param patterns where to add the derived patterns
* @param pattern the pattern for which to add derived forms, without the "glob:" syntax prefix
* @param end should be 0 (reserved for recursive invocations of this method)
*/
private static void addPatternsWithOneDirRemoved(final Set<String> patterns, final String pattern, int end) {
final int length = pattern.length();
int start;
while ((start = pattern.indexOf("**", end)) >= 0) {
end = start + 2; // 2 is the length of "**".
if (end < length) {
if (pattern.charAt(end) != '/') {
continue;
}
if (start == 0) {
end++; // Ommit the leading slash if there is nothing before it.
}
}
if (start > 0 && pattern.charAt(--start) != '/') {
continue;
}
String reduced = pattern.substring(0, start) + pattern.substring(end);
patterns.add(DEFAULT_SYNTAX + reduced);
addPatternsWithOneDirRemoved(patterns, reduced, start);
}
}

/**
* Applies some heuristic rules for simplifying the set of patterns,
* then returns the patterns as an array.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,77 @@ public void testExcludeOmission() {
List<String> excludes = List.of("baz/**");
PathMatcher matcher = PathSelector.of(directory, includes, excludes, true);
String s = matcher.toString();
assertTrue(s.contains("glob:**/*.java"));
assertTrue(s.contains("glob:**/*.java") || s.contains("glob:{**/,}*.java"));
assertFalse(s.contains("project.pj")); // Unnecessary exclusion should have been omitted.
assertFalse(s.contains(".DS_Store"));
}

/**
* Test to verify the current behavior of ** patterns before implementing brace expansion improvement.
* This test documents the expected behavior that must be preserved after the optimization.
*/
@Test
public void testDoubleAsteriskPatterns(final @TempDir Path directory) throws IOException {
// Create a nested directory structure to test ** behavior
Path src = Files.createDirectory(directory.resolve("src"));
Path main = Files.createDirectory(src.resolve("main"));
Path java = Files.createDirectory(main.resolve("java"));
Path test = Files.createDirectory(src.resolve("test"));
Path testJava = Files.createDirectory(test.resolve("java"));

// Create files at different levels
Files.createFile(directory.resolve("root.java"));
Files.createFile(src.resolve("src.java"));
Files.createFile(main.resolve("main.java"));
Files.createFile(java.resolve("deep.java"));
Files.createFile(test.resolve("test.java"));
Files.createFile(testJava.resolve("testdeep.java"));

// Test that ** matches zero or more directories (POSIX behavior)
PathMatcher matcher = PathSelector.of(directory, List.of("src/**/test/**/*.java"), null, false);

// Should match files in src/test/java/ (** matches zero dirs before test, zero dirs after test)
assertTrue(matcher.matches(testJava.resolve("testdeep.java")));

// Should also match files directly in src/test/ (** matches zero dirs after test)
assertTrue(matcher.matches(test.resolve("test.java")));

// Should NOT match files in other paths
assertFalse(matcher.matches(directory.resolve("root.java")));
assertFalse(matcher.matches(src.resolve("src.java")));
assertFalse(matcher.matches(main.resolve("main.java")));
assertFalse(matcher.matches(java.resolve("deep.java")));
}

@Test
public void testLiteralBracesAreEscapedInMavenSyntax(@TempDir Path directory) throws IOException {
// Create a file with literal braces in the name
Files.createDirectories(directory.resolve("dir"));
Path file = directory.resolve("dir/foo{bar}.txt");
Files.createFile(file);

// In Maven syntax (no explicit glob:), user-provided braces must be treated literally
PathMatcher matcher = PathSelector.of(directory, List.of("**/foo{bar}.txt"), null, false);

assertTrue(matcher.matches(file));
}

@Test
public void testBraceAlternationOnlyWithExplicitGlob(@TempDir Path directory) throws IOException {
// Create src/main/java and src/test/java with files
Path mainJava = Files.createDirectories(directory.resolve("src/main/java"));
Path testJava = Files.createDirectories(directory.resolve("src/test/java"));
Path mainFile = Files.createFile(mainJava.resolve("Main.java"));
Path testFile = Files.createFile(testJava.resolve("Test.java"));

// Without explicit glob:, braces from user input are escaped and treated literally -> no matches
PathMatcher mavenSyntax = PathSelector.of(directory, List.of("src/{main,test}/**/*.java"), null, false);
assertFalse(mavenSyntax.matches(mainFile));
assertFalse(mavenSyntax.matches(testFile));

// With explicit glob:, braces should act as alternation and match both
PathMatcher explicitGlob = PathSelector.of(directory, List.of("glob:src/{main,test}/**/*.java"), null, false);
assertTrue(explicitGlob.matches(mainFile));
assertTrue(explicitGlob.matches(testFile));
}
}
Loading