Skip to content

Commit dc09f23

Browse files
committed
Replace org.apache.maven.shared.model.fileset by a temporary copy of the PathSelector class of Maven core.
This is for the transition to Maven 4.0.0 API. As a side effect, this change may improve performances because the new implementation based on `FileVisitor` makes some efforts for avoiding to scan unnecessary directories.
1 parent 08d2091 commit dc09f23

5 files changed

Lines changed: 777 additions & 60 deletions

File tree

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,6 @@
144144
<version>${mavenVersion}</version>
145145
<scope>provided</scope>
146146
</dependency>
147-
<dependency>
148-
<groupId>org.apache.maven.shared</groupId>
149-
<artifactId>file-management</artifactId>
150-
<version>${mavenFileManagementVersion}</version>
151-
</dependency>
152147
<dependency>
153148
<groupId>org.apache.maven.shared</groupId>
154149
<artifactId>maven-archiver</artifactId>

src/it/MJAR-260-invalid-automatic-module-name/verify.bsh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ try
4545
String[] snippets = new String[] {
4646
"[INFO] BUILD FAILURE",
4747
"[ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin",
48-
"Caused by: org.apache.maven.api.plugin.MojoException: Error assembling JAR",
48+
"Caused by: org.apache.maven.api.plugin.MojoException: Error while assembling the JAR file.",
4949
"Caused by: org.codehaus.plexus.archiver.jar.ManifestException: Invalid automatic module name: 'in-valid.name.with.new.keyword'"
5050
};
5151

src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java

Lines changed: 40 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
*/
1919
package org.apache.maven.plugins.jar;
2020

21-
import java.io.File;
2221
import java.io.IOException;
2322
import java.nio.file.Files;
2423
import java.nio.file.Path;
2524
import java.util.Arrays;
25+
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Objects;
2828
import java.util.jar.Attributes;
@@ -39,8 +39,6 @@
3939
import org.apache.maven.shared.archiver.MavenArchiveConfiguration;
4040
import org.apache.maven.shared.archiver.MavenArchiver;
4141
import org.apache.maven.shared.archiver.MavenArchiverException;
42-
import org.apache.maven.shared.model.fileset.FileSet;
43-
import org.apache.maven.shared.model.fileset.util.FileSetManager;
4442
import org.codehaus.plexus.archiver.Archiver;
4543
import org.codehaus.plexus.archiver.jar.JarArchiver;
4644

@@ -55,8 +53,6 @@ public abstract class AbstractJarMojo implements org.apache.maven.api.plugin.Moj
5553

5654
private static final String[] DEFAULT_INCLUDES = new String[] {"**/**"};
5755

58-
private static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class";
59-
6056
/**
6157
* List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
6258
* is being packaged into the JAR.
@@ -222,48 +218,41 @@ protected Path getJarFile(Path basedir, String resultFinalName, String classifie
222218
}
223219

224220
/**
225-
* Generates the JAR.
221+
* Generates the <abbr>JAR</abbr> file.
226222
*
227223
* @return the path to the created archive file
228-
* @throws MojoException in case of an error
224+
* @throws IOException in case of an error while reading a file or writing in the JAR file
229225
*/
230-
public Path createArchive() throws MojoException {
226+
@SuppressWarnings("checkstyle:UnusedLocalVariable") // Checkstyle bug: does not detect `includedFiles` usage.
227+
public Path createArchive() throws IOException {
228+
String archiverName = "jar";
231229
Path basedir = outputDirectory != null
232230
? outputDirectory
233231
: Path.of(project.getBuild().getDirectory());
234232
String resultFinalName =
235233
finalName != null ? finalName : project.getBuild().getFinalName();
236234
Path jarFile = getJarFile(basedir, resultFinalName, getClassifier());
237-
238-
FileSetManager fileSetManager = new FileSetManager();
239-
FileSet jarContentFileSet = new FileSet();
240-
jarContentFileSet.setDirectory(getClassesDirectory().toAbsolutePath().toString());
241-
jarContentFileSet.setIncludes(Arrays.asList(getIncludes()));
242-
jarContentFileSet.setExcludes(Arrays.asList(getExcludes()));
243-
244-
String[] includedFiles = fileSetManager.getIncludedFiles(jarContentFileSet);
245-
246-
if (detectMultiReleaseJar
247-
&& Arrays.stream(includedFiles)
248-
.anyMatch(
249-
p -> p.startsWith("META-INF" + File.separatorChar + "versions" + File.separatorChar))) {
250-
getLog().debug("Adding 'Multi-Release: true' manifest entry.");
251-
archive.addManifestEntry(Attributes.Name.MULTI_RELEASE.toString(), "true");
235+
Path classesDirectory = getClassesDirectory();
236+
if (Files.exists(classesDirectory)) {
237+
var includedFiles = new org.apache.maven.plugins.jar.Archiver(
238+
classesDirectory,
239+
(includes != null) ? Arrays.asList(includes) : List.of(),
240+
(excludes != null && excludes.length > 0)
241+
? Arrays.asList(excludes)
242+
: List.of(AbstractJarMojo.DEFAULT_EXCLUDES));
243+
244+
var scanner = includedFiles.new VersionScanner(detectMultiReleaseJar);
245+
if (Files.exists(classesDirectory)) {
246+
Files.walkFileTree(classesDirectory, scanner);
247+
}
248+
if (detectMultiReleaseJar && scanner.detectedMultiReleaseJAR) {
249+
getLog().debug("Adding 'Multi-Release: true' manifest entry.");
250+
archive.addManifestEntry(Attributes.Name.MULTI_RELEASE.toString(), "true");
251+
}
252+
if (scanner.containsModuleDescriptor) {
253+
archiverName = "mjar";
254+
}
252255
}
253-
254-
// May give false positives if the files is named as module descriptor
255-
// but is not in the root of the archive or in the versioned area
256-
// (and hence not actually a module descriptor).
257-
// That is fine since the modular Jar archiver will gracefully
258-
// handle such case.
259-
// And also such case is unlikely to happen as file ending
260-
// with "module-info.class" is unlikely to be included in Jar file
261-
// unless it is a module descriptor.
262-
boolean containsModuleDescriptor =
263-
Arrays.stream(includedFiles).anyMatch(p -> p.endsWith(MODULE_DESCRIPTOR_FILE_NAME));
264-
265-
String archiverName = containsModuleDescriptor ? "mjar" : "jar";
266-
267256
MavenArchiver archiver = new MavenArchiver();
268257
archiver.setCreatedBy("Maven JAR Plugin", "org.apache.maven.plugins", "maven-jar-plugin");
269258
archiver.setBuildJdkSpecDefaultEntry(archive.getManifest().isAddBuildEnvironmentEntries());
@@ -275,23 +264,16 @@ public Path createArchive() throws MojoException {
275264

276265
archive.setForced(forceCreation);
277266

278-
try {
279-
Path contentDirectory = getClassesDirectory();
280-
if (!Files.exists(contentDirectory)) {
281-
if (!forceCreation) {
282-
getLog().warn("JAR will be empty - no content was marked for inclusion!");
283-
}
284-
} else {
285-
archiver.getArchiver().addDirectory(contentDirectory.toFile(), getIncludes(), getExcludes());
267+
Path contentDirectory = getClassesDirectory();
268+
if (!Files.exists(contentDirectory)) {
269+
if (!forceCreation) {
270+
getLog().warn("JAR will be empty - no content was marked for inclusion!");
286271
}
287-
288-
archiver.createArchive(session, project, archive);
289-
290-
return jarFile;
291-
} catch (Exception e) {
292-
// TODO: improve error handling
293-
throw new MojoException("Error assembling JAR", e);
272+
} else {
273+
archiver.getArchiver().addDirectory(contentDirectory.toFile(), getIncludes(), getExcludes());
294274
}
275+
archiver.createArchive(session, project, archive);
276+
return jarFile;
295277
}
296278

297279
/**
@@ -304,8 +286,12 @@ public void execute() throws MojoException {
304286
if (skipIfEmpty && isEmpty(getClassesDirectory())) {
305287
getLog().info(String.format("Skipping packaging of the %s.", getType()));
306288
} else {
307-
Path jarFile = createArchive();
308-
289+
Path jarFile;
290+
try {
291+
jarFile = createArchive();
292+
} catch (Exception e) {
293+
throw new MojoException("Error while assembling the JAR file.", e);
294+
}
309295
if (attach) {
310296
ProducedArtifact artifact;
311297
String classifier = getClassifier();
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugins.jar;
20+
21+
import java.io.IOException;
22+
import java.nio.file.FileVisitResult;
23+
import java.nio.file.Path;
24+
import java.nio.file.PathMatcher;
25+
import java.nio.file.SimpleFileVisitor;
26+
import java.nio.file.attribute.BasicFileAttributes;
27+
import java.util.Collection;
28+
29+
import org.apache.maven.api.annotations.Nonnull;
30+
31+
/**
32+
* Creates the JAR file.
33+
*
34+
* TODO: this is a work in progress.
35+
*/
36+
final class Archiver extends SimpleFileVisitor<Path> {
37+
/**
38+
* Combination of includes and excludes path matchers applied on files.
39+
*/
40+
@Nonnull
41+
private final PathMatcher fileMatcher;
42+
43+
/**
44+
* Combination of includes and excludes path matchers applied on directories.
45+
*/
46+
@Nonnull
47+
private final PathMatcher directoryMatcher;
48+
49+
/**
50+
* Creates a new archiver.
51+
*
52+
* @param directory the base directory of the files to archive
53+
* @param includes the patterns of the files to include, or null or empty for including all files
54+
* @param excludes the patterns of the files to exclude, or null or empty for no exclusion
55+
*/
56+
Archiver(Path directory, Collection<String> includes, Collection<String> excludes) {
57+
fileMatcher = PathSelector.of(directory, includes, excludes);
58+
if (fileMatcher instanceof PathSelector ps) {
59+
if (ps.canFilterDirectories()) {
60+
directoryMatcher = (path) -> ps.couldHoldSelected(path);
61+
return;
62+
}
63+
}
64+
directoryMatcher = PathSelector.INCLUDES_ALL;
65+
}
66+
67+
/**
68+
* A scanner of {@code module-info.class} file and {@code META-INF/versions/*} directories.
69+
* This is used only when we need to auto-detect whether the JAR is modular or multiè-release.
70+
* This is not needed anymore for projects using the Maven 4 {@code <source>} elements.
71+
*/
72+
final class VersionScanner extends SimpleFileVisitor<Path> {
73+
/**
74+
* The file to check for deciding whether the JAR is a modular JAR.
75+
*/
76+
private static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class";
77+
78+
/**
79+
* The directory level in the {@code ./META-INF/versions/###/} path.
80+
* The root directory is at level 0 before we enter in that directory.
81+
* After the execution of {@code preVisitDirectory} (i.e., at the time of visiting files), values are:
82+
*
83+
* <ol>
84+
* <li>for any file in the {@code ./} root classes directory.</li>
85+
* <li>for any file in the {@code ./META-INF/} directory.</li>
86+
* <li>for any file in the {@code ./META-INF/versions/} directory (i.e., any version number).</li>
87+
* <li>for any file in the {@code ./META-INF/versions/###/} directory (i.e., any versioned file)</li>
88+
* </ol>
89+
*/
90+
private int level;
91+
92+
/**
93+
* First level of versioned files in a {@code ./META-INF/versions/###/} directory.
94+
*/
95+
private static final int LEVEL_OF_VERSIONED_FILES = 4;
96+
97+
/**
98+
* Whether a {@code META-INF/versions/} directory has been found.
99+
* The value of this flag is invalid if this scanner was constructed
100+
* with a {@code detectMultiReleaseJar} argument value of {@code true}.
101+
*/
102+
boolean detectedMultiReleaseJAR;
103+
104+
/**
105+
* Whether a {@code module-info.class} file has been found.
106+
*/
107+
boolean containsModuleDescriptor;
108+
109+
/**
110+
* Creates a scanner with all flags initially {@code false}.
111+
*
112+
* @param detectMultiReleaseJar whether to enable the detection of multi-release JAR
113+
*/
114+
VersionScanner(boolean detectMultiReleaseJar) {
115+
detectedMultiReleaseJAR = !detectMultiReleaseJar; // Not actually true, but will cause faster stop.
116+
}
117+
118+
/**
119+
* Returns {@code true} if the given directory is one of the parts of {@code ./META-INF/versions/*}.
120+
* The {@code .} directory is at level 0, {@code META-INF} at level 1, {@code versions} at level 2,
121+
* and one of the versions at level 3. Files at level 4 are versioned class files.
122+
*/
123+
@Override
124+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
125+
if (detectedMultiReleaseJAR && level >= LEVEL_OF_VERSIONED_FILES) {
126+
// When searching only for `module-info.class`, we do not need to go further than that level.
127+
return (level > LEVEL_OF_VERSIONED_FILES)
128+
? FileVisitResult.SKIP_SIBLINGS
129+
: FileVisitResult.SKIP_SUBTREE;
130+
}
131+
if (directoryMatcher.matches(dir)) {
132+
String expected =
133+
switch (level) {
134+
case 1 -> "META-INF";
135+
case 2 -> "versions";
136+
default -> null;
137+
};
138+
if (expected == null || dir.endsWith(expected)) {
139+
level++;
140+
return FileVisitResult.CONTINUE;
141+
}
142+
}
143+
return FileVisitResult.SKIP_SUBTREE;
144+
}
145+
146+
/**
147+
* Decrements our count of directory levels after visiting a directory.
148+
*/
149+
@Override
150+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
151+
level--;
152+
return super.postVisitDirectory(dir, exc);
153+
}
154+
155+
/**
156+
* Updates the flags for the given files. This method terminates the
157+
* scanning if it detects that there is no new information to collect.
158+
*/
159+
@Override
160+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
161+
if (fileMatcher.matches(file)) {
162+
if (level == 1 || level == LEVEL_OF_VERSIONED_FILES) {
163+
// Root directory or a `META-INF/versions/###/` directory.
164+
containsModuleDescriptor |= file.endsWith(MODULE_DESCRIPTOR_FILE_NAME);
165+
}
166+
detectedMultiReleaseJAR |= (level >= LEVEL_OF_VERSIONED_FILES);
167+
if (detectedMultiReleaseJAR) {
168+
if (containsModuleDescriptor) {
169+
return FileVisitResult.TERMINATE;
170+
}
171+
if (level > LEVEL_OF_VERSIONED_FILES) {
172+
return FileVisitResult.SKIP_SIBLINGS;
173+
}
174+
}
175+
}
176+
return FileVisitResult.CONTINUE;
177+
}
178+
}
179+
180+
/**
181+
* Determines if the given directory should be scanned for files to archive.
182+
*/
183+
@Override
184+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
185+
return directoryMatcher.matches(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
186+
}
187+
188+
/**
189+
* Archives a file in a directory.
190+
*/
191+
@Override
192+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
193+
if (fileMatcher.matches(file)) {
194+
// TODO
195+
}
196+
return FileVisitResult.CONTINUE;
197+
}
198+
}

0 commit comments

Comments
 (0)