From 1134f5af45456bf98a1b53ca811610812492163e Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Sun, 12 Oct 2025 17:39:27 +0200
Subject: [PATCH 01/25] Migrate from Maven Archiver to standard `jar` tool by
using the standard `java.util.spi.ToolProvider` API (requires Java 9+). Files
in the `classes` directories are dispatched to the `--manifest` and
`--release` options, which allow additional verifications by the `jar` tool.
Derive a POM for each individual JAR file as the intersection of the project
model and the module-info of the JAR file.
Side effects:
* Remove the default `**/package.html` exclude.
* Automatic use of `META-INF/MANIFEST.MF` file found in `classes` directory.
* Automatic Multi-Release always enabled, unless `detectMultiReleaseJar` is set to `false`.
* In a multi-module project, use Java module names as artifact names.
* Whether the `--date` option is supported depends on the Java version.
* Re-run the `jar` tool with the --validate operation mode if validation was not implicit.
---
pom.xml | 28 +-
.../verify.bsh | 3 +-
.../invoker.properties | 10 +-
src/it/MJAR-292-detect-mjar/pom.xml | 4 +-
src/it/MJAR-292-disable-detect-mjar/pom.xml | 1 +
src/it/MJAR-30-include/verify.groovy | 2 +-
src/it/MJAR-70-recreation/verify.bsh | 2 +-
src/it/mjar-71-01/pom.xml | 4 +-
.../some-manifest.mf} | 0
src/it/mjar-71-01/verify.groovy | 16 +-
src/it/mjar-71-02/pom.xml | 2 +-
src/it/mjar-71-02/verify.groovy | 16 +-
src/it/multi-module/pom.xml | 67 ++
.../foo.bar.more/main/java/module-info.java | 19 +
.../foo.bar.more/main/java/more/MainFile.java | 29 +
.../src/foo.bar/main/java/foo/MainFile.java | 29 +
.../src/foo.bar/main/java/module-info.java | 19 +
src/it/multi-module/verify.groovy | 62 ++
src/it/multirelease-with-modules/pom.xml | 102 +++
.../foo.bar.more/main/java/module-info.java | 19 +
.../foo.bar.more/main/java/more/MainFile.java | 29 +
.../main/java/more/OtherFile.java | 29 +
.../main/java_16/more/OtherFile.java | 30 +
.../src/foo.bar/main/java/foo/MainFile.java | 29 +
.../src/foo.bar/main/java/foo/OtherFile.java | 29 +
.../foo.bar/main/java/foo/YetAnotherFile.java | 29 +
.../src/foo.bar/main/java/module-info.java | 19 +
.../foo.bar/main/java_16/foo/OtherFile.java | 34 +
.../multirelease-with-modules/verify.groovy | 67 ++
.../maven/plugins/jar/AbstractJarMojo.java | 384 ++++++-----
.../org/apache/maven/plugins/jar/Archive.java | 605 ++++++++++++++++++
.../maven/plugins/jar/DirectoryRole.java | 73 +++
.../maven/plugins/jar/FileCollector.java | 464 ++++++++++++++
.../org/apache/maven/plugins/jar/JarMojo.java | 11 +
.../maven/plugins/jar/MetadataFiles.java | 250 ++++++++
.../maven/plugins/jar/PomDerivation.java | 519 +++++++++++++++
.../apache/maven/plugins/jar/Providers.java | 18 +-
.../apache/maven/plugins/jar/TestJarMojo.java | 13 +-
.../maven/plugins/jar/TimestampCheck.java | 237 +++++++
.../maven/plugins/jar/ToolExecutor.java | 474 ++++++++++++++
.../apache/maven/plugins/jar/JarMojoTest.java | 11 +-
41 files changed, 3552 insertions(+), 236 deletions(-)
rename src/it/mjar-71-01/src/main/{resources/META-INF/MANIFEST.MF => my-custom-dir/some-manifest.mf} (100%)
create mode 100644 src/it/multi-module/pom.xml
create mode 100644 src/it/multi-module/src/foo.bar.more/main/java/module-info.java
create mode 100644 src/it/multi-module/src/foo.bar.more/main/java/more/MainFile.java
create mode 100644 src/it/multi-module/src/foo.bar/main/java/foo/MainFile.java
create mode 100644 src/it/multi-module/src/foo.bar/main/java/module-info.java
create mode 100644 src/it/multi-module/verify.groovy
create mode 100644 src/it/multirelease-with-modules/pom.xml
create mode 100644 src/it/multirelease-with-modules/src/foo.bar.more/main/java/module-info.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/MainFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/OtherFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar.more/main/java_16/more/OtherFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar/main/java/foo/MainFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar/main/java/foo/OtherFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar/main/java/foo/YetAnotherFile.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar/main/java/module-info.java
create mode 100644 src/it/multirelease-with-modules/src/foo.bar/main/java_16/foo/OtherFile.java
create mode 100644 src/it/multirelease-with-modules/verify.groovy
create mode 100644 src/main/java/org/apache/maven/plugins/jar/Archive.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/DirectoryRole.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/FileCollector.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/MetadataFiles.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
create mode 100644 src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
diff --git a/pom.xml b/pom.xml
index c84c2485..a00bd420 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,26 @@
Apache Maven JAR PluginBuilds a Java Archive (JAR) file from the compiled project classes and resources.
+
+
+ evenisse
+ Emmanuel Venisse
+ evenisse@apache.org
+
+ Java Developer
+
+
+
+ desruisseaux
+ Martin Desruisseaux
+ desruisseaux@apache.org
+ Geomatys
+
+ Java Developer
+
+ +1
+
+ Jerome Lacoste
@@ -133,9 +153,9 @@
provided
- org.apache.maven.shared
- file-management
- ${mavenFileManagementVersion}
+ org.apache.maven
+ maven-support
+ ${mavenVersion}org.apache.maven.shared
@@ -205,7 +225,7 @@
- src/it/mjar-71-01/src/main/resources/META-INF/MANIFEST.MF
+ src/it/mjar-71-01/src/main/my-custom-dir/some-manifest.mfsrc/it/mjar-71-02/src/main/resources/META-INF/MANIFEST.MF
diff --git a/src/it/MJAR-260-invalid-automatic-module-name/verify.bsh b/src/it/MJAR-260-invalid-automatic-module-name/verify.bsh
index 5b9d457f..7b39fb52 100644
--- a/src/it/MJAR-260-invalid-automatic-module-name/verify.bsh
+++ b/src/it/MJAR-260-invalid-automatic-module-name/verify.bsh
@@ -45,8 +45,7 @@ try
String[] snippets = new String[] {
"[INFO] BUILD FAILURE",
"[ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin",
- "Caused by: org.apache.maven.api.plugin.MojoException: Error assembling JAR",
- "Caused by: org.codehaus.plexus.archiver.jar.ManifestException: Invalid automatic module name: 'in-valid.name.with.new.keyword'"
+ "Caused by: org.apache.maven.api.plugin.MojoException: Invalid automatic module name: \"in-valid.name.with.new.keyword\"."
};
System.out.println("\nVerifying log snippets...");
diff --git a/src/it/MJAR-275-reproducible-module-info/invoker.properties b/src/it/MJAR-275-reproducible-module-info/invoker.properties
index 71eea457..452fbdb2 100644
--- a/src/it/MJAR-275-reproducible-module-info/invoker.properties
+++ b/src/it/MJAR-275-reproducible-module-info/invoker.properties
@@ -5,9 +5,9 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -15,7 +15,5 @@
# specific language governing permissions and limitations
# under the License.
-# NOTE: Requires Java 10+ to compile the module declaration for Java 9+,
-# this is due that compiling the module declaration generates a
-# module descriptor with the JDK version on it, making it unreproducible.
-invoker.java.version = 10+
+# The --date option needed for reproducible build is available only since Java 19.
+invoker.java.version = 19+
diff --git a/src/it/MJAR-292-detect-mjar/pom.xml b/src/it/MJAR-292-detect-mjar/pom.xml
index 9c0afd52..d473e74c 100644
--- a/src/it/MJAR-292-detect-mjar/pom.xml
+++ b/src/it/MJAR-292-detect-mjar/pom.xml
@@ -86,8 +86,8 @@
false
diff --git a/src/it/MJAR-292-disable-detect-mjar/pom.xml b/src/it/MJAR-292-disable-detect-mjar/pom.xml
index 1cc61ea9..c868bdce 100644
--- a/src/it/MJAR-292-disable-detect-mjar/pom.xml
+++ b/src/it/MJAR-292-disable-detect-mjar/pom.xml
@@ -84,6 +84,7 @@
myproject.HelloWorld
+ false
diff --git a/src/it/MJAR-30-include/verify.groovy b/src/it/MJAR-30-include/verify.groovy
index 28d3e5f3..1b0b838c 100644
--- a/src/it/MJAR-30-include/verify.groovy
+++ b/src/it/MJAR-30-include/verify.groovy
@@ -67,7 +67,7 @@ try
{
String artifactName = artifactNames[i];
if ( !contents.contains( artifactName ) )
- {
+ {
System.err.println( "Artifact[" + artifactName + "] not found in jar archive" );
return false;
}
diff --git a/src/it/MJAR-70-recreation/verify.bsh b/src/it/MJAR-70-recreation/verify.bsh
index 29b80ca2..25da0f97 100644
--- a/src/it/MJAR-70-recreation/verify.bsh
+++ b/src/it/MJAR-70-recreation/verify.bsh
@@ -58,7 +58,7 @@ if ( buildLog.exists() ) {
int jarPluginExecutions = 0;
String[] lines = buildLogContent.split( "\n" );
for ( String line : lines ) {
- if ( line.contains( "Building jar:" ) && line.contains( "MJAR-70-recreation-1.0-SNAPSHOT.jar" ) ) {
+ if ( line.contains( "Building JAR:" ) && line.contains( "MJAR-70-recreation-1.0-SNAPSHOT.jar" ) ) {
jarPluginExecutions++;
System.out.println( "Found JAR creation: " + line );
}
diff --git a/src/it/mjar-71-01/pom.xml b/src/it/mjar-71-01/pom.xml
index cb6b3195..d5b6de07 100644
--- a/src/it/mjar-71-01/pom.xml
+++ b/src/it/mjar-71-01/pom.xml
@@ -25,7 +25,7 @@ under the License.
1.0jarit-mjar-71
- Test that the default manifest is added by default if found under target/classes. Can also be overriden.
+ Test that the specified manifest is used.http://maven.apache.org
@@ -36,7 +36,7 @@ under the License.
@project.version@
- src/main/resources/META-INF/MANIFEST.MF
+ src/main/my-custom-dir/some-manifest.mf
diff --git a/src/it/mjar-71-01/src/main/resources/META-INF/MANIFEST.MF b/src/it/mjar-71-01/src/main/my-custom-dir/some-manifest.mf
similarity index 100%
rename from src/it/mjar-71-01/src/main/resources/META-INF/MANIFEST.MF
rename to src/it/mjar-71-01/src/main/my-custom-dir/some-manifest.mf
diff --git a/src/it/mjar-71-01/verify.groovy b/src/it/mjar-71-01/verify.groovy
index 97f78e88..60faf65e 100644
--- a/src/it/mjar-71-01/verify.groovy
+++ b/src/it/mjar-71-01/verify.groovy
@@ -51,14 +51,14 @@ try
// Only compare files
if ( entry.getName().equals( "META-INF/MANIFEST.MF" ) )
{
- String manifest = IOUtils.toString( jar.getInputStream ( entry ) );
- int index = manifest.indexOf( "Archiver-Version: foobar-1.23456" );
- if ( index <= 0 )
- {
- System.err.println( "MANIFEST doesn't contain: 'Archiver-Version: foobar-1.23456'" );
- return false;
- }
- return true;
+ String manifest = IOUtils.toString( jar.getInputStream ( entry ) );
+ int index = manifest.indexOf( "Archiver-Version: foobar-1.23456" );
+ if ( index <= 0 )
+ {
+ System.err.println( "MANIFEST doesn't contain: 'Archiver-Version: foobar-1.23456'" );
+ return false;
+ }
+ return true;
}
}
}
diff --git a/src/it/mjar-71-02/pom.xml b/src/it/mjar-71-02/pom.xml
index fa3adac7..3fd9b506 100644
--- a/src/it/mjar-71-02/pom.xml
+++ b/src/it/mjar-71-02/pom.xml
@@ -25,7 +25,7 @@ under the License.
1.0jarit-mjar-71-02
- Test that the default manifest is not added when found under target/classes but support is disabled.
+ Test that the manifest found under target/classes is automatically used.http://maven.apache.org
diff --git a/src/it/mjar-71-02/verify.groovy b/src/it/mjar-71-02/verify.groovy
index 33e40531..ffddfcbf 100644
--- a/src/it/mjar-71-02/verify.groovy
+++ b/src/it/mjar-71-02/verify.groovy
@@ -51,14 +51,14 @@ try
// Only compare files
if ( entry.getName().equals ( "META-INF/MANIFEST.MF" ) )
{
- String manifest = IOUtils.toString( jar.getInputStream ( entry ) );
- int index = manifest.indexOf( "Archiver-Version: foobar-1.23456" );
- if ( index > 0 )
- {
- System.err.println( "MANIFEST contains: 'Archiver-Version: foobar-1.23456', but shouldn't" );
- return false;
- }
- return true;
+ String manifest = IOUtils.toString( jar.getInputStream ( entry ) );
+ int index = manifest.indexOf( "Archiver-Version: foobar-1.23456" );
+ if ( index <= 0 )
+ {
+ System.err.println( "MANIFEST doesn't contain: 'Archiver-Version: foobar-1.23456'" );
+ return false;
+ }
+ return true;
}
}
}
diff --git a/src/it/multi-module/pom.xml b/src/it/multi-module/pom.xml
new file mode 100644
index 00000000..d4a781e9
--- /dev/null
+++ b/src/it/multi-module/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+ 4.1.0
+
+ org.apache.maven.plugins
+ multi-module
+ 1.0-SNAPSHOT
+ jar
+ Multi-module
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 4.0.0-beta-3
+
+
+
+
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ @project.version@
+
+
+
+ true
+ foo.bar/foo.MainFile
+
+
+
+
+
+
+
+ foo.bar
+ src/foo.bar/main/java
+
+
+ foo.bar.more
+ src/foo.bar.more/main/java
+
+
+
+
diff --git a/src/it/multi-module/src/foo.bar.more/main/java/module-info.java b/src/it/multi-module/src/foo.bar.more/main/java/module-info.java
new file mode 100644
index 00000000..778a3a4a
--- /dev/null
+++ b/src/it/multi-module/src/foo.bar.more/main/java/module-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module foo.bar.more {}
diff --git a/src/it/multi-module/src/foo.bar.more/main/java/more/MainFile.java b/src/it/multi-module/src/foo.bar.more/main/java/more/MainFile.java
new file mode 100644
index 00000000..d64f30a7
--- /dev/null
+++ b/src/it/multi-module/src/foo.bar.more/main/java/more/MainFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package more;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class MainFile {
+ public static void main(String[] args) {
+ System.out.println("MainFile of more");
+ }
+}
diff --git a/src/it/multi-module/src/foo.bar/main/java/foo/MainFile.java b/src/it/multi-module/src/foo.bar/main/java/foo/MainFile.java
new file mode 100644
index 00000000..502f2780
--- /dev/null
+++ b/src/it/multi-module/src/foo.bar/main/java/foo/MainFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package foo;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class MainFile {
+ public static void main(String[] args) {
+ System.out.println("MainFile");
+ }
+}
diff --git a/src/it/multi-module/src/foo.bar/main/java/module-info.java b/src/it/multi-module/src/foo.bar/main/java/module-info.java
new file mode 100644
index 00000000..38f61c0e
--- /dev/null
+++ b/src/it/multi-module/src/foo.bar/main/java/module-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module foo.bar {}
diff --git a/src/it/multi-module/verify.groovy b/src/it/multi-module/verify.groovy
new file mode 100644
index 00000000..df8756de
--- /dev/null
+++ b/src/it/multi-module/verify.groovy
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+File target = new File(basedir, "target");
+
+Set content = new HashSet<>();
+content.add("module-info.class")
+content.add("foo/MainFile.class")
+content.add("META-INF/MANIFEST.MF")
+content.add("META-INF/maven/org.apache.maven.plugins/multi-module/pom.xml")
+content.add("META-INF/maven/org.apache.maven.plugins/multi-module/pom.properties")
+verify(new File(target, "foo.bar-1.0-SNAPSHOT.jar"), content, "foo.MainFile")
+
+content.clear()
+content.add("module-info.class")
+content.add("more/MainFile.class")
+content.add("META-INF/MANIFEST.MF")
+content.add("META-INF/maven/org.apache.maven.plugins/multi-module/pom.xml")
+content.add("META-INF/maven/org.apache.maven.plugins/multi-module/pom.properties")
+verify(new File(target, "foo.bar.more-1.0-SNAPSHOT.jar"), content, null)
+
+void verify(File artifact, Set content, String mainClass)
+{
+ JarFile jar = new JarFile(artifact)
+ Enumeration jarEntries = jar.entries()
+ while (jarEntries.hasMoreElements())
+ {
+ JarEntry entry = (JarEntry) jarEntries.nextElement()
+ if (!entry.isDirectory())
+ {
+ String name = entry.getName()
+ assert content.remove(name) : "Missing entry: " + name
+ }
+ }
+ assert content.isEmpty() : "Unexpected entries: " + content
+
+ Attributes attributes = jar.getManifest().getMainAttributes()
+ assert attributes.get(Attributes.Name.MULTI_RELEASE) == null
+ assert Objects.equals(mainClass, attributes.get(Attributes.Name.MAIN_CLASS))
+
+ jar.close();
+}
diff --git a/src/it/multirelease-with-modules/pom.xml b/src/it/multirelease-with-modules/pom.xml
new file mode 100644
index 00000000..5f02bbbe
--- /dev/null
+++ b/src/it/multirelease-with-modules/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ 4.1.0
+
+ org.apache.maven.plugins
+ multirelease-with-modules
+ 1.0-SNAPSHOT
+ jar
+ Multirelease with modules
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 4.0.0-beta-3
+
+
+
+
+ 17
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 3.1.0
+
+
+ move
+
+ run
+
+ prepare-package
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ @project.version@
+
+
+
+ true
+ true
+ foo.bar/foo.MainFile
+
+
+
+
+
+
+
+ foo.bar
+ src/foo.bar/main/java
+ 15
+
+
+ foo.bar
+ src/foo.bar/main/java_16
+ 16
+
+
+ foo.bar.more
+ src/foo.bar.more/main/java
+ 15
+
+
+ foo.bar.more
+ src/foo.bar.more/main/java_16
+ 16
+
+
+
+
diff --git a/src/it/multirelease-with-modules/src/foo.bar.more/main/java/module-info.java b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/module-info.java
new file mode 100644
index 00000000..778a3a4a
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/module-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module foo.bar.more {}
diff --git a/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/MainFile.java b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/MainFile.java
new file mode 100644
index 00000000..d64f30a7
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/MainFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package more;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class MainFile {
+ public static void main(String[] args) {
+ System.out.println("MainFile of more");
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/OtherFile.java b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/OtherFile.java
new file mode 100644
index 00000000..54e29b3c
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar.more/main/java/more/OtherFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package more;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class OtherFile {
+ public static void main(String[] args) {
+ System.out.println("OtherFile of more");
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar.more/main/java_16/more/OtherFile.java b/src/it/multirelease-with-modules/src/foo.bar.more/main/java_16/more/OtherFile.java
new file mode 100644
index 00000000..4b21485e
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar.more/main/java_16/more/OtherFile.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package more;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class OtherFile {
+ public static void main(String[] args) {
+ System.out.println("OtherFile of more on Java 16");
+ MainFile.main(args); // Verify that we have access to the base version.
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/MainFile.java b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/MainFile.java
new file mode 100644
index 00000000..502f2780
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/MainFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package foo;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class MainFile {
+ public static void main(String[] args) {
+ System.out.println("MainFile");
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/OtherFile.java b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/OtherFile.java
new file mode 100644
index 00000000..472210e1
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/OtherFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package foo;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class OtherFile {
+ public static void main(String[] args) {
+ System.out.println("OtherFile");
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/YetAnotherFile.java b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/YetAnotherFile.java
new file mode 100644
index 00000000..ab5f9009
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar/main/java/foo/YetAnotherFile.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package foo;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class YetAnotherFile {
+ public static void main(String[] args) {
+ System.out.println("YetAnotherFile");
+ }
+}
diff --git a/src/it/multirelease-with-modules/src/foo.bar/main/java/module-info.java b/src/it/multirelease-with-modules/src/foo.bar/main/java/module-info.java
new file mode 100644
index 00000000..38f61c0e
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar/main/java/module-info.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+module foo.bar {}
diff --git a/src/it/multirelease-with-modules/src/foo.bar/main/java_16/foo/OtherFile.java b/src/it/multirelease-with-modules/src/foo.bar/main/java_16/foo/OtherFile.java
new file mode 100644
index 00000000..cbfa0b98
--- /dev/null
+++ b/src/it/multirelease-with-modules/src/foo.bar/main/java_16/foo/OtherFile.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package foo;
+
+/**
+ * Test {@code <Source>}.
+ * Another {@code <Source>}.
+ */
+public class OtherFile {
+ public static void main(String[] args) {
+ System.out.println("OtherFile on Java 16");
+ MainFile.main(args); // Verify that we have access to the base version.
+ }
+
+ static void requireJava16() {
+ System.out.println("Method available only on Java 16+");
+ }
+}
diff --git a/src/it/multirelease-with-modules/verify.groovy b/src/it/multirelease-with-modules/verify.groovy
new file mode 100644
index 00000000..d62d91f2
--- /dev/null
+++ b/src/it/multirelease-with-modules/verify.groovy
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+File target = new File(basedir, "target");
+
+Set content = new HashSet<>();
+content.add("module-info.class")
+content.add("foo/MainFile.class")
+content.add("foo/OtherFile.class")
+content.add("foo/YetAnotherFile.class")
+content.add("META-INF/versions/16/foo/OtherFile.class")
+content.add("META-INF/MANIFEST.MF")
+content.add("META-INF/maven/org.apache.maven.plugins/multirelease-with-modules/pom.xml")
+content.add("META-INF/maven/org.apache.maven.plugins/multirelease-with-modules/pom.properties")
+verify(new File(target, "foo.bar-1.0-SNAPSHOT.jar"), content, "foo.MainFile")
+
+content.clear()
+content.add("module-info.class")
+content.add("more/MainFile.class")
+content.add("more/OtherFile.class")
+content.add("META-INF/versions/16/more/OtherFile.class")
+content.add("META-INF/MANIFEST.MF")
+content.add("META-INF/maven/org.apache.maven.plugins/multirelease-with-modules/pom.xml")
+content.add("META-INF/maven/org.apache.maven.plugins/multirelease-with-modules/pom.properties")
+verify(new File(target, "foo.bar.more-1.0-SNAPSHOT.jar"), content, null)
+
+void verify(File artifact, Set content, String mainClass)
+{
+ JarFile jar = new JarFile(artifact)
+ Enumeration jarEntries = jar.entries()
+ while (jarEntries.hasMoreElements())
+ {
+ JarEntry entry = (JarEntry) jarEntries.nextElement()
+ if (!entry.isDirectory())
+ {
+ String name = entry.getName()
+ assert content.remove(name) : "Missing entry: " + name
+ }
+ }
+ assert content.isEmpty() : "Unexpected entries: " + content
+
+ Attributes attributes = jar.getManifest().getMainAttributes()
+ assert Objects.equals("true", attributes.get(Attributes.Name.MULTI_RELEASE))
+ assert Objects.equals(mainClass, attributes.get(Attributes.Name.MAIN_CLASS))
+
+ jar.close();
+}
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index 10e00afb..df93deff 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -18,44 +18,46 @@
*/
package org.apache.maven.plugins.jar;
-import java.io.File;
+import javax.lang.model.SourceVersion;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.jar.Attributes;
-import java.util.stream.Stream;
+import java.util.spi.ToolProvider;
+import org.apache.maven.api.PathScope;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
import org.apache.maven.api.Session;
+import org.apache.maven.api.Type;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.plugin.Log;
import org.apache.maven.api.plugin.MojoException;
import org.apache.maven.api.plugin.annotations.Parameter;
+import org.apache.maven.api.services.PathMatcherFactory;
import org.apache.maven.api.services.ProjectManager;
import org.apache.maven.shared.archiver.MavenArchiveConfiguration;
import org.apache.maven.shared.archiver.MavenArchiver;
-import org.apache.maven.shared.archiver.MavenArchiverException;
-import org.apache.maven.shared.model.fileset.FileSet;
-import org.apache.maven.shared.model.fileset.util.FileSetManager;
-import org.codehaus.plexus.archiver.Archiver;
-import org.codehaus.plexus.archiver.jar.JarArchiver;
/**
* Base class for creating a JAR file from project classes.
*
* @author Emmanuel Venisse
+ * @author Martin Desruisseaux
*/
public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Mojo {
-
- private static final String[] DEFAULT_EXCLUDES = new String[] {"**/package.html"};
-
- private static final String[] DEFAULT_INCLUDES = new String[] {"**/**"};
-
- private static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class";
+ /**
+ * Identifier of the tool to use. This identifier shall match the identifier of a tool
+ * registered as a {@link ToolProvider}. By default, the {@code "jar"} tool is used.
+ *
+ * @since 4.0.0-beta-2
+ */
+ @Parameter(defaultValue = "jar", required = true)
+ protected String toolId;
/**
* List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
@@ -72,23 +74,20 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
protected String[] excludes;
/**
- * Directory containing the generated JAR.
+ * Directory containing the generated JAR files.
*/
@Parameter(defaultValue = "${project.build.directory}", required = true)
protected Path outputDirectory;
/**
- * Name of the generated JAR.
+ * Name of the generated JAR file.
+ * The default value is {@code "${project.build.finalName}"},
+ * which itself defaults to {@code "${artifactId}-${version}"}.
+ * Ignored if the Maven sub-project to archive uses module hierarchy.
*/
@Parameter(defaultValue = "${project.build.finalName}", readonly = true)
protected String finalName;
- /**
- * The JAR archiver.
- */
- @Inject
- protected Map archivers;
-
/**
* The Maven project.
*/
@@ -108,13 +107,22 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
@Parameter
protected MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
+ /**
+ * The service to use for attaching the artifacts produced by this plugin.
+ */
@Inject
protected ProjectManager projectManager;
/**
- * Require the jar plugin to build a new JAR even if none of the contents appear to have changed.
- * By default, this plugin looks to see if the output JAR exists and inputs have not changed.
- * If these conditions are true, the plugin skips creation of the JAR file.
+ * The service to use for creating include and exclude filters.
+ */
+ @Inject
+ private PathMatcherFactory matcherFactory;
+
+ /**
+ * Require the jar plugin to build new JAR files even if none of the contents appear to have changed.
+ * By default, this plugin looks to see if the output JAR files exist and inputs have not changed.
+ * If these conditions are true, the plugin skips creation of the JAR files.
* This does not work when other plugins, like the maven-shade-plugin, are configured to post-process the JAR.
* This plugin can not detect the post-processing, and so leaves the post-processed JAR file in place.
* This can lead to failures when those plugins do not expect to find their own output as an input.
@@ -136,8 +144,11 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* Timestamp for reproducible output archive entries.
* This is either formatted as ISO 8601 extended offset date-time
* (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset '2019-10-05T20:37:42+06:00'),
- * or as an integer representing seconds since the epoch
- * (like SOURCE_DATE_EPOCH).
+ * or as an integer representing seconds since the Java epoch (January 1st, 1970).
+ * If not configured or disabled,
+ * the SOURCE_DATE_EPOCH
+ * environment variable is used as a fallback value,
+ * to ease forcing Reproducible Build externally when the build has not enabled it natively in POM.
*
* @since 3.2.0
*/
@@ -145,9 +156,12 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
protected String outputTimestamp;
/**
- * Whether to detect multi-release JAR files.
- * If the JAR contains the {@code META-INF/versions} directory it will be detected as a multi-release JAR file
- * ("MRJAR"), adding the {@code Multi-Release: true} attribute to the main section of the JAR {@code MANIFEST.MF}.
+ * Whether to detect multi-release JAR files.
+ * If the JAR contains the {@code META-INF/versions} directory it will be detected as a multi-release JAR file,
+ * adding the {@code Multi-Release: true} attribute to the main section of the JAR {@code MANIFEST.MF} entry.
+ * In addition, the class files in {@code META-INF/versions} will be checked for API compatibility
+ * with the class files in the base version. If this flag is {@code false}, then the {@code META-INF/versions}
+ * directories are included without processing.
*
* @since 3.4.0
*/
@@ -166,7 +180,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
protected AbstractJarMojo() {}
/**
- * Specifies whether to attach the jar to the project.
+ * Specifies whether to attach the JAR file(s) to the project.
*
* @since 4.0.0-beta-2
*/
@@ -179,23 +193,7 @@ protected AbstractJarMojo() {}
protected abstract Path getClassesDirectory();
/**
- * Return the {@linkplain #project Maven project}.
- *
- * @return the Maven project
- */
- protected final Project getProject() {
- return project;
- }
-
- /**
- * {@return the MOJO logger}
- */
- protected final Log getLog() {
- return log;
- }
-
- /**
- * {@return the classifier of the JAR file to produce}
+ * {@return the classifier of the JAR file(s) to produce}
* This is usually null or empty for the main artifact, or {@code "tests"} for the JAR file of test code.
*/
protected abstract String getClassifier();
@@ -207,167 +205,199 @@ protected final Log getLog() {
protected abstract String getType();
/**
- * Returns the JAR file to generate, based on an optional classifier.
+ * {@return the scope of dependencies}
+ * It should be {@link PathScope#MAIN_COMPILE} or {@link PathScope#TEST_COMPILE}.
+ * Note that we use compile scope rather than runtime scope because dependencies
+ * cannot appear in {@code requires} statement if they didn't had compile scope.
+ */
+ protected abstract PathScope getDependencyScope();
+
+ /**
+ * {@return the JAR tool to use for archiving the code}
*
- * @param basedir the output directory
- * @param resultFinalName the name of the JAR file
- * @param classifier an optional classifier
- * @return the file to generate
+ * @throws MojoException if no JAR tool was found
+ *
+ * @since 4.0.0-beta-2
*/
- protected Path getJarFile(Path basedir, String resultFinalName, String classifier) {
- Objects.requireNonNull(basedir, "basedir is not allowed to be null");
- Objects.requireNonNull(resultFinalName, "finalName is not allowed to be null");
- String fileName = resultFinalName + (hasClassifier(classifier) ? '-' + classifier : "") + ".jar";
- return basedir.resolve(fileName);
+ protected ToolProvider getJarTool() throws MojoException {
+ return ToolProvider.findFirst(toolId).orElseThrow(() -> new MojoException("No such \"" + toolId + "\" tool."));
}
/**
- * Generates the JAR.
+ * Returns whether the specified Java version is supported.
*
- * @return the path to the created archive file
- * @throws MojoException in case of an error
+ * @param release name of an {@link SourceVersion} enumeration constant
+ * @return whether the current environment support that version
*/
- public Path createArchive() throws MojoException {
- Path basedir = outputDirectory != null
- ? outputDirectory
- : Path.of(project.getBuild().getDirectory());
- String resultFinalName =
- finalName != null ? finalName : project.getBuild().getFinalName();
- Path jarFile = getJarFile(basedir, resultFinalName, getClassifier());
-
- FileSetManager fileSetManager = new FileSetManager();
- FileSet jarContentFileSet = new FileSet();
- jarContentFileSet.setDirectory(getClassesDirectory().toAbsolutePath().toString());
- jarContentFileSet.setIncludes(Arrays.asList(getIncludes()));
- jarContentFileSet.setExcludes(Arrays.asList(getExcludes()));
-
- String[] includedFiles = fileSetManager.getIncludedFiles(jarContentFileSet);
-
- if (detectMultiReleaseJar
- && Arrays.stream(includedFiles)
- .anyMatch(
- p -> p.startsWith("META-INF" + File.separatorChar + "versions" + File.separatorChar))) {
- getLog().debug("Adding 'Multi-Release: true' manifest entry.");
- archive.addManifestEntry(Attributes.Name.MULTI_RELEASE.toString(), "true");
+ private static boolean isSupported(String release) {
+ try {
+ return SourceVersion.latestSupported().compareTo(SourceVersion.valueOf(release)) >= 0;
+ } catch (IllegalArgumentException e) {
+ return false;
}
+ }
- // May give false positives if the files is named as module descriptor
- // but is not in the root of the archive or in the versioned area
- // (and hence not actually a module descriptor).
- // That is fine since the modular Jar archiver will gracefully
- // handle such case.
- // And also such case is unlikely to happen as file ending
- // with "module-info.class" is unlikely to be included in Jar file
- // unless it is a module descriptor.
- boolean containsModuleDescriptor =
- Arrays.stream(includedFiles).anyMatch(p -> p.endsWith(MODULE_DESCRIPTOR_FILE_NAME));
-
- String archiverName = containsModuleDescriptor ? "mjar" : "jar";
+ /**
+ * Returns the output time stamp or, as a fallback, the {@code SOURCE_DATE_EPOCH} environment variable.
+ * If the time stamp is expressed in seconds, it is converted to ISO 8601 format. Otherwise it is returned as-is.
+ *
+ * @return the time stamp in presumed ISO 8601 format, or {@code null} if none
+ *
+ * @since 4.0.0-beta-2
+ */
+ protected String getOutputTimestamp() {
+ String time = nullIfAbsent(outputTimestamp);
+ if (time == null) {
+ time = nullIfAbsent(System.getenv("SOURCE_DATE_EPOCH"));
+ if (time == null) {
+ return null;
+ }
+ }
+ if (!isSupported("RELEASE_19")) {
+ log.warn("Reproducible build requires Java 19 or later.");
+ return null;
+ }
+ for (int i = time.length(); --i >= 0; ) {
+ char c = time.charAt(i);
+ if ((c < '0' || c > '9') && (i != 0 || c != '-')) {
+ return time;
+ }
+ }
+ return Instant.ofEpochSecond(Long.parseLong(time)).toString();
+ }
- MavenArchiver archiver = new MavenArchiver();
- archiver.setCreatedBy("Maven JAR Plugin", "org.apache.maven.plugins", "maven-jar-plugin");
- archiver.setBuildJdkSpecDefaultEntry(archive.getManifest().isAddBuildEnvironmentEntries());
- archiver.setArchiver((JarArchiver) archivers.get(archiverName));
- archiver.setOutputFile(jarFile.toFile());
+ /**
+ * {@return the patterns of files to include, or an empty list if no include pattern was specified}
+ */
+ protected List getIncludes() {
+ return asList(includes);
+ }
- // configure for Reproducible Builds based on outputTimestamp value
- archiver.configureReproducibleBuild(outputTimestamp);
+ /**
+ * {@return the patterns of files to exclude, or an empty list if no exclude pattern was specified}
+ */
+ protected List getExcludes() {
+ return asList(excludes);
+ }
- archive.setForced(forceCreation);
+ /**
+ * Returns the given elements as a list if non-null.
+ *
+ * @param elements the elements, or {@code null}
+ * @return the elements as a list, or {@code null} if the given array was null
+ */
+ private static List asList(String[] elements) {
+ return (elements == null) ? List.of() : Arrays.asList(elements);
+ }
- try {
- Path contentDirectory = getClassesDirectory();
- if (!Files.exists(contentDirectory)) {
- if (!forceCreation) {
- getLog().warn("JAR will be empty - no content was marked for inclusion!");
- }
- } else {
- archiver.getArchiver().addDirectory(contentDirectory.toFile(), getIncludes(), getExcludes());
+ /**
+ * Generates the JAR files.
+ * Map keys are module names or {@code null} if the project does not use module hierarchy.
+ * Values are (type, path) pairs associated with each module where
+ * type is {@code "pom"}, {@code "jar"} or {@code "test-jar"} and path
+ * is the path to the POM or JAR file.
+ *
+ *
Note that a null key does not necessarily means that the JAR is not modular.
+ * It only means that the project was not compiled with module hierarchy,
+ * i.e. {@code target/classes/} subdirectories having module names.
+ * A project can be compiled with package hierarchy and still be modular.
+ *
+ * @return the paths to the created archive files
+ * @throws IOException if an error occurred while walking the file tree
+ * @throws MojoException if an error occurred while writing a JAR file
+ */
+ public Map> createArchives() throws IOException, MojoException {
+ final Path classesDirectory = getClassesDirectory();
+ final boolean notExists = Files.notExists(classesDirectory);
+ if (notExists) {
+ if (forceCreation) {
+ log.warn("No JAR created because no content was marked for inclusion.");
+ }
+ if (skipIfEmpty) {
+ log.info(String.format("Skipping packaging of the %s.", getType()));
+ return Map.of();
}
-
- archiver.createArchive(session, project, archive);
-
- return jarFile;
- } catch (Exception e) {
- // TODO: improve error handling
- throw new MojoException("Error assembling JAR", e);
}
+ archive.setForced(forceCreation);
+ // TODO: we want a null manifest if there is no configuration.
+ final var archiver = new MavenArchiver();
+ archiver.setCreatedBy("Maven JAR Plugin", "org.apache.maven.plugins", "maven-jar-plugin");
+ archiver.setBuildJdkSpecDefaultEntry(archive.getManifest().isAddBuildEnvironmentEntries());
+ var executor = new ToolExecutor(this, archiver.getManifest(session, project, archive), archive);
+ var files = new FileCollector(this, executor, classesDirectory, matcherFactory);
+ if (!notExists) {
+ Files.walkFileTree(classesDirectory, files);
+ }
+ files.prune(skipIfEmpty);
+ List moduleRoots = files.getModuleHierarchyRoots();
+ if (!moduleRoots.isEmpty()) {
+ executor.pomDerivation = new PomDerivation(this, moduleRoots);
+ }
+ return executor.writeAllJARs(files);
}
/**
- * Generates the JAR.
+ * Generates the JAR file, then attaches the artifact.
*
* @throws MojoException in case of an error
*/
@Override
+ @SuppressWarnings("UseSpecificCatch")
public void execute() throws MojoException {
- if (skipIfEmpty && isEmpty(getClassesDirectory())) {
- getLog().info(String.format("Skipping packaging of the %s.", getType()));
- } else {
- Path jarFile = createArchive();
-
- if (attach) {
- ProducedArtifact artifact;
- String classifier = getClassifier();
- if (hasClassifier(classifier)) {
- artifact = session.createProducedArtifact(
- project.getGroupId(),
- project.getArtifactId(),
- project.getVersion(),
- classifier,
- null,
- getType());
- } else {
- if (projectHasAlreadySetAnArtifact()) {
- throw new MojoException("You have to use a classifier "
- + "to attach supplemental artifacts to the project instead of replacing them.");
+ final Map> artifactFiles;
+ try {
+ artifactFiles = createArchives();
+ } catch (MojoException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new MojoException("Error while assembling the JAR file.", e);
+ }
+ if (artifactFiles.isEmpty()) {
+ // Message already logged by `createArchives()`.
+ return;
+ }
+ if (attach) {
+ final String classifier = nullIfAbsent(getClassifier());
+ for (Map.Entry> entry : artifactFiles.entrySet()) {
+ String moduleName = entry.getKey();
+ for (Map.Entry path : entry.getValue().entrySet()) {
+ String type = path.getKey();
+ ProducedArtifact artifact;
+ if (moduleName == null && classifier == null && Type.JAR.equals(type)) {
+ if (projectHasAlreadySetAnArtifact()) {
+ throw new MojoException("You have to use a classifier "
+ + "to attach supplemental artifacts to the project instead of replacing them.");
+ }
+ artifact = project.getMainArtifact().orElseThrow();
+ } else {
+ artifact = session.createProducedArtifact(
+ project.getGroupId(),
+ (moduleName != null) ? moduleName : project.getArtifactId(),
+ project.getVersion(),
+ classifier,
+ null,
+ type);
}
- artifact = project.getMainArtifact().get();
+ projectManager.attachArtifact(project, artifact, path.getValue());
}
- projectManager.attachArtifact(project, artifact, jarFile);
- } else {
- getLog().debug("Skipping attachment of the " + getType() + " artifact to the project.");
}
+ } else {
+ log.debug("Skipping attachment of the " + getType() + " artifact to the project.");
}
}
- private static boolean isEmpty(Path directory) {
- if (!Files.isDirectory(directory)) {
- return true;
- }
- try (Stream children = Files.list(directory)) {
- return children.findAny().isEmpty();
- } catch (IOException e) {
- throw new MavenArchiverException("Unable to access directory", e);
- }
- }
-
+ /**
+ * Verifies whether the main artifact is already set.
+ * This verification does not apply for module hierarchy, where more than one artifact is produced.
+ */
private boolean projectHasAlreadySetAnArtifact() {
- Path path = projectManager.getPath(project).orElse(null);
- return path != null && Files.isRegularFile(path);
+ return projectManager.getPath(project).filter(Files::isRegularFile).isPresent();
}
/**
- * Return {@code true} if the classifier is not {@code null} and contains something other than white spaces.
- *
- * @param classifier the classifier to verify
- * @return {@code true} if the classifier is set
+ * Returns the given value if non-null, non-empty and non-blank, or {@code null} otherwise.
*/
- private static boolean hasClassifier(String classifier) {
- return classifier != null && !classifier.isBlank();
- }
-
- private String[] getIncludes() {
- if (includes != null && includes.length > 0) {
- return includes;
- }
- return DEFAULT_INCLUDES;
- }
-
- private String[] getExcludes() {
- if (excludes != null && excludes.length > 0) {
- return excludes;
- }
- return DEFAULT_EXCLUDES;
+ static String nullIfAbsent(String value) {
+ return (value == null || value.isBlank()) ? null : value;
}
}
diff --git a/src/main/java/org/apache/maven/plugins/jar/Archive.java b/src/main/java/org/apache/maven/plugins/jar/Archive.java
new file mode 100644
index 00000000..c17812ef
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/jar/Archive.java
@@ -0,0 +1,605 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.jar;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.apache.maven.api.Type;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
+import org.apache.maven.api.plugin.Log;
+import org.apache.maven.api.plugin.MojoException;
+
+/**
+ * Files or root directories to archive for a single module.
+ * A single instance of {@code Archive} may contain many directories for different target Java releases.
+ * Many instances of {@code Archive} may exist when archiving a multi-modules project.
+ */
+final class Archive {
+ /**
+ * Path to the POM file generated for this archive, or {@code null} if none.
+ * This is non-null only if module source hierarchy is used, in which case the dependencies
+ * declared in this file are the intersection of the project dependencies and the content of
+ * the {@code module-info.class} file.
+ */
+ @Nullable
+ Path pomFile;
+
+ /**
+ * The JAR file to create. May be an existing file,
+ * in which case the file creation may be skipped if the file is still up-to-date.
+ */
+ @Nonnull
+ final Path jarFile;
+
+ /**
+ * A helper class for checking whether an existing JAR file is still up-to-date.
+ * This is null if there is no existing JAR file, or if we determined that the file is outdated.
+ */
+ private TimestampCheck existingJAR;
+
+ /**
+ * Name of the module being archived when the project is using module hierarchy.
+ * This is {@code null} if the project is using package hierarchy, either because it is a classical
+ * class-path project or because it is a single module compiled without using the module hierarchy.
+ * When using module source hierarchy, {@code javac} guarantees that the module name in the output
+ * directory is the name of the parent directory of {@code module-info.class}.
+ */
+ @Nullable
+ final String moduleName;
+
+ /**
+ * Path to {@code META-INF/MANIFEST.MF}, or {@code null} if none. The manifest file
+ * should be included by the {@code --manifest} option instead of as an ordinary file.
+ */
+ @Nullable
+ private Path manifest;
+
+ /**
+ * The Maven generated {@code pom.xml} and {@code pom.properties} files, or {@code null} if none.
+ * This first item shall be the base directory where the files are located.
+ */
+ @Nullable
+ List mavenFiles;
+
+ /**
+ * Fully-qualified name of the main class, or {@code null} if none.
+ * This is the value to provide to the {@code --main-class} option.
+ */
+ private String mainClass;
+
+ /**
+ * Files or root directories to store in the JAR file for each target Java release
+ * other than the base release. Keys are the target Java release with {@code null} for the base
+ * release.
+ */
+ @Nonnull
+ private final NavigableMap filesetForRelease;
+
+ /**
+ * Files or root directories to archive for a single target Java release of a single module.
+ * The {@link Archive} enclosing shall contain at least one instance of {@code FileSet} for
+ * the base release, and an arbitrary amount of other instances for other target releases.
+ */
+ final class FileSet {
+ /**
+ * The root directory of all files or directories to archive.
+ * This is the value to pass to the {@code -C} tool option.
+ */
+ @Nonnull
+ final Path directory;
+
+ /**
+ * The files or directories to include in the JAR file.
+ * May be absolute paths or paths relative to {@link #directory}.
+ */
+ @Nonnull
+ final List files;
+
+ /**
+ * Creates an initially empty set of files or directories for a specific target Java release.
+ *
+ * @param directory the base directory of the files or directories to archive
+ */
+ private FileSet(Path directory) {
+ this.directory = directory;
+ this.files = new ArrayList<>();
+ }
+
+ /**
+ * Discards all files in this file set, normally because those files are not in any module.
+ * This method returns a common parent directory for all the files that were discarded.
+ * The caller should use that common directory for logging a warning message.
+ *
+ * @param base base directory found by previous invocations of this method, or {@code null} if none
+ * @return common directory of discarded files
+ */
+ private Path discardAllFiles(Path base) {
+ for (Path file : files) {
+ file = directory.resolve(file);
+ if (base == null) {
+ base = file.getParent();
+ } else {
+ while (!file.startsWith(base)) {
+ base = base.getParent();
+ if (base == null) {
+ break;
+ }
+ }
+ }
+ }
+ files.clear();
+ return base;
+ }
+
+ /**
+ * Adds the given path to the list of files or directories to archive.
+ * This method may store a relative path instead of the absolute path.
+ *
+ * @param item a file or directory to archive
+ * @param attributes the file's basic attributes
+ * @param isDirectory whether the file is a directory
+ * @throws IllegalArgumentException if the given path cannot be made relative to the base directory
+ */
+ void add(Path item, BasicFileAttributes attributes, boolean isDirectory) {
+ TimestampCheck tc = existingJAR;
+ if (tc != null && tc.isUpdated(item, attributes, isDirectory)) {
+ existingJAR = null; // Signal that the existing file is outdated.
+ }
+ if (files.isEmpty()) {
+ /*
+ * In our tests, it seems that the first file after the "-C" option needs to be relative
+ * to the directory given to "-C" and all other files need to be absolute. This behavior
+ * does not seem to be documented, but we couldn't get the "jar" tool to work otherwise
+ * (except by repeating "-C" before each file).
+ */
+ item = directory.relativize(item);
+ }
+ files.add(item);
+ }
+
+ /**
+ * Adds to the given list the arguments to provide to the "jar" tool for this version.
+ * Elements added to the list shall be instances of {@link String} or {@link Path}.
+ *
+ * @param addTo the list where to add the arguments as {@link String} or {@link Path} instances
+ * @param version the target Java release, or {@code null} for the base version of the JAR file
+ */
+ private void arguments(List
*/
@Nullable
- private final List exclusion;
+ private final List excludedFiles;
+
+ /**
+ * Directories to exclude. This field serves the same purpose as {@link #excludedFiles},
+ * but where the sources a directories instead of files.
+ */
+ @Nullable
+ private final List excludedDirectories;
/**
* Files found in the output directory when package hierarchy is used.
@@ -178,9 +185,11 @@ final class FileCollector extends SimpleFileVisitor {
fileMatcher = matcherFactory.createPathMatcher(directory, mojo.getIncludes(), mojo.getExcludes(), false);
directoryMatcher = matcherFactory.deriveDirectoryMatcher(fileMatcher);
if (matcherFactory.isIncludesAll(fileMatcher) && matcherFactory.isIncludesAll(directoryMatcher)) {
- exclusion = null;
+ excludedFiles = null;
+ excludedDirectories = null;
} else {
- exclusion = new ArrayList<>();
+ excludedFiles = new ArrayList<>();
+ excludedDirectories = new ArrayList<>();
}
packageHierarchy = context.newArchive(null, null, directory);
moduleHierarchy = new LinkedHashMap<>();
@@ -249,7 +258,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
role = DirectoryRole.ROOT;
} else {
if (!directoryMatcher.matches(directory)) {
- exclusion.add(directory); // Cannot be null if excluded directories may exist.
+ excludedDirectories.add(directory); // Cannot be null if excluded directories may exist.
return FileVisitResult.SKIP_SUBTREE;
}
checkForManifest = false;
@@ -343,7 +352,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
*/
if (role == DirectoryRole.RESOURCES) {
currentFilesToArchive.add(directory, attributes, true);
- if (exclusion == null) {
+ if (excludedFiles == null) {
/*
* Since we are skipping the whole directory, `postVisitDirectory(…)` will not be invoked.
* We must reset `currentFilesToArchive` and `currentTargetVersion` by an explicit call.
@@ -423,7 +432,7 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
currentFilesToArchive.add(file, attributes, false);
}
} else {
- exclusion.add(file); // Cannot be null if excluded files may exist.
+ excludedFiles.add(file); // Cannot be null if excluded files may exist.
}
return FileVisitResult.CONTINUE;
}
@@ -469,6 +478,17 @@ Path handleOrphanFiles() {
return packageHierarchy.discardAllFiles();
}
+ /**
+ * Returns the object in charge of moving excluded files to a temporary directory, or {@code null} if none.
+ */
+ private ExcludedFiles exclusion() throws IOException {
+ if ((excludedFiles == null || excludedFiles.isEmpty())
+ && (excludedDirectories == null || excludedDirectories.isEmpty())) {
+ return null;
+ }
+ return new ExcludedFiles(rootDirectory, excludedFiles, excludedDirectories);
+ }
+
/**
* Writes all JAR files.
* If the project is multi-module, then this method returns the path to the generated parent POM file.
@@ -481,10 +501,9 @@ Path handleOrphanFiles() {
* @throws IOException if an error occurred while reading or writing a manifest file
*/
Path writeAllJARs(final ToolExecutor executor) throws IOException {
- try (ExcludedFiles moved =
- (exclusion == null || exclusion.isEmpty()) ? null : new ExcludedFiles(rootDirectory, exclusion)) {
- if (moved != null) {
- moved.move();
+ try (ExcludedFiles exclusion = exclusion()) {
+ if (exclusion != null) {
+ exclusion.move();
}
for (Archive module : moduleHierarchy.values()) {
executor.writeSingleJAR(this, module);
diff --git a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
index 9920e8d4..1cb6acb2 100644
--- a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
+++ b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
@@ -167,13 +167,14 @@ final class PomDerivation {
builtModules = new HashMap<>(moduleRoots.size()); // TODO: use newHashMap with JDK19.
for (Path root : moduleRoots) {
root = root.toRealPath();
- ModuleDescriptor descriptor = fromURI.get(root.toUri()).descriptor();
- if (descriptor != null) {
+ ModuleReference reference = fromURI.get(root.toUri());
+ if (reference != null) { // Actually a null value would be a bug, but be tolerant.
+ String moduleName = reference.descriptor().name();
builtModules.put(
- descriptor.name(),
+ moduleName,
Dependency.newBuilder()
.groupId(projectModel.getGroupId())
- .artifactId(descriptor.name())
+ .artifactId(moduleName)
.version(projectModel.getVersion())
.type(Type.MODULAR_JAR)
.build());
From 245dcb22131f553bf6e92c0fee7565b48d4d56ab Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Thu, 27 Aug 2026 12:55:35 +0200
Subject: [PATCH 20/25] Execute `jar --validate` only if requested.
https://github.com/apache/maven-jar-plugin/issues/484
---
.../maven/plugins/jar/AbstractJarMojo.java | 50 ++++++++++++++++---
.../org/apache/maven/plugins/jar/Archive.java | 28 +++++++----
.../maven/plugins/jar/ToolExecutor.java | 9 +++-
3 files changed, 69 insertions(+), 18 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index 786000f5..62bde4bf 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -25,6 +25,7 @@
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.spi.ToolProvider;
@@ -169,6 +170,20 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
@Parameter(property = "maven.jar.detectMultiReleaseJar", defaultValue = "true")
protected boolean detectMultiReleaseJar;
+ /**
+ * Whether to validate the JAR files after their creation.
+ * If {@code true}, the {@code jar} tool is executed a second time with
+ * the {@code --validate} option for each created JAR file.
+ * This verification may slow down the build if these files are large.
+ * If {@code false} (the default), there is no second {@code jar} execution.
+ * If {@code auto}, the Maven JAR Plugin will decide itself whether
+ * to execute {@code jar --validate} based on heuristic rules.
+ *
+ * @since 4.0.0-beta-2
+ */
+ @Parameter(defaultValue = "false")
+ protected String validate;
+
/**
* The MOJO logger.
*/
@@ -268,6 +283,16 @@ protected List getExcludes() {
return asList(excludes);
}
+ /**
+ * Returns the given elements as a list if non-null.
+ *
+ * @param elements the elements, or {@code null}
+ * @return the elements as a list, or {@code null} if the given array was null
+ */
+ private static List asList(String[] elements) {
+ return (elements == null) ? List.of() : Arrays.asList(elements);
+ }
+
/**
* Returns the output directory and ensures that the directory exists.
* The returned directory will be either {@link #outputDirectory} if non-null,
@@ -292,13 +317,26 @@ protected Path getOutputDirectory() throws IOException {
}
/**
- * Returns the given elements as a list if non-null.
- *
- * @param elements the elements, or {@code null}
- * @return the elements as a list, or {@code null} if the given array was null
+ * Returns the value of the {@link #validate} parameter, or {@code null} if {@code "auto"}.
*/
- private static List asList(String[] elements) {
- return (elements == null) ? List.of() : Arrays.asList(elements);
+ final Boolean getValidate() {
+ String value = validate;
+ if (value != null) {
+ value = value.strip();
+ if (!value.isEmpty()) {
+ switch (value.toLowerCase(Locale.ENGLISH)) {
+ default:
+ throw new MojoException("The 'validate' parameter value cannot be \"" + value + "\".");
+ case "auto":
+ return null;
+ case "true":
+ return Boolean.TRUE;
+ case "false":
+ break;
+ }
+ }
+ }
+ return Boolean.FALSE;
}
/**
diff --git a/src/main/java/org/apache/maven/plugins/jar/Archive.java b/src/main/java/org/apache/maven/plugins/jar/Archive.java
index 51bbccbb..e1298755 100644
--- a/src/main/java/org/apache/maven/plugins/jar/Archive.java
+++ b/src/main/java/org/apache/maven/plugins/jar/Archive.java
@@ -533,27 +533,33 @@ void arguments(final List addTo) {
/**
* Adds to the given list the arguments to provide to the "jar" tool for validating the JAR file.
- * The file is validated only if the validation was not done implicitly at JAR creation time.
- * This is the case if no {@code --release} option was used.
- * This method adds the following options:
+ * The file is validated only if requested or, in automatic mode, if the validation was not done implicitly at
+ * JAR creation time. Implicit validation happens if some {@code --release} options were used.
+ * If {@code true}, this method adds the following options:
*
*
*
{@code --validate} operation mode
*
{@code --file} followed by the path to the JAR file
*
*
+ * @param validate whether to validate, or {@code null} for automatic based on heuristic rules
* @param addTo the list where to add the arguments as {@link String} or {@link Path} instances
* @return whether a validation should be run
*/
- boolean validate(final List addTo) {
- if (filesetForRelease.keySet().stream().anyMatch(Objects::nonNull)) {
- // At least one --release option was used. Validation was implicit.
- return false;
+ boolean validate(final Boolean validate, final List addTo) {
+ boolean effective;
+ if (validate != null) {
+ effective = validate;
+ } else {
+ // If at least one --release option was used, then validation was implicit.
+ effective = !filesetForRelease.keySet().stream().anyMatch(Objects::nonNull);
}
- addTo.add("--validate");
- addTo.add("--file");
- addTo.add(jarFile);
- return true;
+ if (effective) {
+ addTo.add("--validate");
+ addTo.add("--file");
+ addTo.add(jarFile);
+ }
+ return effective;
}
/**
diff --git a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
index 60c20818..103111ad 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
@@ -96,6 +96,12 @@ final class ToolExecutor {
*/
private final String classifier;
+ /**
+ * Whether to validate the JAR file after its creation.
+ * If {@code null}, a value will be determined automatically based on heuristic rules.
+ */
+ private final Boolean validate;
+
/**
* The tool to use for creating the JAR files.
*/
@@ -200,6 +206,7 @@ final class ToolExecutor {
(mojo.finalName != null) ? mojo.finalName : project.getBuild().getFinalName();
forceCreation = mojo.forceCreation;
outputTimestamp = mojo.getOutputTimestamp();
+ validate = mojo.getValidate();
logger = mojo.log;
tool = mojo.getJarTool();
@@ -422,7 +429,7 @@ void writeSingleJAR(final FileCollector files, final Archive archive) throws IOE
* we skip the post-creation validation pass; the archive was already created
* successfully by the `--create` pass above.
*/
- if (archive.validate(arguments)) {
+ if (archive.validate(validate, arguments)) {
final int version = Runtime.version().feature();
if (version >= JDK_FIXING_JAR_VALIDATE) {
int status = executeJarTool();
From 9c90049c0eb517b01bc751ca9bc1fb25f613ab79 Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Mon, 31 Aug 2026 12:36:41 +0900
Subject: [PATCH 21/25] Upgrade Maven Compiler Plugin from beta-3 to beta-5 in
integration tests. It allows the removal of a hack.
---
.../pom.xml | 23 +------------------
src/it/multimodule-multirelease/pom.xml | 23 +------------------
src/it/multimodule/pom.xml | 2 +-
3 files changed, 3 insertions(+), 45 deletions(-)
diff --git a/src/it/multimodule-multirelease-with-excludes/pom.xml b/src/it/multimodule-multirelease-with-excludes/pom.xml
index 8155e6d2..d0351313 100644
--- a/src/it/multimodule-multirelease-with-excludes/pom.xml
+++ b/src/it/multimodule-multirelease-with-excludes/pom.xml
@@ -31,7 +31,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 4.0.0-beta-3
+ 4.0.0-beta-5
@@ -40,27 +40,6 @@
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 3.1.0
-
-
- move
-
- run
-
- prepare-package
-
-
-
-
-
-
-
-
-
org.apache.maven.pluginsmaven-jar-plugin
diff --git a/src/it/multimodule-multirelease/pom.xml b/src/it/multimodule-multirelease/pom.xml
index 5f02bbbe..7394f632 100644
--- a/src/it/multimodule-multirelease/pom.xml
+++ b/src/it/multimodule-multirelease/pom.xml
@@ -31,7 +31,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 4.0.0-beta-3
+ 4.0.0-beta-5
@@ -40,27 +40,6 @@
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 3.1.0
-
-
- move
-
- run
-
- prepare-package
-
-
-
-
-
-
-
-
-
org.apache.maven.pluginsmaven-jar-plugin
diff --git a/src/it/multimodule/pom.xml b/src/it/multimodule/pom.xml
index d4a781e9..5e0794c0 100644
--- a/src/it/multimodule/pom.xml
+++ b/src/it/multimodule/pom.xml
@@ -31,7 +31,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 4.0.0-beta-3
+ 4.0.0-beta-5
From 13769e8abed6d78d184947ec79b33e9677787a44 Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Mon, 31 Aug 2026 12:51:19 +0900
Subject: [PATCH 22/25] Apply English corrections suggested in comments to the
pull request.
---
.../maven/plugins/jar/AbstractJarMojo.java | 40 ++++++-----
.../org/apache/maven/plugins/jar/Archive.java | 32 ++++-----
.../maven/plugins/jar/DirectoryRole.java | 10 +--
.../maven/plugins/jar/ExcludedFiles.java | 2 +-
.../maven/plugins/jar/FileCollector.java | 66 +++++++++----------
.../maven/plugins/jar/MetadataFiles.java | 8 +--
.../maven/plugins/jar/PomDerivation.java | 6 +-
.../maven/plugins/jar/TimestampCheck.java | 16 ++---
.../maven/plugins/jar/ToolExecutor.java | 8 +--
.../apache/maven/plugins/jar/ArchiveTest.java | 10 +--
10 files changed, 102 insertions(+), 96 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index 62bde4bf..0330ed69 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -51,7 +51,7 @@
*/
public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Mojo {
/**
- * Identifier of the tool to use. This identifier shall match the identifier of a tool
+ * Identifier of the tool to use. This identifier must match the identifier of a tool
* registered as a {@link ToolProvider}. By default, the {@code "jar"} tool is used.
*
* @since 4.0.0-beta-2
@@ -150,7 +150,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* environment variable is used as a fallback value,
* to ease forcing Reproducible Build externally when the build has not enabled it natively in POM.
*
- *
This property is supported only with Java Development Kit (JDK) version 19 or later.
+ *
This property is supported only in Java 19 or later.
*
* @since 3.2.0
*/
@@ -159,11 +159,17 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
/**
* Whether to detect multi-release JAR files.
- * If the JAR contains the {@code META-INF/versions} directory it will be detected as a multi-release JAR file,
- * adding the {@code Multi-Release: true} attribute to the main section of the JAR {@code MANIFEST.MF} entry.
- * In addition, the class files in {@code META-INF/versions} will be checked for API compatibility
- * with the class files in the base version. If this flag is {@code false}, then the {@code META-INF/versions}
- * directories are included without processing.
+ * If the JAR contains the {@code META-INF/versions} directory, it is detected as a multi-release JAR file.
+ * In such case:
+ *
+ *
+ *
the {@code Multi-Release: true} attribute is added
+ * to the main section of the JAR {@code MANIFEST.MF} entry, and
+ *
the class files in {@code META-INF/versions} are checked for API compatibility
+ * with the class files in the base version.
+ *
+ *
+ * If this flag is {@code false}, then the {@code META-INF/versions} directories are included without processing.
*
* @since 3.4.0
*/
@@ -174,7 +180,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* Whether to validate the JAR files after their creation.
* If {@code true}, the {@code jar} tool is executed a second time with
* the {@code --validate} option for each created JAR file.
- * This verification may slow down the build if these files are large.
+ * This verification can slow down the build if these files are large.
* If {@code false} (the default), there is no second {@code jar} execution.
* If {@code auto}, the Maven JAR Plugin will decide itself whether
* to execute {@code jar --validate} based on heuristic rules.
@@ -224,7 +230,7 @@ protected AbstractJarMojo() {}
* {@return the scope of dependencies}
* It should be {@link PathScope#MAIN_COMPILE} or {@link PathScope#TEST_COMPILE}.
* Note that we use compile scope rather than runtime scope because dependencies
- * cannot appear in {@code requires} statement if they didn't had compile scope.
+ * cannot appear in {@code requires} statement if they don't have compile scope.
*/
protected abstract PathScope getDependencyScope();
@@ -240,10 +246,10 @@ protected ToolProvider getJarTool() throws MojoException {
}
/**
- * Returns the output time stamp or, as a fallback, the {@code SOURCE_DATE_EPOCH} environment variable.
- * If the time stamp is expressed in seconds, it is converted to ISO 8601 format. Otherwise it is returned as-is.
+ * Returns the output timestamp or, as a fallback, the {@code SOURCE_DATE_EPOCH} environment variable.
+ * If the timestamp is expressed in seconds, it is converted to ISO 8601 format. Otherwise it is returned as-is.
*
- * @return the time stamp in presumed ISO 8601 format, or {@code null} if none
+ * @return the timestamp in presumed ISO 8601 format, or {@code null} if none
* @throws MojoException if the timestamp looks like a number of seconds but cannot be parsed as such
*
* @since 4.0.0-beta-2
@@ -272,14 +278,14 @@ protected String getOutputTimestamp() {
/**
* {@return the patterns of files to include, or an empty list if no include pattern was specified}
*/
- protected List getIncludes() {
+ final List getIncludes() {
return asList(includes);
}
/**
* {@return the patterns of files to exclude, or an empty list if no exclude pattern was specified}
*/
- protected List getExcludes() {
+ final List getExcludes() {
return asList(excludes);
}
@@ -303,7 +309,7 @@ private static List asList(String[] elements) {
* However, this method does not try to create the parent directory, which should be the Maven sub-project.
*
* @return the directory containing the generated JAR files.
- * @throws IOException if the output directory did not existed and could not be created
+ * @throws IOException if the output directory did not exist and could not be created
*/
protected Path getOutputDirectory() throws IOException {
Path dir = outputDirectory;
@@ -346,9 +352,9 @@ final Boolean getValidate() {
* type is {@code "pom"}, {@code "jar"} or {@code "test-jar"} and path
* is the path to the POM or JAR file.
*
- *
Note that a null key does not necessarily means that the JAR is not modular.
+ *
Note that a null key does not necessarily mean that the JAR is not modular.
* It only means that the project was not compiled with module hierarchy,
- * i.e. {@code target/classes/} subdirectories having module names.
+ * that is {@code target/classes/} subdirectories having module names.
* A project can be compiled with package hierarchy and still be modular.
*
* @return the paths to the created archive files
diff --git a/src/main/java/org/apache/maven/plugins/jar/Archive.java b/src/main/java/org/apache/maven/plugins/jar/Archive.java
index e1298755..726f059c 100644
--- a/src/main/java/org/apache/maven/plugins/jar/Archive.java
+++ b/src/main/java/org/apache/maven/plugins/jar/Archive.java
@@ -43,8 +43,8 @@
/**
* Files or root directories to archive for a single module.
- * A single instance of {@code Archive} may contain many directories for different target Java releases.
- * Many instances of {@code Archive} may exist when archiving a multi-modules project.
+ * A single instance of {@code Archive} can contain many directories for different target Java releases.
+ * Many instances of {@code Archive} can exist when archiving a multi-modules project.
*/
final class Archive {
/**
@@ -57,8 +57,8 @@ final class Archive {
Path pomFile;
/**
- * The JAR file to create. May be an existing file,
- * in which case the file creation may be skipped if the file is still up-to-date.
+ * The JAR file to create. Can be an existing file,
+ * in which case the file creation can be skipped if the file is still up-to-date.
*/
@Nonnull
final Path jarFile;
@@ -92,7 +92,7 @@ final class Archive {
/**
* The Maven generated {@code pom.xml} and {@code pom.properties} files, or {@code null} if none.
- * This first item shall be the base directory where the files are located.
+ * This first item must be the base directory where the files are located.
*/
@Nullable
List mavenFiles;
@@ -113,7 +113,7 @@ final class Archive {
/**
* Files or root directories to archive for a single target Java release of a single module.
- * The {@link Archive} enclosing shall contain at least one instance of {@code FileSet} for
+ * The {@link Archive} enclosing must contain at least one instance of {@code FileSet} for
* the base release, and an arbitrary amount of other instances for other target releases.
*/
final class FileSet {
@@ -126,7 +126,7 @@ final class FileSet {
/**
* The files or directories to include in the JAR file.
- * May be absolute paths or paths relative to {@link #directory}.
+ * Can be absolute paths or paths relative to {@link #directory}.
* It usually contains only the files or directories directly in
* the root {@linkplain #directory}, not in sub-directories.
*/
@@ -194,12 +194,12 @@ void add(Path item, BasicFileAttributes attributes, boolean isDirectory) {
* Our tests suggest that the first file after the directory specified by the {@code -C} option must
* be relative to that directory and all files after the first one must be prefixed by the directory
* which was specified in the {@code -C} option. This behavior is not very intuitive and replying on
- * it may be fragile. Furthermore, it seems that the relativized file needs to be the shortest one,
+ * it can be fragile. Furthermore, it seems that the relativized file needs to be the shortest one,
* otherwise the {@code jar} tool rejects files after the first one with "names do not match".
* Which file is first depends on the unspecified directory-iteration order.
* Repeating the {@code -C} option for each file seems safer.
*
- * @param addTo the list where to add the arguments as {@link String} or {@link Path} instances
+ * @param addTo the list to add the arguments as {@link String} or {@link Path} instances to
* @param version the target Java release, or {@code null} for the base version of the JAR file
* @throws IllegalArgumentException if a path cannot be made relative to the base directory
*/
@@ -299,7 +299,7 @@ FileSet baseRelease() {
/**
* Returns the {@code module-info.class} files. Conceptually, there is at most once such file per module.
- * However, more than one file may exist if additional files are provided for additional Java releases.
+ * However, more than one file can exist if additional files are provided for additional Java releases.
* This method returns only the files that exist.
*
* @return all {@code module-info.class} files found for all target Java releases
@@ -361,8 +361,8 @@ public void prune(final boolean skipIfEmpty) {
/**
* {@return whether this archive has nothing to archive}
- * Note that this method may return {@code false} even when there is zero file to archive.
- * It may happen if {@link AbstractJarMojo#skipIfEmpty} is {@code false}. In such case, the
+ * This method can return {@code false} even when there is zero file to archive.
+ * It can happen if {@link AbstractJarMojo#skipIfEmpty} is {@code false}. In such case, the
* "empty" JAR file will still contain at {@code META-INF/MANIFEST.MF} file.
*
*
Prerequisites
@@ -408,7 +408,7 @@ FileSet newTargetRelease(Path directory, Runtime.Version version) {
* Java tools). If a module is specified, the main class is kept only if the module match. The intent is to
* allow users to specify on which module the main class applies when they use plugin configuration.
*
- *
This method may modify the {@code content} manifest. Caller shall ensure that the given manifest
+ *
This method may modify the {@code content} manifest. Caller must ensure that the given manifest
* is not a shared instance. This method returns whether a change has actually been done.
*
* @param content combination of existing {@code MANIFEST.MF} and manifest inferred from configuration, or null
@@ -486,7 +486,7 @@ Manifest mergeManifest(Path file, Manifest content) throws IOException {
/**
* Adds to the given list the arguments to provide to the "jar" tool for each version.
- * Elements added to the list shall be instances of {@link String} or {@link Path}.
+ * Elements added to the list must be instances of {@link String} or {@link Path}.
* Callers should have added the following options (if applicable) before to invoke this method:
*
*
@@ -543,7 +543,7 @@ void arguments(final List addTo) {
*
*
* @param validate whether to validate, or {@code null} for automatic based on heuristic rules
- * @param addTo the list where to add the arguments as {@link String} or {@link Path} instances
+ * @param addTo the list to add the arguments as {@link String} or {@link Path} instances to
* @return whether a validation should be run
*/
boolean validate(final Boolean validate, final List addTo) {
@@ -620,7 +620,7 @@ Path writeDebugFile(Path baseDir, Path debugDirectory, String classifier, List/META-INF/versions"} directory.
* This is part of the JAR specification, except the {@code } prefix.
- * The sub-directories are named according Java releases such as "21".
+ * The sub-directories are named according to Java releases such as "21".
* The next location can only be {@link #RESOURCES}.
*/
VERSIONS,
/**
* The Maven-specific {@code "META-INF/versions-modular"} directory.
- * Note that {@code "/META-INF/versions-modular"} is not forbidden, but does not make sense.
- * The sub-directories are named according Java releases such as "21".
+ * {@code "/META-INF/versions-modular"} is not forbidden, but does not make sense.
+ * The sub-directories are named according to Java releases such as "21".
* The next location can only be {@link #MODULES}.
*/
VERSIONS_MODULAR,
/**
* The Maven-specific {@code "META-INF/versions-modular"} directory.
- * All sub-directories shall have the name of a Java module.
+ * All sub-directories must have the name of a Java module.
* The next location can only be {@link #NAMED_MODULE}.
*/
MODULES,
@@ -67,7 +67,7 @@ enum DirectoryRole {
/**
* The classes or other types of resources to include in a single archive.
- * May also be other files in the {@code META-INF} directory.
+ * Can also be other files in the {@code META-INF} directory.
*/
RESOURCES
}
diff --git a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
index 91de3a4f..31b2be18 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
@@ -27,7 +27,7 @@
/**
* A list of files to temporarily move outside the directory to package in a JAR archive.
- * This is used for excluding files from the JAR archive according include/exclude filters.
+ * This is used for excluding files from the JAR archive according to include/exclude filters.
* We move these files for making possible to specify the whole directory to the {@code jar} tool.
* This approach is used instead of enumerating files in arguments given to the {@code jar} tool because
* such enumeration can not contain directory entries (otherwise the whole directory would be included).
diff --git a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
index 3fb0ddfc..66f1c08d 100644
--- a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
+++ b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
@@ -139,14 +139,14 @@ final class FileCollector extends SimpleFileVisitor {
/**
* The current module being archived. This field is updated every times that {@code FileCollector}
- * enters in a new module directory.
+ * visits a new module directory.
*/
@Nonnull
private Archive currentModule;
/**
* The module and target Java release currently being scanned. This field is updated every times that
- * {@code FileCollector} enters in a new module directory or in a new target Java release for a given module.
+ * {@code FileCollector} visits a new module directory or in a new target Java release for a given module.
*/
@Nonnull
private Archive.FileSet currentFilesToArchive;
@@ -211,7 +211,7 @@ private void resetToPackageHierarchy() {
*
* @param directory a {@code ""} or {@code "META-INF/versions-modular/"} directory
*/
- private void enterModuleDirectory(final Path directory) {
+ private void preVisitVersionDirectory(final Path directory) {
String moduleName = directory.getFileName().toString();
currentModule = moduleHierarchy.computeIfAbsent(
moduleName, (name) -> context.newArchive(name, currentTargetVersion, directory));
@@ -228,7 +228,7 @@ private void enterModuleDirectory(final Path directory) {
* @param useDirectly whether the directory is {@code "META-INF/versions/"}
* @return whether to skip the directory because of invalid version number
*/
- private boolean enterVersionDirectory(final Path directory, final boolean useDirectly) {
+ private boolean preVisitVersionDirectory(final Path directory, final boolean useDirectly) {
try {
currentTargetVersion = Runtime.Version.parse(directory.getFileName().toString());
} catch (IllegalArgumentException e) {
@@ -244,7 +244,7 @@ private boolean enterVersionDirectory(final Path directory, final boolean useDir
/**
* Determines if the given directory should be scanned for files to archive.
* This method may also update {@link #currentFilesToArchive} if it detects
- * that we are entering in a new module or a new target Java release.
+ * that we are visiting the content of a new module or a new target Java release.
*
* @param directory the directory which will be traversed
* @param attributes the directory's basic attributes
@@ -266,7 +266,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
switch (role) {
case ROOT:
/*
- * Entering in any subdirectory of `target/classes` (or other directory to archive).
+ * Visiting any subdirectory of `target/classes` (or other directory to archive).
* We need to handle `META-INF` and modules in a special way, and archive the rest.
*/
if (directory.endsWith(MetadataFiles.META_INF)) {
@@ -274,7 +274,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
checkForManifest = true;
} else if (Files.isRegularFile(directory.resolve(MODULE_DESCRIPTOR_FILE_NAME))) {
role = DirectoryRole.NAMED_MODULE;
- enterModuleDirectory(directory);
+ preVisitVersionDirectory(directory);
} else {
role = DirectoryRole.RESOURCES;
}
@@ -282,7 +282,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
case META_INF:
/*
- * Entering in a subdirectory of `META-INF` or `/META-INF`. We will need to handle
+ * Visiting a subdirectory of `META-INF` or `/META-INF`. We will need to handle
* `MANIFEST.MF`, `versions` and `versions-modular` in a special way, and archive the rest.
*/
if (detectMultiReleaseJar && directory.endsWith(VERSIONS)) {
@@ -300,11 +300,11 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
case VERSIONS:
/*
- * Entering in a `META-INF/versions//` directory for a specific target Java release.
- * May also be a `/META-INF/versions//` directory, even if the latter is not
+ * Visiting a `META-INF/versions//` directory for a specific target Java release.
+ * Can also be a `/META-INF/versions//` directory, even if the latter is not
* the layout generated by Maven Compiler Plugin.
*/
- if (enterVersionDirectory(directory, true)) {
+ if (preVisitVersionDirectory(directory, true)) {
// An error occurred while parsing the version number.
return FileVisitResult.SKIP_SUBTREE;
}
@@ -313,11 +313,11 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
case VERSIONS_MODULAR:
/*
- * Entering in a `META-INF/versions-modular//` directory for a specific target Java release.
+ * Visiting a `META-INF/versions-modular//` directory for a specific target Java release.
* That directory contains all modules for the version.
*/
resetToPackageHierarchy(); // No module in particular yet.
- if (enterVersionDirectory(directory, false)) {
+ if (preVisitVersionDirectory(directory, false)) {
// An error occurred while parsing the version number.
return FileVisitResult.SKIP_SUBTREE;
}
@@ -326,15 +326,15 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
case MODULES:
/*
- * Entering in a `META-INF/versions-modular//` directory.
+ * Visiting a `META-INF/versions-modular//` directory.
*/
- enterModuleDirectory(directory);
+ preVisitVersionDirectory(directory);
role = DirectoryRole.NAMED_MODULE;
break;
case NAMED_MODULE:
/*
- * Entering in a `` or `META-INF/versions-modular//` subdirectory.
+ * Visiting a `` or `META-INF/versions-modular//` subdirectory.
* A module could have its own `META-INF` subdirectory, so we need to check again.
*/
if (directory.endsWith(MetadataFiles.META_INF)) {
@@ -348,7 +348,7 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
}
/*
* Do not move this condition inside the `switch` block because `role` may have been modified.
- * The `role` value is now the role of `directory`, not anymore the role of parent directory.
+ * The `role` value is now the role of `directory`, not the role of parent directory.
*/
if (role == DirectoryRole.RESOURCES) {
currentFilesToArchive.add(directory, attributes, true);
@@ -356,8 +356,8 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
/*
* Since we are skipping the whole directory, `postVisitDirectory(…)` will not be invoked.
* We must reset `currentFilesToArchive` and `currentTargetVersion` by an explicit call.
- * This is important mostly after we added a whole `META-INF/versions/` directory,
- * otherwise base files visited afterwards (directory iteration order is unspecified)
+ * This is important mostly after we added a whole `META-INF/versions/` directory.
+ * Otherwise, since directory iteration order is unspecified, base files visited afterwards
* would be added to this version's file set instead of the base release.
*/
resetToParentDirectoryState();
@@ -369,8 +369,8 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
}
/**
- * Updates the {@code FileCollector} state after we finished to scan the content of a directory.
- * The fields to update depend on which directory has been visited (module, version, etc.).
+ * Updates the {@code FileCollector} state after we finished scanning the contents of a directory.
+ * The fields to update depend on which directory has been visited (module, version, etc.).
*
* @param directory the directory which has been traversed
* @param error the error that occurred while traversing the directory, or {@code null} if none
@@ -397,8 +397,8 @@ public FileVisitResult postVisitDirectory(final Path directory, final IOExceptio
}
/**
- * Updates {@code FileCollector} to a state suitable to the parent of the directory that we finished to scan.
- * Contrarily to {@link #postVisitDirectory(Path, IOException)}, this method expects the last element of
+ * Updates {@code FileCollector} to a state suitable for the parent of the directory that we finished to scan.
+ * Contrary to {@link #postVisitDirectory(Path, IOException)}, this method expects the last element of
* {@link #directoryRoles} to describe the parent directory, not the directory that we finished to visit.
*/
@SuppressWarnings("checkstyle:MissingSwitchDefault")
@@ -440,8 +440,8 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
/**
* Removes all empty archives and ensures that the lowest version is declared as the base version.
* This method should be invoked after all output directories to archive have been fully scanned.
- * If {@code skipIfEmpty} is {@code false}, then this method ensures that at least one archive
- * remains even if that archive is empty.
+ * If {@code skipIfEmpty} is {@code false}, this method ensures that at least one archive remains
+ * even if that archive is empty.
*
* @param skipIfEmpty value of {@link AbstractJarMojo#skipIfEmpty}
*/
@@ -453,7 +453,7 @@ public void prune(boolean skipIfEmpty) {
}
/**
- * Moves, copies or ignores orphan files.
+ * Moves, copies, or ignores orphan files.
* An orphan file is a file which is not in any module when module hierarchy is used.
* For example, some Maven plugins may create files such as {@code META-INF/LICENSE},
* {@code META-INF/NOTICE} or {@code META-INF/DEPENDENCIES}. These files are not in
@@ -462,10 +462,10 @@ public void prune(boolean skipIfEmpty) {
*
*
A possible strategy could be to copy the {@code LICENSE} and {@code NOTICE} files
* in each module, and ignore the {@code DEPENDENCIES} file because its content is not
- * correct for a module. For now, we just log a warning an ignore.
+ * correct for a module. For now, we just log a warning and ignore.
*
*
Prerequisites
- * The {@link #prune(boolean)} method should have been invoked once before to invoke this method.
+ * The {@link #prune(boolean)} method should have been invoked once before invoking this method.
*
* @return if this method ignored some files, the root directory of those files
*/
@@ -491,7 +491,7 @@ private ExcludedFiles exclusion() throws IOException {
/**
* Writes all JAR files.
- * If the project is multi-module, then this method returns the path to the generated parent POM file.
+ * If the project is multi-module, this method returns the path to the generated parent POM file.
*
*
Prerequisites
* The {@link #prune(boolean)} method should have been invoked once before to invoke this method.
@@ -522,16 +522,16 @@ Path writeAllJARs(final ToolExecutor executor) throws IOException {
* {@return the paths to all root directories of modules in a module hierarchy}
* They are usually {@code target/classes/} directories, but could also
* be sub-directories in the {@code META-INF/modular-versions//} directory.
- * If the project does not use module hierarchy, then this method returns an empty list.
+ * If the project does not use module hierarchy, this method returns an empty list.
*
*
Ignored package hierarchy
- * Note that an empty list does not necessarily means that the JAR is not modular,
+ * Note that an empty list does not mean that the JAR is not modular,
* as a modular JAR can also be built from package hierarchy. But we intentionally
* ignore the latter case because this method is used for deriving POM files, and
- * we do not perform such derivation for projects organized in the classical Maven 3 way.
+ * we do not perform such derivation for projects organized in the Maven 3 way.
*
*
Prerequisites
- * The {@link #prune(boolean)} method should have been invoked once before to invoke this method.
+ * The {@link #prune(boolean)} method should have been invoked once before invoking this method.
*/
List getModuleHierarchyRoots() {
return moduleHierarchy.values().stream()
diff --git a/src/main/java/org/apache/maven/plugins/jar/MetadataFiles.java b/src/main/java/org/apache/maven/plugins/jar/MetadataFiles.java
index b26305c5..51264e8b 100644
--- a/src/main/java/org/apache/maven/plugins/jar/MetadataFiles.java
+++ b/src/main/java/org/apache/maven/plugins/jar/MetadataFiles.java
@@ -70,7 +70,7 @@ final class MetadataFiles implements Closeable {
/**
* All files and directories in the order that they were created.
- * The first element of this list shall be the root temporary directory created by this class.
+ * The first element of this list must be the root temporary directory created by this class.
*/
private final List filesToDelete;
@@ -178,11 +178,11 @@ public List addPOM(final MavenArchiveConfiguration archive, final boolean
final Path baseDir = baseDirectory();
final Path mavenDir = createDirectories(baseDir, META_INF, MAVEN_DIR, groupId, artifactId);
final Path pomFile = linkOrCopy(attachedPOM, mavenDir.resolve("pom.xml"));
- filesToDelete.add(pomFile); // Add soon for deleting this file even if an exception is thrown below.
+ filesToDelete.add(pomFile); // Register now for deleting this file even if an exception is thrown below.
/*
* Subset of above "pom.xml" file but written as a properties file.
* If reproducible build is enabled, we will need to reformat after
- * writing for ensuring a deterministic order of entries.
+ * writing to ensure a deterministic order of entries.
*/
final var properties = new Properties();
Path propertiesFile = archive.getPomPropertiesFile();
@@ -196,7 +196,7 @@ public List addPOM(final MavenArchiveConfiguration archive, final boolean
properties.setProperty("version", version);
propertiesFile = mavenDir.resolve("pom.properties");
try (BufferedWriter out = Files.newBufferedWriter(propertiesFile)) {
- filesToDelete.add(propertiesFile); // Add soon for deleting this file even if an exception is thrown below.
+ filesToDelete.add(propertiesFile); // Register now for deleting even if an exception is thrown below.
properties.store(out, "Subset of pom.xml");
}
if (reproducible) {
diff --git a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
index 1cb6acb2..43d7b0bb 100644
--- a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
+++ b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
@@ -99,9 +99,9 @@ final class PomDerivation {
private final Map fromURI;
/**
- * Module names associated to Maven dependencies.
+ * Module names associated with Maven dependencies.
* This map contains {@link DependencyCoordinates#getId()} as keys and module references as values.
- * This is used for detecting which dependencies are really used according {@code module-info.class}.
+ * This is used to detect which dependencies are really used according to {@code module-info.class}.
*
* @todo The keys should be instances of {@link DependencyCoordinates}. Unfortunately, as of Maven 4.0.0-rc-5
* that interface does not define the {@code equals} and {@code hashCode} contracts.
@@ -455,7 +455,7 @@ private Model deriveModulePOM() throws ModelBuilderException {
}
/**
- * Modifies the optional and scope elements of the given dependency according the given modifiers.
+ * Modifies the optional and scope elements of the given dependency according to the given modifiers.
*
* @param dependency the dependency to amend
* @param modifiers the modifiers to apply
diff --git a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
index 003ba88c..8a663aa8 100644
--- a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
+++ b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
@@ -37,8 +37,8 @@
/**
* Checks file timestamps in order to determine if anything changed compared to an existing JAR file.
* This class may scan directories, but only if they have not already been visited by {@link FileCollector}.
- * Note that the latter can occur only if {@link FileCollector} has no {@code PathMatcher}.
- * Therefore, this class uses no {@code PathMatcher} neither.
+ * The latter can only occur if {@link FileCollector} has no {@code PathMatcher}.
+ * Therefore, this class uses no {@code PathMatcher}.
*
*
Ignore files
* The {@code META-INF/MANIFEST.MF} file and the {@code META-INF/maven/} directory are ignored.
@@ -67,13 +67,13 @@ final class TimestampCheck extends SimpleFileVisitor {
/**
* Entries of the JAR file. Note that getting elements from this enumeration can be costly.
- * Therefore, we do not fetch all elements in advance but only when needed.
+ * Therefore, we only fetch elements when needed.
*/
private Enumeration extends ZipEntry> entries;
/**
* Files found in the JAR file but not yet traversed by the file visitor.
- * Files are added lazily only when needed, and removed as soon as they have been traversed.
+ * Files are added lazily when needed, and removed as soon as they have been traversed.
* Path are absolute (resolved with {@link #classesDir}).
* For each entry, the associated value is whether the path is a directory.
*/
@@ -81,7 +81,7 @@ final class TimestampCheck extends SimpleFileVisitor {
/**
* Some of the files in the build directory. This list contains only the files for which we have already
- * verified the timestamp. We store them in a separated list for avoiding to check the timestamp twice.
+ * verified the timestamp. We store them in a separate list for avoiding to check the timestamp twice.
* We need this list because we still need to verify if the files are in the {@link #jarFile}.
* For each entry, the associated value is whether the path is a directory.
*/
@@ -138,7 +138,7 @@ boolean isUpToDateJAR(final Collection fileSets) {
try (ZipFile jar = new ZipFile(jarFile.toFile())) {
entries = jar.entries();
for (Path file : filesInBuild.keySet()) {
- if (!isFoundInJAR(file)) {
+ if (!removeFromFilesInJAR(file)) {
return false;
}
}
@@ -229,7 +229,7 @@ private boolean hasUpdatedInSubdir(final Path directory) throws IOException {
*/
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) {
- if (jarFileTime.compareTo(attributes.lastModifiedTime()) >= 0 && isFoundInJAR(file)) {
+ if (jarFileTime.compareTo(attributes.lastModifiedTime()) >= 0 && removeFromFilesInJAR(file)) {
return FileVisitResult.CONTINUE;
} else {
hasUpdates = true;
@@ -244,7 +244,7 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
* @param file the file to check
* @return whether the given file was found in the JAR file
*/
- private boolean isFoundInJAR(final Path file) {
+ private boolean removeFromFilesInJAR(final Path file) {
if (filesInJAR.remove(file) != null) {
return true;
}
diff --git a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
index 103111ad..1ef5027a 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
@@ -129,7 +129,7 @@ final class ToolExecutor {
/**
* A buffer for the arguments given to the "jar" tool, reused for each module.
- * Each element of the list shall be instances of either {@link String} or {@link Path}.
+ * Each element of the list must be instances of either {@link String} or {@link Path}.
*/
private final List arguments;
@@ -161,8 +161,8 @@ final class ToolExecutor {
/**
* The file from which {@link #manifestFromPlugin} has been read, or {@code null} if none.
- * If non-null, reading that file shall produce the same manifest as {@link #manifestFromPlugin}.
- * It implies that this field shall be {@code null} if {@link #manifestFromPlugin} is the result
+ * If non-null, reading that file must produce the same manifest as {@link #manifestFromPlugin}.
+ * It implies that this field must be {@code null} if {@link #manifestFromPlugin} is the result
* of merging elements specified in {@code } with a file specified in the plugin configuration.
*/
private final Path manifestFile;
@@ -174,7 +174,7 @@ final class ToolExecutor {
/**
* The timestamp in ISO-8601 extended offset date-time, or {@code null} if none.
- * If user provided a value in seconds, it shall have been converted to ISO-8601.
+ * If user provided a value in seconds, it must have been converted to ISO-8601.
* This is used for reproducible builds.
*/
private final String outputTimestamp;
diff --git a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
index 38580060..8f5b4498 100644
--- a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
+++ b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
@@ -76,7 +76,7 @@ private static Object mainClassOf(Manifest m) {
* Verifies that {@link Archive#setMainClass(Manifest)} takes ownership for a module-qualified
* {@code "module/Class"} main class.
*
- * Note that the {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
+ * The {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
* The standard JAR specification accepts only the {@code "foo.MainFile"} class name.
*/
@Test
@@ -92,7 +92,7 @@ void owningModuleClaimsMainClassAndRemovesItFromManifest() {
/**
* Verifies that {@link Archive#setMainClass(Manifest)} does not take ownership fo
* a module-qualified {@code "module/Class"} main class when the module name does not match.
- * Note that the {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
+ * The {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
* The standard JAR specification accepts only the {@code "foo.MainFile"} class name.
*/
@Test
@@ -104,7 +104,7 @@ void nonOwningModuleRejectsMainClass() {
}
/**
- * Tests that which module keeps the main class must not depend on processing order.
+ * Tests that which module keeps the main class does not depend on processing order.
* {@link ToolExecutor} gives each module a copy of the shared plugin manifest;
* this pins that the owning module (and only it) keeps the main class in either order,
* and that the shared manifest is never consumed.
@@ -120,7 +120,7 @@ void mainClassAssignmentIsIndependentOfModuleOrder() {
* Asserts that {@link Archive#setMainClass(Manifest)} returns {@code true}
* for the owner and {@code false} for the other module.
*
- *
Note that the {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
+ *
The {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
* The standard JAR specification accepts only the {@code "foo.MainFile"} class name.
* This extension is used by the plugin for identifying in which JAR file to add this
* {@code Main-Class} manifest entry.
* In our tests, it seems that the first JAR entry after the {@code -C} option
- * shall be relative, and only that file. Furthermore, it seems that this file shall be the
+ * must be relative, and only that file. Furthermore, it seems that this file must be the
* shortest. We tried to apply this heuristic rules in a branch, but it does not save a lot
* of characters compared to repeating {@code -C}.
*/
From cdf4da5776e5702b162417c3c75f254b7979aee4 Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Thu, 3 Sep 2026 19:47:27 +0900
Subject: [PATCH 23/25] Apply more English corrections and a few API changes
suggested on the pull request.
---
.../maven/plugins/jar/AbstractJarMojo.java | 15 +++----
.../org/apache/maven/plugins/jar/Archive.java | 45 +++++++++----------
.../maven/plugins/jar/ExcludedFiles.java | 13 +++---
.../maven/plugins/jar/FileCollector.java | 13 +++---
.../maven/plugins/jar/PomDerivation.java | 11 ++---
.../maven/plugins/jar/TimestampCheck.java | 8 ++--
.../maven/plugins/jar/ToolExecutor.java | 8 ++--
.../apache/maven/plugins/jar/ArchiveTest.java | 6 +--
8 files changed, 60 insertions(+), 59 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index 0330ed69..7dd2945e 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -44,7 +44,7 @@
import org.apache.maven.shared.archiver.MavenArchiver;
/**
- * Base class for creating a JAR file from project classes.
+ * Base class for creating a JAR file from project classes.
*
* @author Emmanuel Venisse
* @author Martin Desruisseaux
@@ -74,13 +74,13 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
protected String[] excludes;
/**
- * Directory containing the generated JAR files.
+ * Directory containing the generated JAR files.
*/
@Parameter(defaultValue = "${project.build.directory}", required = true)
protected Path outputDirectory;
/**
- * Name of the generated JAR file.
+ * Name of the generated JAR file.
* The default value is {@code "${project.build.finalName}"},
* which itself defaults to {@code "${artifactId}-${version}"}.
* Ignored if the Maven sub-project to archive uses module hierarchy.
@@ -124,7 +124,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* By default, this plugin looks to see if the output JAR files exist and inputs have not changed.
* If these conditions are true, the plugin skips creation of the JAR files.
* This does not work when other plugins, like the maven-shade-plugin, are configured to post-process the JAR.
- * This plugin can not detect the post-processing, and so leaves the post-processed JAR file in place.
+ * This plugin cannot detect the post-processing, and so leaves the post-processed JAR file in place.
* This can lead to failures when those plugins do not expect to find their own output as an input.
* Set this parameter to {@code true} to avoid these problems by forcing this plugin to recreate the JAR every time.
*
@@ -148,7 +148,8 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* If not configured or disabled,
* the SOURCE_DATE_EPOCH
* environment variable is used as a fallback value,
- * to ease forcing Reproducible Build externally when the build has not enabled it natively in POM.
+ * to ease forcing Reproducible Build externally when the build has not enabled it natively in
+ * POM.
*
*
This property is supported only in Java 19 or later.
*
@@ -402,9 +403,7 @@ public void execute() throws MojoException {
final Map> artifactFiles;
try {
artifactFiles = createArchives();
- } catch (MojoException e) {
- throw e;
- } catch (Exception e) {
+ } catch (IOException e) {
throw new MojoException("Error while assembling the JAR file.", e);
}
if (artifactFiles.isEmpty()) {
diff --git a/src/main/java/org/apache/maven/plugins/jar/Archive.java b/src/main/java/org/apache/maven/plugins/jar/Archive.java
index 726f059c..3c7c0c65 100644
--- a/src/main/java/org/apache/maven/plugins/jar/Archive.java
+++ b/src/main/java/org/apache/maven/plugins/jar/Archive.java
@@ -57,7 +57,7 @@ final class Archive {
Path pomFile;
/**
- * The JAR file to create. Can be an existing file,
+ * The JAR file to create. Can be an existing file,
* in which case the file creation can be skipped if the file is still up-to-date.
*/
@Nonnull
@@ -125,7 +125,7 @@ final class FileSet {
final Path directory;
/**
- * The files or directories to include in the JAR file.
+ * The files or directories to include in the JAR file.
* Can be absolute paths or paths relative to {@link #directory}.
* It usually contains only the files or directories directly in
* the root {@linkplain #directory}, not in sub-directories.
@@ -178,8 +178,7 @@ private Path discardAllFiles(Path base) {
* @param isDirectory whether the file is a directory
*/
void add(Path item, BasicFileAttributes attributes, boolean isDirectory) {
- TimestampCheck tc = existingJAR;
- if (tc != null && tc.isUpdated(item, attributes, isDirectory)) {
+ if (existingJAR != null && existingJAR.isUpdated(item, attributes, isDirectory)) {
existingJAR = null; // Signal that the existing file is outdated.
}
files.add(item);
@@ -203,7 +202,7 @@ void add(Path item, BasicFileAttributes attributes, boolean isDirectory) {
* @param version the target Java release, or {@code null} for the base version of the JAR file
* @throws IllegalArgumentException if a path cannot be made relative to the base directory
*/
- private void arguments(List addTo, Runtime.Version version) {
+ private void addArgumentsTo(List addTo, Runtime.Version version) {
if (files.isEmpty()) {
return;
}
@@ -298,7 +297,7 @@ FileSet baseRelease() {
}
/**
- * Returns the {@code module-info.class} files. Conceptually, there is at most once such file per module.
+ * Returns the {@code module-info.class} files. Conceptually, there is at most one such file per module.
* However, more than one file can exist if additional files are provided for additional Java releases.
* This method returns only the files that exist.
*
@@ -361,12 +360,12 @@ public void prune(final boolean skipIfEmpty) {
/**
* {@return whether this archive has nothing to archive}
- * This method can return {@code false} even when there is zero file to archive.
- * It can happen if {@link AbstractJarMojo#skipIfEmpty} is {@code false}. In such case, the
- * "empty" JAR file will still contain at {@code META-INF/MANIFEST.MF} file.
+ * This method can return {@code false} even when there are no file to archive.
+ * It can happen if {@link AbstractJarMojo#skipIfEmpty} is {@code false}. In this case,
+ * the "empty" JAR file will still contain at {@code META-INF/MANIFEST.MF} file.
*
- *
Prerequisites
- * The {@link #prune(boolean)} method should be invoked before this method for accurate result.
+ *
Prerequisites:
+ * The {@link #prune(boolean)} method should be invoked before this method for accurate result.
*/
public boolean isEmpty() {
return filesetForRelease.isEmpty();
@@ -375,20 +374,20 @@ public boolean isEmpty() {
/**
* Checks whether the JAR file already exists and can be reused.
* This method verifies that the JAR file contains all the files to archive,
- * contains no extra file, and no file to archive is newer than the JAR file.
+ * contains no extra files, and no file to archive is newer than the JAR file.
*
*
This method can be invoked only once.
- * If invoked more often, it returns {@code false} on all subsequent invocations.
+ * If invoked more than once, it returns {@code false} on all subsequent invocations.
*
* @return whether the JAR file already exists and can be reused
*/
public boolean isUpToDateJAR() {
- final TimestampCheck tc = existingJAR;
- if (tc == null) {
+ final TimestampCheck candidate = existingJAR;
+ if (candidate == null) {
return false;
}
existingJAR = null; // Let GC do its job.
- return tc.isUpToDateJAR(filesetForRelease.values());
+ return candidate.isUpToDateJAR(filesetForRelease.values());
}
/**
@@ -408,8 +407,8 @@ FileSet newTargetRelease(Path directory, Runtime.Version version) {
* Java tools). If a module is specified, the main class is kept only if the module match. The intent is to
* allow users to specify on which module the main class applies when they use plugin configuration.
*
- *
This method may modify the {@code content} manifest. Caller must ensure that the given manifest
- * is not a shared instance. This method returns whether a change has actually been done.
+ *
This method can modify the {@code content} manifest. Caller must ensure that the given manifest
+ * is not a shared instance. This method returns whether a change has been done.
*
* @param content combination of existing {@code MANIFEST.MF} and manifest inferred from configuration, or null
* @return whether the given manifest has been modified by this method
@@ -453,7 +452,7 @@ boolean setManifest(Path file, boolean force) {
* If both {@code file} and {@code content} are non-null, then {@code content} must be the
* result of reading {@code file}.
*
- *
This method never modifies the given {@code content} object. If manifest are merged,
+ *
This method never modifies the given {@code content} object. If manifests are merged,
* a new {@link Manifest} instance is created. Therefore, caller can check whether this
* method returned a new instance as a way to recognize that a merge occurred.
*
@@ -511,7 +510,7 @@ Manifest mergeManifest(Path file, Manifest content) throws IOException {
*
* @param addTo the list where to add the arguments as {@link String} or {@link Path} instances
*/
- void arguments(final List addTo) {
+ void addArgumentsTo(final List addTo) {
addTo.add("--file");
addTo.add(jarFile);
if (manifest != null) {
@@ -527,7 +526,7 @@ void arguments(final List addTo) {
addTo.addAll(mavenFiles);
}
for (Map.Entry entry : filesetForRelease.entrySet()) {
- entry.getValue().arguments(addTo, entry.getKey());
+ entry.getValue().addArgumentsTo(addTo, entry.getKey());
}
}
@@ -546,7 +545,7 @@ void arguments(final List addTo) {
* @param addTo the list to add the arguments as {@link String} or {@link Path} instances to
* @return whether a validation should be run
*/
- boolean validate(final Boolean validate, final List addTo) {
+ boolean addValidateArgumentsTo(final Boolean validate, final List addTo) {
boolean effective;
if (validate != null) {
effective = validate;
@@ -571,7 +570,7 @@ boolean validate(final Boolean validate, final List addTo) {
* @param baseDir project base directory for relativizing the arguments
* @param debugDirectory the directory where to write the debug file
* @param classifier the classifier (e.g. "tests"), or {@code null} if none
- * @param arguments the arguments formatted by {@link #arguments(List)}
+ * @param arguments the arguments formatted by {@link #addArgumentsTo(List)}
* @return the debug file where arguments have been written
* @throws IOException if an error occurred while writing the debug file
*/
diff --git a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
index 31b2be18..c0fff941 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
@@ -27,10 +27,11 @@
/**
* A list of files to temporarily move outside the directory to package in a JAR archive.
- * This is used for excluding files from the JAR archive according to include/exclude filters.
+ * This class is needed when the {@link AbstractJarMojo} configuration has include or exclude filters.
+ * Excluded files are temporarily moved outside the directory to archive.
* We move these files for making possible to specify the whole directory to the {@code jar} tool.
* This approach is used instead of enumerating files in arguments given to the {@code jar} tool because
- * such enumeration can not contain directory entries (otherwise the whole directory would be included).
+ * such enumeration cannot contain directory entries (otherwise the whole directory would be included).
* Some software such as Spring applications component scan relies on the presence of directory entries.
*/
final class ExcludedFiles implements Closeable {
@@ -69,9 +70,9 @@ final class ExcludedFiles implements Closeable {
*/
ExcludedFiles(Path directory, List excludedFiles, List excludedDirectories) throws IOException {
indexOfFirstDirectory = excludedFiles.size();
- final int nd = excludedDirectories.size();
- original = excludedFiles.toArray(new Path[indexOfFirstDirectory + nd]);
- System.arraycopy(excludedDirectories.toArray(), 0, original, indexOfFirstDirectory, nd);
+ final int numDirs = excludedDirectories.size();
+ original = excludedFiles.toArray(new Path[indexOfFirstDirectory + numDirs]);
+ System.arraycopy(excludedDirectories.toArray(), 0, original, indexOfFirstDirectory, numDirs);
moved = new Path[original.length];
temporaryDirectory = Files.createTempDirectory(directory, "excluded-");
}
@@ -113,7 +114,7 @@ public void move() throws IOException {
/**
* Moves the temporary files back to their original locations.
* This method can be invoked even if only a subset of the files were moved.
- * The latter may happen if an error occurred in the middle of {@link #move()} execution.
+ * The latter can happen if an error occurred in the middle of {@link #move()} execution.
*
* @throws IOException if an error occurred while moving a temporary files or deleting the temporary directory.
*/
diff --git a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
index 66f1c08d..3df2aa98 100644
--- a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
+++ b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
@@ -52,7 +52,8 @@
*
*
*
- * The reason for using the options is that they allow the {@code jar} tool to perform additional verifications.
+ * The reason for using the {@code --release} and {@code --manifest} options instead of adding explicitly
+ * the entries is because the options allow the {@code jar} tool to perform additional verifications.
* For example, when using the {@code --release} option, {@code jar} verifies the API compatibility.
*/
final class FileCollector extends SimpleFileVisitor {
@@ -207,7 +208,7 @@ private void resetToPackageHierarchy() {
/**
* Declares that the given directory is the base directory of a module.
* For an output generated by {@code javac} from a module source hierarchy,
- * the directory name is guaranteed to be the module name.
+ * the directory name is the module name.
*
* @param directory a {@code ""} or {@code "META-INF/versions-modular/"} directory
*/
@@ -464,8 +465,8 @@ public void prune(boolean skipIfEmpty) {
* in each module, and ignore the {@code DEPENDENCIES} file because its content is not
* correct for a module. For now, we just log a warning and ignore.
*
- *
Prerequisites
- * The {@link #prune(boolean)} method should have been invoked once before invoking this method.
+ *
Prerequisites:
+ * The {@link #prune(boolean)} method should have been invoked once before invoking this method.
*
* @return if this method ignored some files, the root directory of those files
*/
@@ -493,8 +494,8 @@ private ExcludedFiles exclusion() throws IOException {
* Writes all JAR files.
* If the project is multi-module, this method returns the path to the generated parent POM file.
*
- *
Prerequisites
- * The {@link #prune(boolean)} method should have been invoked once before to invoke this method.
+ *
Prerequisites:
+ * The {@link #prune(boolean)} method should have been invoked once before to invoke this method.
*
* @return path to the generated parent POM file, or {@code null} if none
* @throws MojoException if an error occurred during the execution of the "jar" tool
diff --git a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
index 43d7b0bb..de6abd6f 100644
--- a/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
+++ b/src/main/java/org/apache/maven/plugins/jar/PomDerivation.java
@@ -137,14 +137,14 @@ final class PomDerivation {
rethrow(result);
final Map dependencies = result.getDependencies();
final Path[] allModulePaths = toRealPaths(moduleRoots, dependencies.values());
- fromURI = new HashMap<>(allModulePaths.length); // TODO: use newHashMap with JDK19.
+ fromURI = new HashMap<>(allModulePaths.length);
moduleFinder = ModuleFinder.of(allModulePaths);
if (moduleFinder != null) {
for (ModuleReference reference : moduleFinder.findAll()) {
reference.location().ifPresent((location) -> fromURI.put(location, reference));
}
}
- fromDependency = new HashMap<>(dependencies.size()); // TODO: use newHashMap with JDK19.
+ fromDependency = new HashMap<>(dependencies.size());
for (Map.Entry entry : dependencies.entrySet()) {
Path modulePath = entry.getValue().toRealPath();
ModuleReference reference = fromURI.get(modulePath.toUri());
@@ -484,6 +484,7 @@ private static Dependency amend(Dependency dependency, EnumSet modifie
*
* @see ForModule#writeModulePOM()
*/
+ @SuppressWarnings("deprecation")
Path writeParentPOM(final Archive packageHierarchy) throws IOException {
Path pomFile = derivePathToPOM(packageHierarchy.jarFile);
Model.Builder builder = Model.newBuilder(projectModel, true);
@@ -520,9 +521,9 @@ private static Path derivePathToPOM(final Path jarFile) {
*/
private static void writePOM(Model model, Path file) throws IOException, XMLStreamException {
try (BufferedWriter out = Files.newBufferedWriter(file)) {
- var sw = new MavenStaxWriter();
- sw.setAddLocationInformation(false);
- sw.write(out, model);
+ var xmlWriter = new MavenStaxWriter();
+ xmlWriter.setAddLocationInformation(false);
+ xmlWriter.write(out, model);
out.newLine();
}
}
diff --git a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
index 8a663aa8..83f94df6 100644
--- a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
+++ b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
@@ -74,14 +74,14 @@ final class TimestampCheck extends SimpleFileVisitor {
/**
* Files found in the JAR file but not yet traversed by the file visitor.
* Files are added lazily when needed, and removed as soon as they have been traversed.
- * Path are absolute (resolved with {@link #classesDir}).
+ * Paths are absolute (resolved with {@link #classesDir}).
* For each entry, the associated value is whether the path is a directory.
*/
private final Map filesInJAR;
/**
* Some of the files in the build directory. This list contains only the files for which we have already
- * verified the timestamp. We store them in a separate list for avoiding to check the timestamp twice.
+ * verified the timestamp. We store them in a separate list to avoid checking to check the timestamp twice.
* We need this list because we still need to verify if the files are in the {@link #jarFile}.
* For each entry, the associated value is whether the path is a directory.
*/
@@ -89,7 +89,7 @@ final class TimestampCheck extends SimpleFileVisitor {
/**
* Whether at least one file is more recent than the JAR file.
- * The scan of files will stop quickly after this flag become {@code true}.
+ * The scan of files will stop soon after this flag become {@code true}.
*/
private boolean hasUpdates;
@@ -128,7 +128,7 @@ boolean isUpdated(final Path file, final BasicFileAttributes attributes, final b
}
/**
- * Checks if the JAR file contains all the given files, no extra entry, and no outdated entry.
+ * Checks if the JAR file contains all the given files, no extra entries, and no outdated entry.
*
* @param fileSets pairs of base directory and files potentially relative to the base directory
* @return whether the JAR file is up-to-date
diff --git a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
index 1ef5027a..d50e416a 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
@@ -306,8 +306,8 @@ Archive newArchive(final String moduleName, final Runtime.Version version, final
* The derived POM files are the intersections of the project POM with the
* content of {@code module-info.class} files.
*
- *
Prerequisites
- * The {@link FileCollector#prune(boolean)} method should have been invoked once before to invoke this method.
+ *
Prerequisites:
+ * The {@link FileCollector#prune(boolean)} method should have been invoked once before to invoke this method.
*
* @param files the result of scanning the build directory for listing the files or directories to archive
* @return the paths to the created archive files
@@ -402,7 +402,7 @@ void writeSingleJAR(final FileCollector files, final Archive archive) throws IOE
logger.warn("Reproducible build requires Java " + JDK_SUPPORT_DATE + " or later.");
}
}
- archive.arguments(arguments);
+ archive.addArgumentsTo(arguments);
/*
* Execute the `jar` tool with arguments determined by the values dispatched
* in the various fields of the `Archive`. Information and error essages are logged.
@@ -429,7 +429,7 @@ void writeSingleJAR(final FileCollector files, final Archive archive) throws IOE
* we skip the post-creation validation pass; the archive was already created
* successfully by the `--create` pass above.
*/
- if (archive.validate(validate, arguments)) {
+ if (archive.addValidateArgumentsTo(validate, arguments)) {
final int version = Runtime.version().feature();
if (version >= JDK_FIXING_JAR_VALIDATE) {
int status = executeJarTool();
diff --git a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
index 8f5b4498..279b3d30 100644
--- a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
+++ b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
@@ -90,7 +90,7 @@ void owningModuleClaimsMainClassAndRemovesItFromManifest() {
}
/**
- * Verifies that {@link Archive#setMainClass(Manifest)} does not take ownership fo
+ * Verifies that {@link Archive#setMainClass(Manifest)} does not take ownership of
* a module-qualified {@code "module/Class"} main class when the module name does not match.
* The {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
* The standard JAR specification accepts only the {@code "foo.MainFile"} class name.
@@ -139,7 +139,7 @@ private static void assertOwnership(String first, String second, boolean ownerIs
}
/**
- * Tests that {@code FileSet} association to target release is consistent regardless creation order.
+ * Tests that {@code FileSet} association to target release is consistent regardless of creation order.
* Verifies that the base (version-less) release binds to the true {@code } directory even
* when the {@link Archive} was first created from a {@code META-INF/versions-modular//}
* directory (which happens when the file-tree walk visits the version directory first).
@@ -219,7 +219,7 @@ private static List argsAfterAdding(Path directory, Path... filesInOrder
base.add(f, null, false);
}
var args = new ArrayList();
- archive.arguments(args);
+ archive.addArgumentsTo(args);
return args;
}
From b09021426c1259d821634af39f7cc7c831deb0e0 Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Thu, 3 Sep 2026 20:15:56 +0900
Subject: [PATCH 24/25] Remove the `title` attribute in `` because it is
not accepted by Javadoc.
---
.../org/apache/maven/plugins/jar/AbstractJarMojo.java | 8 ++++----
src/main/java/org/apache/maven/plugins/jar/Archive.java | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
index 7dd2945e..00075cde 100644
--- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java
@@ -44,7 +44,7 @@
import org.apache.maven.shared.archiver.MavenArchiver;
/**
- * Base class for creating a JAR file from project classes.
+ * Base class for creating a JAR file from project classes.
*
* @author Emmanuel Venisse
* @author Martin Desruisseaux
@@ -74,13 +74,13 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
protected String[] excludes;
/**
- * Directory containing the generated JAR files.
+ * Directory containing the generated JAR files.
*/
@Parameter(defaultValue = "${project.build.directory}", required = true)
protected Path outputDirectory;
/**
- * Name of the generated JAR file.
+ * Name of the generated JAR file.
* The default value is {@code "${project.build.finalName}"},
* which itself defaults to {@code "${artifactId}-${version}"}.
* Ignored if the Maven sub-project to archive uses module hierarchy.
@@ -149,7 +149,7 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
* the SOURCE_DATE_EPOCH
* environment variable is used as a fallback value,
* to ease forcing Reproducible Build externally when the build has not enabled it natively in
- * POM.
+ * POM.
*
*
This property is supported only in Java 19 or later.
*
diff --git a/src/main/java/org/apache/maven/plugins/jar/Archive.java b/src/main/java/org/apache/maven/plugins/jar/Archive.java
index 3c7c0c65..75e1001a 100644
--- a/src/main/java/org/apache/maven/plugins/jar/Archive.java
+++ b/src/main/java/org/apache/maven/plugins/jar/Archive.java
@@ -57,7 +57,7 @@ final class Archive {
Path pomFile;
/**
- * The JAR file to create. Can be an existing file,
+ * The JAR file to create. Can be an existing file,
* in which case the file creation can be skipped if the file is still up-to-date.
*/
@Nonnull
From 40169c8709022c071a438d0d214a508055a62970 Mon Sep 17 00:00:00 2001
From: Martin Desruisseaux
Date: Mon, 7 Sep 2026 20:47:31 +0900
Subject: [PATCH 25/25] Apply changes requested on the pull request, mostly
English fixes and variable renaming.
---
src/it/jar-without-sources/pom.xml | 2 +-
.../maven/plugins/jar/DirectoryRole.java | 19 ++++++++++++-------
.../maven/plugins/jar/ExcludedFiles.java | 10 +++++-----
.../maven/plugins/jar/FileCollector.java | 18 +++++++++---------
.../maven/plugins/jar/TimestampCheck.java | 5 ++---
.../maven/plugins/jar/ToolExecutor.java | 6 +++---
.../apache/maven/plugins/jar/ArchiveTest.java | 19 ++++++++++---------
7 files changed, 42 insertions(+), 37 deletions(-)
diff --git a/src/it/jar-without-sources/pom.xml b/src/it/jar-without-sources/pom.xml
index 3b990ad5..3b9e3a9a 100644
--- a/src/it/jar-without-sources/pom.xml
+++ b/src/it/jar-without-sources/pom.xml
@@ -27,7 +27,7 @@
This project has neither `src/main` nor `src/test`, so nothing is compiled
and the `target` directory is never created by an earlier lifecycle phase.
Versions 3.x of the JAR plugin still produced an empty, manifest-only JAR in that case.
- This IT verifies that versions 4.x of the JAR plugin has the same behavior for compatibility purposes.
+ This IT verifies that versions 4.x of the JAR plugin have the same behavior for compatibility purposes.
NOTE: it does not mean that it is a good idea to produce an empty JAR in such case.
It would also be a reasonable choice if a future version decide to fail the build instead.
diff --git a/src/main/java/org/apache/maven/plugins/jar/DirectoryRole.java b/src/main/java/org/apache/maven/plugins/jar/DirectoryRole.java
index 79d3c5f3..461948f0 100644
--- a/src/main/java/org/apache/maven/plugins/jar/DirectoryRole.java
+++ b/src/main/java/org/apache/maven/plugins/jar/DirectoryRole.java
@@ -24,14 +24,14 @@
enum DirectoryRole {
/**
* The root directory. This is usually {@code "target/classes"}.
- * The next locations can be {@link #META_INF}, {@link #NAMED_MODULE} or {@link #RESOURCES}.
+ * The next directory roles can be {@link #META_INF}, {@link #NAMED_MODULE} or {@link #RESOURCES}.
*/
ROOT,
/**
* The {@code "META-INF"} or {@code "/META-INF"} directory.
* This is part of the JAR specification.
- * The next locations can be {@link #VERSIONS} or {@link #VERSIONS_MODULAR}.
+ * The next directory roles can be {@link #VERSIONS} or {@link #VERSIONS_MODULAR}.
*/
META_INF,
@@ -39,7 +39,7 @@ enum DirectoryRole {
* The {@code "META-INF/versions"} or {@code "/META-INF/versions"} directory.
* This is part of the JAR specification, except the {@code } prefix.
* The sub-directories are named according to Java releases such as "21".
- * The next location can only be {@link #RESOURCES}.
+ * The next directory role can only be {@link #RESOURCES}.
*/
VERSIONS,
@@ -47,21 +47,26 @@ enum DirectoryRole {
* The Maven-specific {@code "META-INF/versions-modular"} directory.
* {@code "/META-INF/versions-modular"} is not forbidden, but does not make sense.
* The sub-directories are named according to Java releases such as "21".
- * The next location can only be {@link #MODULES}.
+ * The next directory role can only be {@link #MODULES}.
*/
VERSIONS_MODULAR,
/**
- * The Maven-specific {@code "META-INF/versions-modular"} directory.
+ * A directory other than the root directory which contains many modules.
+ * This is typically a {@code "META-INF/versions-modular/"} directory
+ * containing all modules for Java version n.
* All sub-directories must have the name of a Java module.
- * The next location can only be {@link #NAMED_MODULE}.
+ * The next directory role can only be {@link #NAMED_MODULE}.
+ *
+ *
The root directory may also contain many modules,
+ * but is nevertheless handled by a separated enumeration value {@link #ROOT}.
*/
MODULES,
/**
* The root of a single Java module in a module hierarchy.
* The name of this directory is the Java module name.
- * The next location can only be {@link #RESOURCES}.
+ * The next directory role can only be {@link #RESOURCES}.
*/
NAMED_MODULE,
diff --git a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
index c0fff941..7f1dca3a 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java
@@ -29,14 +29,14 @@
* A list of files to temporarily move outside the directory to package in a JAR archive.
* This class is needed when the {@link AbstractJarMojo} configuration has include or exclude filters.
* Excluded files are temporarily moved outside the directory to archive.
- * We move these files for making possible to specify the whole directory to the {@code jar} tool.
+ * We move these files to make it possible to pass an entire directory to the {@code jar} tool.
* This approach is used instead of enumerating files in arguments given to the {@code jar} tool because
* such enumeration cannot contain directory entries (otherwise the whole directory would be included).
* Some software such as Spring applications component scan relies on the presence of directory entries.
*/
final class ExcludedFiles implements Closeable {
/**
- * The paths of files or directories to temporarily move in another directory.
+ * The paths of files or directories to temporarily move to another directory.
*/
private final Path[] original;
@@ -61,11 +61,11 @@ final class ExcludedFiles implements Closeable {
private final int indexOfFirstDirectory;
/**
- * Creates a new list of files to move in a temporary directory.
+ * Creates a new list of files to move to a temporary directory.
*
* @param directory the directory which was scanned for files to include in the JAR
- * @param excludedFiles paths of files to temporarily move in another directory
- * @param excludedDirectories paths of directories to temporarily move in another directory
+ * @param excludedFiles paths of files to temporarily move to another directory
+ * @param excludedDirectories paths of directories to temporarily move to another directory
* @throws IOException if an error occurred while creating the temporary directory.
*/
ExcludedFiles(Path directory, List excludedFiles, List excludedDirectories) throws IOException {
diff --git a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
index 3df2aa98..cc5e197c 100644
--- a/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
+++ b/src/main/java/org/apache/maven/plugins/jar/FileCollector.java
@@ -37,8 +37,8 @@
import org.apache.maven.api.services.PathMatcherFactory;
/**
- * Dispatch the files from the output directory into the JAR files to create.
- * Instead of just archiving as-is the content of the output directory, this class separates
+ * Distributes the files from the output directory into the JAR files to create.
+ * Instead of just archiving the content of the output directory as-is, this class separates
* the following subdirectories to the options listed below:
*
*
@@ -52,13 +52,13 @@
*
*
*
- * The reason for using the {@code --release} and {@code --manifest} options instead of adding explicitly
- * the entries is because the options allow the {@code jar} tool to perform additional verifications.
- * For example, when using the {@code --release} option, {@code jar} verifies the API compatibility.
+ * The reason for using the {@code --release} and {@code --manifest} options instead of adding the
+ * entries explicitly is because the options allow the {@code jar} tool to perform additional verifications.
+ * For example, when using the {@code --release} option, {@code jar} verifies API compatibility.
*/
final class FileCollector extends SimpleFileVisitor {
/**
- * The file to check for deciding whether the JAR is modular.
+ * The file to check to decide whether the JAR is modular.
*/
static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class";
@@ -106,7 +106,7 @@ final class FileCollector extends SimpleFileVisitor {
/**
* Files to exclude. These files will be moved to a temporary location
- * for allowing {@link ToolExecutor} to specify whole directories to the {@code jar} tool.
+ * to allow {@link ToolExecutor} to specify whole directories to the {@code jar} tool.
* Specifying whole directories is preferable to enumerating the files because otherwise,
* the generated JAR file contains only entries for the files and is missing
* entries for the directories.
@@ -119,7 +119,7 @@ final class FileCollector extends SimpleFileVisitor {
/**
* Directories to exclude. This field serves the same purpose as {@link #excludedFiles},
- * but where the sources a directories instead of files.
+ * but where the sources are directories instead of files.
*/
@Nullable
private final List excludedDirectories;
@@ -146,7 +146,7 @@ final class FileCollector extends SimpleFileVisitor {
private Archive currentModule;
/**
- * The module and target Java release currently being scanned. This field is updated every times that
+ * The module and target Java release currently being scanned. This field is updated every time that
* {@code FileCollector} visits a new module directory or in a new target Java release for a given module.
*/
@Nonnull
diff --git a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
index 83f94df6..c5737647 100644
--- a/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
+++ b/src/main/java/org/apache/maven/plugins/jar/TimestampCheck.java
@@ -40,9 +40,8 @@
* The latter can only occur if {@link FileCollector} has no {@code PathMatcher}.
* Therefore, this class uses no {@code PathMatcher}.
*
- *
Ignore files
- * The {@code META-INF/MANIFEST.MF} file and the {@code META-INF/maven/} directory are ignored.
- * See {@link #isIgnored(Path)} for the rational.
+ *
The {@code META-INF/MANIFEST.MF} file and the {@code META-INF/maven/} directory are ignored.
+ * See {@link #isIgnored(Path)} for the rational.
*/
final class TimestampCheck extends SimpleFileVisitor {
/**
diff --git a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
index d50e416a..9c2f1cde 100644
--- a/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
+++ b/src/main/java/org/apache/maven/plugins/jar/ToolExecutor.java
@@ -60,7 +60,7 @@ final class ToolExecutor {
/**
* First JDK feature version whose {@code jar} tool support the {@code --date} option.
*/
- private static final int JDK_SUPPORT_DATE = 19;
+ private static final int JDK_SUPPORTING_DATE = 19;
/**
* The {@value} attribute. Its value is automatically generated by {@link Manifest},
@@ -395,11 +395,11 @@ void writeSingleJAR(final FileCollector files, final Archive archive) throws IOE
arguments.add("--no-compress");
}
if (outputTimestamp != null) {
- if (Runtime.version().feature() >= JDK_SUPPORT_DATE) {
+ if (Runtime.version().feature() >= JDK_SUPPORTING_DATE) {
arguments.add("--date");
arguments.add(outputTimestamp);
} else {
- logger.warn("Reproducible build requires Java " + JDK_SUPPORT_DATE + " or later.");
+ logger.warn("Reproducible build requires Java " + JDK_SUPPORTING_DATE + " or later.");
}
}
archive.addArgumentsTo(arguments);
diff --git a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
index 279b3d30..976bd4a2 100644
--- a/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
+++ b/src/test/java/org/apache/maven/plugins/jar/ArchiveTest.java
@@ -55,21 +55,21 @@ private static Archive archive(String moduleName, Runtime.Version version, Path
* @return a new manifest with the given attribute value
*/
private static Manifest manifestWithMainClass(String value) {
- Manifest m = new Manifest();
- Attributes attributes = m.getMainAttributes();
+ Manifest manifest = new Manifest();
+ Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.MAIN_CLASS, value);
- return m;
+ return manifest;
}
/**
* Returns the value of the main class attribute.
*
- * @param m the manifest from which to get the value
+ * @param manifest the manifest from which to get the value
* @return the main class attribute value, or {@code null} if none
*/
- private static Object mainClassOf(Manifest m) {
- return m.getMainAttributes().get(Attributes.Name.MAIN_CLASS);
+ private static Object mainClassOf(Manifest manifest) {
+ return manifest.getMainAttributes().get(Attributes.Name.MAIN_CLASS);
}
/**
@@ -122,16 +122,17 @@ void mainClassAssignmentIsIndependentOfModuleOrder() {
*
*
The {@code "foo.bar/"} prefix in this test (the module name) is a Maven extension.
* The standard JAR specification accepts only the {@code "foo.MainFile"} class name.
- * This extension is used by the plugin for identifying in which JAR file to add this
+ * This extension is used by the plugin to identify which JAR file to add this
* {@code Main-Class} manifest entry.
*/
private static void assertOwnership(String first, String second, boolean ownerIsFirst) {
+ boolean ownerIsSecond = !ownerIsFirst;
final Path path = Path.of(".");
final Manifest shared = manifestWithMainClass("foo.bar/foo.MainFile");
final Manifest m1 = new Manifest(shared);
final Manifest m2 = new Manifest(shared);
assertEquals(ownerIsFirst, archive(first, null, path).setMainClass(m1));
- assertEquals(!ownerIsFirst, archive(second, null, path).setMainClass(m2));
+ assertEquals(ownerIsSecond, archive(second, null, path).setMainClass(m2));
// Per-module copies must leave the shared plugin manifest untouched.
assertEquals("foo.bar/foo.MainFile", mainClassOf(shared));
assertNull(mainClassOf(m1));
@@ -166,7 +167,7 @@ void baseReleaseBindingIsIndependentOfDirectoryOrder() {
*
*
Historical note
* In our tests, it seems that the first JAR entry after the {@code -C} option
- * must be relative, and only that file. Furthermore, it seems that this file must be the
+ * must be relative, and only that file. Furthermore, it seems that this filename must be the
* shortest. We tried to apply this heuristic rules in a branch, but it does not save a lot
* of characters compared to repeating {@code -C}.
*/