This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new f01db87731 Simplify "**" handling using brace expansion (#11125)
f01db87731 is described below

commit f01db87731a47619fee053b0597668f6ad297660
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Wed Sep 17 07:59:04 2025 +0200

    Simplify "**" handling using brace expansion (#11125)
    
    - Convert "**/" to "{**/,}" after escaping special chars (including braces)
    - Treat user braces literally in Maven-style patterns; alternation remains 
with explicit glob:
    - Add tests for literal braces and explicit glob alternation
    
    Fixes #11110
---
 .../java/org/apache/maven/impl/PathSelector.java   | 46 +++-----------
 .../org/apache/maven/impl/PathSelectorTest.java    | 71 +++++++++++++++++++++-
 2 files changed, 79 insertions(+), 38 deletions(-)

diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java
index 20d68cd7bb..a8cc3da2ec 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/PathSelector.java
@@ -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);
                 }
@@ -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.
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java
index c7d860bc0b..f541ee5e40 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/PathSelectorTest.java
@@ -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));
+    }
 }

Reply via email to