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

slachiewicz pushed a commit to branch excludeFix
in repository https://gitbox.apache.org/repos/asf/maven-javadoc-plugin.git

commit 7718b18e6a168e9b01212d2479cbbb949fe32485
Author: Sylwester Lachiewicz <[email protected]>
AuthorDate: Fri Oct 10 23:24:01 2025 +0200

    Fix excludePackageNames wildcard handling to correctly match subpackages
---
 .../maven/plugins/javadoc/AbstractJavadocMojo.java | 13 +++-
 .../apache/maven/plugins/javadoc/JavadocUtil.java  | 18 +++--
 .../maven/plugins/javadoc/JavadocUtilTest.java     | 76 ++++++++++++++++++++++
 3 files changed, 98 insertions(+), 9 deletions(-)

diff --git 
a/src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java 
b/src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java
index d00714cf..a6d6bf6b 100644
--- a/src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java
+++ b/src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java
@@ -678,16 +678,23 @@ public abstract class AbstractJavadocMojo extends 
AbstractMojo {
      * <code>-subpackages</code>. Multiple packages can be separated by commas 
(<code>,</code>), colons (<code>:</code>)
      * or semicolons (<code>;</code>).
      * <p>
-     * Wildcards work as followed:
+     * Wildcards work as follows:
      * <ul>
-     *   <li>a wildcard at the beginning should match one or more 
directories</li>
-     *   <li>any other wildcard must match exactly one directory</li>
+     *   <li>a wildcard at the beginning should match one or more package name 
segments</li>
+     *   <li>a wildcard at the end should match one or more package name 
segments (to include all subpackages)</li>
+     *   <li>a wildcard in the middle should match exactly one package name 
segment</li>
      * </ul>
      * </p>
      * Example:
      * <pre>
      * 
&lt;excludePackageNames&gt;*.internal:org.acme.exclude1.*:org.acme.exclude2&lt;/excludePackageNames&gt;
      * </pre>
+     * This will exclude:
+     * <ul>
+     *   <li>All packages ending with <code>.internal</code> (e.g., 
<code>com.example.internal</code>, <code>org.internal</code>)</li>
+     *   <li>All subpackages under <code>org.acme.exclude1</code> (e.g., 
<code>org.acme.exclude1.sub</code>, 
<code>org.acme.exclude1.sub.deep</code>)</li>
+     *   <li>Only the package <code>org.acme.exclude2</code> (not its 
subpackages)</li>
+     * </ul>
      * @see <a 
href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc";>Javadoc
 option exclude</a>.
      */
     @Parameter(property = "excludePackageNames")
diff --git a/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java 
b/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
index 040e9992..fdb3c96a 100644
--- a/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
+++ b/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
@@ -389,13 +389,19 @@ public class JavadocUtil {
         for (String excludePackagename : excludePackagenames) {
             // Usage of wildcard was bad specified and bad implemented, i.e. 
using String.contains()
             //   without respecting surrounding context
-            // Following implementation should match requirements as defined 
in the examples:
+            // Following implementation should match requirements as aligned 
with javadoc -exclude behavior:
             // - A wildcard at the beginning should match one or more 
directories
-            // - Any other wildcard must match exactly one directory
-            Pattern p = Pattern.compile(excludePackagename
-                    .replace(".", regexFileSeparator)
-                    .replaceFirst("^\\*", ".+")
-                    .replace("*", "[^" + regexFileSeparator + "]+"));
+            // - A wildcard at the end should match one or more directories 
(to include all subpackages)
+            // - A wildcard in the middle should match exactly one directory
+            String pattern = excludePackagename.replace(".", 
regexFileSeparator);
+            // Handle leading wildcard: match one or more directory levels
+            pattern = pattern.replaceFirst("^\\*", ".+");
+            // Handle trailing wildcard: match one or more directory levels 
(for subpackages)
+            pattern = pattern.replaceFirst("\\*$", ".+");
+            // Handle wildcards in the middle: match exactly one directory 
level
+            pattern = pattern.replace("*", "[^" + regexFileSeparator + "]+");
+
+            Pattern p = Pattern.compile(pattern);
 
             for (String aFileList : fileList) {
                 if (p.matcher(aFileList).matches()) {
diff --git 
a/src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java 
b/src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java
index 3b4f50e3..9e1255c5 100644
--- a/src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java
+++ b/src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java
@@ -681,4 +681,80 @@ public class JavadocUtilTest extends PlexusTestCase {
         List<String> values = JavadocUtil.toList(value);
         assertThat(values).containsExactly("*.internal", 
"org.acme.exclude1.*", "org.acme.exclude2");
     }
+
+    /**
+     * Test for getExcludedPackages with wildcard patterns
+     */
+    public void testGetExcludedPackages() throws Exception {
+        // Create test directory structure
+        File testDir = new File(getBasedir(), 
"target/test/unit/exclude-packages-test");
+        if (testDir.exists()) {
+            FileUtils.deleteDirectory(testDir);
+        }
+
+        // Create package structure:
+        // org.example (with Main.java)
+        // org.example.sub1 (with Class1.java)
+        // org.example.sub2 (with Class2.java)
+        // org.example.sub2.subsub (with Class3.java)
+        // org.other (with Other.java)
+        // com.internal (with Internal.java)
+
+        File orgExample = new File(testDir, "org/example");
+        File orgExampleSub1 = new File(testDir, "org/example/sub1");
+        File orgExampleSub2 = new File(testDir, "org/example/sub2");
+        File orgExampleSub2Subsub = new File(testDir, 
"org/example/sub2/subsub");
+        File orgOther = new File(testDir, "org/other");
+        File comInternal = new File(testDir, "com/internal");
+
+        assertTrue(orgExample.mkdirs());
+        assertTrue(orgExampleSub1.mkdirs());
+        assertTrue(orgExampleSub2.mkdirs());
+        assertTrue(orgExampleSub2Subsub.mkdirs());
+        assertTrue(orgOther.mkdirs());
+        assertTrue(comInternal.mkdirs());
+
+        // Create Java files in each directory
+        new File(orgExample, "Main.java").createNewFile();
+        new File(orgExampleSub1, "Class1.java").createNewFile();
+        new File(orgExampleSub2, "Class2.java").createNewFile();
+        new File(orgExampleSub2Subsub, "Class3.java").createNewFile();
+        new File(orgOther, "Other.java").createNewFile();
+        new File(comInternal, "Internal.java").createNewFile();
+
+        Path testPath = testDir.toPath();
+
+        // Test 1: org.example.* should match all subpackages of org.example
+        // Expected: org.example.sub1, org.example.sub2, 
org.example.sub2.subsub
+        Collection<String> excludes1 = 
Collections.singletonList("org.example.*");
+        Collection<String> result1 = JavadocUtil.getExcludedPackages(testPath, 
excludes1);
+        assertThat(result1)
+                .as("org.example.* should match all subpackages of 
org.example")
+                .containsExactlyInAnyOrder("org.example.sub1", 
"org.example.sub2", "org.example.sub2.subsub");
+
+        // Test 2: org.example should match only org.example itself
+        Collection<String> excludes2 = 
Collections.singletonList("org.example");
+        Collection<String> result2 = JavadocUtil.getExcludedPackages(testPath, 
excludes2);
+        assertThat(result2)
+                .as("org.example should match only org.example package")
+                .containsExactly("org.example");
+
+        // Test 3: *.internal should match any package ending with .internal
+        Collection<String> excludes3 = Collections.singletonList("*.internal");
+        Collection<String> result3 = JavadocUtil.getExcludedPackages(testPath, 
excludes3);
+        assertThat(result3).as("*.internal should match 
com.internal").containsExactly("com.internal");
+
+        // Test 4: org.*.sub1 should match org.example.sub1 (wildcard in the 
middle matches exactly one level)
+        Collection<String> excludes4 = Collections.singletonList("org.*.sub1");
+        Collection<String> result4 = JavadocUtil.getExcludedPackages(testPath, 
excludes4);
+        assertThat(result4).as("org.*.sub1 should match 
org.example.sub1").containsExactly("org.example.sub1");
+
+        // Test 5: Multiple patterns
+        Collection<String> excludes5 = Arrays.asList("org.example.*", 
"org.other");
+        Collection<String> result5 = JavadocUtil.getExcludedPackages(testPath, 
excludes5);
+        assertThat(result5)
+                .as("Multiple patterns should work together")
+                .containsExactlyInAnyOrder(
+                        "org.example.sub1", "org.example.sub2", 
"org.example.sub2.subsub", "org.other");
+    }
 }

Reply via email to