Skip to content
Open
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 @@ -678,16 +678,23 @@ public AbstractJavadocMojo(
* <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")
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -389,13 +389,19 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
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
Comment on lines +392 to +395
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment suggests full alignment with javadoc -exclude behavior, but the plugin’s documented behavior (and tests) keep bare package names (e.g., org.acme.exclude2) as exact-only (not recursive). Please rephrase to clarify this only aligns trailing wildcards with javadoc, not bare package names.

Copilot uses AI. Check for mistakes.
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 + "]+");
Comment on lines +396 to +402
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] To avoid unintended regex interpretation of literal characters, build the regex by quoting non-wildcard fragments and inserting the appropriate wildcard expansions based on position. This keeps behavior the same while making the transformation robust against any non-package characters.

Suggested change
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 + "]+");
// Build regex by quoting non-wildcard fragments and inserting wildcard expansions
StringBuilder patternBuilder = new StringBuilder();
String[] parts = excludePackagename.split("\\*", -1); // -1 to include trailing empty strings
for (int i = 0; i < parts.length; i++) {
if (!parts[i].isEmpty()) {
// Replace '.' with file separator and quote the fragment
patternBuilder.append(Pattern.quote(parts[i].replace(".", regexFileSeparator)));
}
if (i < parts.length - 1) {
// Insert wildcard regex
// If at start or end, use ".+" (one or more directories), else "[^sep]+"
boolean isStart = (i == 0);
boolean isEnd = (i == parts.length - 2 && parts[parts.length - 1].isEmpty());
if (isStart || isEnd) {
patternBuilder.append(".+");
} else {
patternBuilder.append("[^").append(regexFileSeparator).append("]+");
}
}
}
String pattern = patternBuilder.toString();

Copilot uses AI. Check for mistakes.

Pattern p = Pattern.compile(pattern);

for (String aFileList : fileList) {
if (p.matcher(aFileList).matches()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,4 +681,80 @@ public void testToList() {
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");
}
}