Skip to content

Commit 7718b18

Browse files
committed
Fix excludePackageNames wildcard handling to correctly match subpackages
1 parent 20c6e04 commit 7718b18

File tree

3 files changed

+98
-9
lines changed

3 files changed

+98
-9
lines changed

src/main/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojo.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -678,16 +678,23 @@ public AbstractJavadocMojo(
678678
* <code>-subpackages</code>. Multiple packages can be separated by commas (<code>,</code>), colons (<code>:</code>)
679679
* or semicolons (<code>;</code>).
680680
* <p>
681-
* Wildcards work as followed:
681+
* Wildcards work as follows:
682682
* <ul>
683-
* <li>a wildcard at the beginning should match one or more directories</li>
684-
* <li>any other wildcard must match exactly one directory</li>
683+
* <li>a wildcard at the beginning should match one or more package name segments</li>
684+
* <li>a wildcard at the end should match one or more package name segments (to include all subpackages)</li>
685+
* <li>a wildcard in the middle should match exactly one package name segment</li>
685686
* </ul>
686687
* </p>
687688
* Example:
688689
* <pre>
689690
* &lt;excludePackageNames&gt;*.internal:org.acme.exclude1.*:org.acme.exclude2&lt;/excludePackageNames&gt;
690691
* </pre>
692+
* This will exclude:
693+
* <ul>
694+
* <li>All packages ending with <code>.internal</code> (e.g., <code>com.example.internal</code>, <code>org.internal</code>)</li>
695+
* <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>
696+
* <li>Only the package <code>org.acme.exclude2</code> (not its subpackages)</li>
697+
* </ul>
691698
* @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option exclude</a>.
692699
*/
693700
@Parameter(property = "excludePackageNames")

src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,19 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
389389
for (String excludePackagename : excludePackagenames) {
390390
// Usage of wildcard was bad specified and bad implemented, i.e. using String.contains()
391391
// without respecting surrounding context
392-
// Following implementation should match requirements as defined in the examples:
392+
// Following implementation should match requirements as aligned with javadoc -exclude behavior:
393393
// - A wildcard at the beginning should match one or more directories
394-
// - Any other wildcard must match exactly one directory
395-
Pattern p = Pattern.compile(excludePackagename
396-
.replace(".", regexFileSeparator)
397-
.replaceFirst("^\\*", ".+")
398-
.replace("*", "[^" + regexFileSeparator + "]+"));
394+
// - A wildcard at the end should match one or more directories (to include all subpackages)
395+
// - A wildcard in the middle should match exactly one directory
396+
String pattern = excludePackagename.replace(".", regexFileSeparator);
397+
// Handle leading wildcard: match one or more directory levels
398+
pattern = pattern.replaceFirst("^\\*", ".+");
399+
// Handle trailing wildcard: match one or more directory levels (for subpackages)
400+
pattern = pattern.replaceFirst("\\*$", ".+");
401+
// Handle wildcards in the middle: match exactly one directory level
402+
pattern = pattern.replace("*", "[^" + regexFileSeparator + "]+");
403+
404+
Pattern p = Pattern.compile(pattern);
399405

400406
for (String aFileList : fileList) {
401407
if (p.matcher(aFileList).matches()) {

src/test/java/org/apache/maven/plugins/javadoc/JavadocUtilTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,4 +681,80 @@ public void testToList() {
681681
List<String> values = JavadocUtil.toList(value);
682682
assertThat(values).containsExactly("*.internal", "org.acme.exclude1.*", "org.acme.exclude2");
683683
}
684+
685+
/**
686+
* Test for getExcludedPackages with wildcard patterns
687+
*/
688+
public void testGetExcludedPackages() throws Exception {
689+
// Create test directory structure
690+
File testDir = new File(getBasedir(), "target/test/unit/exclude-packages-test");
691+
if (testDir.exists()) {
692+
FileUtils.deleteDirectory(testDir);
693+
}
694+
695+
// Create package structure:
696+
// org.example (with Main.java)
697+
// org.example.sub1 (with Class1.java)
698+
// org.example.sub2 (with Class2.java)
699+
// org.example.sub2.subsub (with Class3.java)
700+
// org.other (with Other.java)
701+
// com.internal (with Internal.java)
702+
703+
File orgExample = new File(testDir, "org/example");
704+
File orgExampleSub1 = new File(testDir, "org/example/sub1");
705+
File orgExampleSub2 = new File(testDir, "org/example/sub2");
706+
File orgExampleSub2Subsub = new File(testDir, "org/example/sub2/subsub");
707+
File orgOther = new File(testDir, "org/other");
708+
File comInternal = new File(testDir, "com/internal");
709+
710+
assertTrue(orgExample.mkdirs());
711+
assertTrue(orgExampleSub1.mkdirs());
712+
assertTrue(orgExampleSub2.mkdirs());
713+
assertTrue(orgExampleSub2Subsub.mkdirs());
714+
assertTrue(orgOther.mkdirs());
715+
assertTrue(comInternal.mkdirs());
716+
717+
// Create Java files in each directory
718+
new File(orgExample, "Main.java").createNewFile();
719+
new File(orgExampleSub1, "Class1.java").createNewFile();
720+
new File(orgExampleSub2, "Class2.java").createNewFile();
721+
new File(orgExampleSub2Subsub, "Class3.java").createNewFile();
722+
new File(orgOther, "Other.java").createNewFile();
723+
new File(comInternal, "Internal.java").createNewFile();
724+
725+
Path testPath = testDir.toPath();
726+
727+
// Test 1: org.example.* should match all subpackages of org.example
728+
// Expected: org.example.sub1, org.example.sub2, org.example.sub2.subsub
729+
Collection<String> excludes1 = Collections.singletonList("org.example.*");
730+
Collection<String> result1 = JavadocUtil.getExcludedPackages(testPath, excludes1);
731+
assertThat(result1)
732+
.as("org.example.* should match all subpackages of org.example")
733+
.containsExactlyInAnyOrder("org.example.sub1", "org.example.sub2", "org.example.sub2.subsub");
734+
735+
// Test 2: org.example should match only org.example itself
736+
Collection<String> excludes2 = Collections.singletonList("org.example");
737+
Collection<String> result2 = JavadocUtil.getExcludedPackages(testPath, excludes2);
738+
assertThat(result2)
739+
.as("org.example should match only org.example package")
740+
.containsExactly("org.example");
741+
742+
// Test 3: *.internal should match any package ending with .internal
743+
Collection<String> excludes3 = Collections.singletonList("*.internal");
744+
Collection<String> result3 = JavadocUtil.getExcludedPackages(testPath, excludes3);
745+
assertThat(result3).as("*.internal should match com.internal").containsExactly("com.internal");
746+
747+
// Test 4: org.*.sub1 should match org.example.sub1 (wildcard in the middle matches exactly one level)
748+
Collection<String> excludes4 = Collections.singletonList("org.*.sub1");
749+
Collection<String> result4 = JavadocUtil.getExcludedPackages(testPath, excludes4);
750+
assertThat(result4).as("org.*.sub1 should match org.example.sub1").containsExactly("org.example.sub1");
751+
752+
// Test 5: Multiple patterns
753+
Collection<String> excludes5 = Arrays.asList("org.example.*", "org.other");
754+
Collection<String> result5 = JavadocUtil.getExcludedPackages(testPath, excludes5);
755+
assertThat(result5)
756+
.as("Multiple patterns should work together")
757+
.containsExactlyInAnyOrder(
758+
"org.example.sub1", "org.example.sub2", "org.example.sub2.subsub", "org.other");
759+
}
684760
}

0 commit comments

Comments
 (0)