diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java
index 42aede9d89fa..39a5c46e6ae6 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Language.java
@@ -45,6 +45,19 @@ public interface Language extends ExtensibleEnum {
*/
Language NONE = language("none");
+ /**
+ * The "resources" language. This is used for files such as images to provide in the output.
+ */
+ Language RESOURCES = language("resources");
+
+ /**
+ * The "script" language. Provided for compatibility with Maven 3.
+ *
+ * @deprecated Use {@link #RESOURCES} instead.
+ */
+ @Deprecated
+ Language SCRIPT = language("script");
+
// TODO: this should be moved out from here to Java Support (builtin into core)
Language JAVA_FAMILY = language("java");
}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
index 219e21f2469b..27cf17908cae 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
@@ -49,25 +49,25 @@
public interface Project {
/**
- * Returns the project groupId.
+ * {@return the project groupId}.
*/
@Nonnull
String getGroupId();
/**
- * Returns the project artifactId.
+ * {@return the project artifactId}.
*/
@Nonnull
String getArtifactId();
/**
- * Returns the project version.
+ * {@return the project version}.
*/
@Nonnull
String getVersion();
/**
- * Returns the project packaging.
+ * {@return the project packaging}.
*
* Note: unlike in legacy code, logical checks against string representing packaging (returned by this method)
* are NOT recommended (code like {@code "pom".equals(project.getPackaging)} must be avoided). Use method
@@ -79,7 +79,7 @@ public interface Project {
Packaging getPackaging();
/**
- * Returns the project language. It is by default determined by {@link #getPackaging()}.
+ * {@return the project language}. It is by default determined by {@link #getPackaging()}.
*
* @see #getPackaging()
*/
@@ -89,7 +89,7 @@ default Language getLanguage() {
}
/**
- * Returns the project POM artifact, which is the artifact of the POM of this project. Every project have a POM
+ * {@return the project POM artifact}, which is the artifact of the POM of this project. Every project have a POM
* artifact, even if the existence of backing POM file is NOT a requirement (i.e. for some transient projects).
*
* @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact)
@@ -100,7 +100,7 @@ default ProducedArtifact getPomArtifact() {
}
/**
- * Returns the project main artifact, which is the artifact produced by this project build, if applicable.
+ * {@return the project main artifact}, which is the artifact produced by this project build, if applicable.
* This artifact MAY be absent if the project is actually not producing any main artifact (i.e. "pom" packaging).
*
* @see #getPackaging()
@@ -113,7 +113,7 @@ default Optional getMainArtifact() {
}
/**
- * Returns the project artifacts as immutable list. Elements are the project POM artifact and the artifact
+ * {@return the project artifacts as immutable list}. Elements are the project POM artifact and the artifact
* produced by this project build, if applicable. Hence, the returned list may have one or two elements
* (never less than 1, never more than 2), depending on project packaging.
*
@@ -129,13 +129,15 @@ default Optional getMainArtifact() {
List getArtifacts();
/**
- * Returns the project model.
+ * {@return the project model}.
*/
@Nonnull
Model getModel();
/**
* Shorthand method.
+ *
+ * @return the build element of the project model
*/
@Nonnull
default Build getBuild() {
@@ -170,19 +172,19 @@ default Build getBuild() {
Path getBasedir();
/**
- * Returns the project direct dependencies (directly specified or inherited).
+ * {@return the project direct dependencies (directly specified or inherited)}.
*/
@Nonnull
List getDependencies();
/**
- * Returns the project managed dependencies (directly specified or inherited).
+ * {@return the project managed dependencies (directly specified or inherited)}.
*/
@Nonnull
List getManagedDependencies();
/**
- * Returns the project ID, usable as key.
+ * {@return the project ID, usable as key}.
*/
@Nonnull
default String getId() {
@@ -216,6 +218,7 @@ default String getId() {
* Gets the root directory of the project, which is the parent directory
* containing the {@code .mvn} directory or flagged with {@code root="true"}.
*
+ * @return the root directory of the project
* @throws IllegalStateException if the root directory could not be found
* @see Session#getRootDirectory()
*/
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
new file mode 100644
index 000000000000..35a18c82e630
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
@@ -0,0 +1,111 @@
+/*
+ * 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.api;
+
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A root directory of source files.
+ * The sources may be Java main classes, test classes, resources or anything else identified by the scope.
+ */
+public interface SourceRoot {
+ /**
+ * {@return the root directory where the sources are stored}.
+ * The path is relative to the POM file.
+ */
+ Path directory();
+
+ /**
+ * {@return the list of pattern matchers for the files to include}.
+ * The default implementation returns an empty list, which means to apply a language-dependent pattern.
+ * For example, for the Java language, the pattern includes all files with the {@code .java} suffix.
+ */
+ default List includes() {
+ return List.of();
+ }
+
+ /**
+ * {@return the list of pattern matchers for the files to exclude}.
+ * The exclusions are applied after the inclusions.
+ * The default implementation returns an empty list.
+ */
+ default List excludes() {
+ return List.of();
+ }
+
+ /**
+ * {@return in which context the source files will be used}.
+ * The default value is {@link ProjectScope#MAIN}.
+ */
+ default ProjectScope scope() {
+ return ProjectScope.MAIN;
+ }
+
+ /**
+ * {@return the language of the source files}.
+ */
+ Language language();
+
+ /**
+ * {@return the name of the Java module (or other language-specific module) which is built by the sources}.
+ * The default value is empty.
+ */
+ default Optional module() {
+ return Optional.empty();
+ }
+
+ /**
+ * {@return the version of the platform where the code will be executed}.
+ * In a Java environment, this is the value of the {@code --release} compiler option.
+ * The default value is empty.
+ */
+ default Optional targetVersion() {
+ return Optional.empty();
+ }
+
+ /**
+ * {@return an explicit target path, overriding the default value}.
+ * When a target path is explicitly specified, the values of the {@link #module()} and {@link #targetVersion()}
+ * elements are not used for inferring the path (they are still used as compiler options however).
+ * It means that for scripts and resources, the files below the path specified by {@link #directory()}
+ * are copied to the path specified by {@code targetPath()} with the exact same directory structure.
+ */
+ default Optional targetPath() {
+ return Optional.empty();
+ }
+
+ /**
+ * {@return whether resources are filtered to replace tokens with parameterized values}.
+ * The default value is {@code false}.
+ */
+ default boolean stringFiltering() {
+ return false;
+ }
+
+ /**
+ * {@return whether the directory described by this source element should be included in the build}.
+ * The default value is {@code true}.
+ */
+ default boolean enabled() {
+ return true;
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
index bb208030c162..2f307ddc8dcc 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java
@@ -23,18 +23,20 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Stream;
import org.apache.maven.api.Artifact;
+import org.apache.maven.api.Language;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.RemoteRepository;
import org.apache.maven.api.Service;
import org.apache.maven.api.Session;
+import org.apache.maven.api.SourceRoot;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
-import org.apache.maven.api.model.Resource;
/**
* Interface to manage the project during its lifecycle.
@@ -46,19 +48,22 @@ public interface ProjectManager extends Service {
/**
* Returns the path to the built project artifact file, if the project has been built.
*
+ * @param project the project for which to get the path
* @return the path of the built project artifact
*/
@Nonnull
Optional getPath(Project project);
/**
- * Returns an immutable collection of attached artifacts for given project.
+ * {@return an immutable collection of attached artifacts for given project}.
+ *
+ * @param project the project from which to get the attached artifacts
*/
@Nonnull
Collection getAttachedArtifacts(Project project);
/**
- * Returns project's all artifacts as immutable collection. The list contains all artifacts, even the attached ones,
+ * {@return project's all artifacts as immutable collection}. The list contains all artifacts, even the attached ones,
* if any. Hence, the list returned by this method depends on which lifecycle step of the build was it invoked.
* The head of returned list is result of {@link Project#getArtifacts()} method, so same applies here: the list can have
* minimum of one element. The maximum number of elements is in turn dependent on build configuration and lifecycle
@@ -67,6 +72,8 @@ public interface ProjectManager extends Service {
*
* This method is shorthand for {@link Project#getArtifacts()} and {@link #getAttachedArtifacts(Project)} methods.
*
+ * @param project the project from which to get all artifacts
+ *
* @see org.apache.maven.api.services.ArtifactManager#getPath(Artifact)
*/
Collection getAllArtifacts(Project project);
@@ -97,6 +104,7 @@ default void attachArtifact(@Nonnull Session session, @Nonnull Project project,
* @param type the type of the artifact (e.g., "jar", "war", "sources")
* @param path the path to the artifact file
* @throws IllegalArgumentException if the session, project, type or path is null
+ *
* @see org.apache.maven.api.Type
*/
default void attachArtifact(
@@ -118,47 +126,70 @@ default void attachArtifact(
void attachArtifact(@Nonnull Project project, @Nonnull ProducedArtifact artifact, @Nonnull Path path);
/**
- * Obtain an immutable list of compile source roots for the given project and scope.
- * Paths are absolute.
+ * {@return all source root directories}, including the disabled ones, for all languages and scopes.
+ * For listing only the {@linkplain SourceRoot#enabled() enabled} source roots,
+ * the following code can be used:
*
- * @param project the project
- * @param scope the scope, i.e. usually main or test
- * @return the list of compile source roots
+ *
+ *
+ * The iteration order is the order in which the sources are declared in the POM file.
+ *
+ * @param project the project for which to get the source roots
*/
@Nonnull
- List getCompileSourceRoots(@Nonnull Project project, @Nonnull ProjectScope scope);
+ Collection getSourceRoots(@Nonnull Project project);
/**
- * Add a compilation source root to the given project for the given scope.
- * The path will be transformed into an absolute path and added to the list for the given scope,
- * if not already present.
+ * {@return all enabled sources that provide files in the given language for the given scope}.
+ * If the given scope is {@code null}, then this method returns the enabled sources for all scopes.
+ * If the given language is {@code null}, then this method returns the enabled sources for all languages.
+ * An arbitrary number of source roots may exist for the same scope and language.
+ * It may be, for example, the case of a multi-versions project.
+ * The iteration order is the order in which the sources are declared in the POM file.
*
- * @param project the project
- * @param scope the scope, i.e. usually main or test
- * @param sourceRoot the new source root
+ * @param project the project for which to get the enabled source roots
+ * @param scope the scope of the sources to return, or {@code null} for all scopes
+ * @param language the language of the sources to return, or {@code null} for all languages
*/
- void addCompileSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Path sourceRoot);
+ Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language);
/**
- * Get the list of resources for the given project and scope
+ * Adds the given source to the given project.
+ * If a source already exists for the given scope, language and directory,
+ * then the behavior depends on the {@code ProjectManager} implementation.
+ * It may do nothing or thrown {@linkplain IllegalArgumentException}.
*
- * @param project the project
- * @param scope the scope, i.e. usually main or test
- * @return the list of resources
+ * @param project the project to update
+ * @param source the source to add
+ * @throws IllegalArgumentException if this project manager rejects the given source because of conflict
+ *
+ * @see #getSourceRoots(Project)
*/
- List getResources(@Nonnull Project project, @Nonnull ProjectScope scope);
+ void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source);
/**
- * Add a resource set to the given project for the given scope.
+ * Resolves and adds the given directory as a source with the given scope and language.
+ * First, this method resolves the given root against the project base directory, then normalizes the path.
+ * If no source already exists for the same scope, language and normalized directory,
+ * these arguments are added as a new {@link SourceRoot} element.
+ * Otherwise (i.e., in case of potential conflict), the behavior depends on the {@code ProjectManager}.
+ * The default implementation does nothing in the latter case.
*
- * @param project the project
- * @param scope the scope, i.e. usually main or test
- * @param resource the resource set to add
+ * @param project the project to update
+ * @param scope scope (main or test) of the directory to add
+ * @param language language of the files contained in the directory to add
+ * @param directory the directory to add if not already present in the source
+ *
+ * @see #getEnabledSourceRoots(Project, ProjectScope, Language)
*/
- void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource);
+ void addSourceRoot(
+ @Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Language language, @Nonnull Path directory);
/**
- * Returns an immutable list of project remote repositories (directly specified or inherited).
+ * {@return an immutable list of project remote repositories} (directly specified or inherited).
*
* @param project the project
*/
@@ -166,7 +197,7 @@ default void attachArtifact(
List getRemoteProjectRepositories(@Nonnull Project project);
/**
- * Returns an immutable list of project remote plugin repositories (directly specified or inherited).
+ * {@return an immutable list of project remote plugin repositories} (directly specified or inherited).
*
* @param project the project
*/
@@ -174,7 +205,9 @@ default void attachArtifact(
List getRemotePluginRepositories(@Nonnull Project project);
/**
- * Returns an immutable map of the project properties.
+ * {@return an immutable map of the project properties}.
+ *
+ * @param project the project for which to get the properties
*
* @see #setProperty(Project, String, String)
*/
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo
index 3a0f0d3dea7c..b3f8478293dd 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -536,7 +536,7 @@
*
- @Deprecated
+ @Deprecated(since = "4.0.0")
@@ -630,6 +630,9 @@
@deprecated Now ignored by Maven.
DOM
+
+ @Deprecated(since = "4.0.0")
+ reporting
@@ -775,7 +778,6 @@
String
-
resources3.0.0+
@@ -783,25 +785,34 @@
files associated with a project. These resources are often included in the final
package.
The default value is {@code src/main/resources}.
+
+ @deprecated Replaced by {@code <Source>} with {@code main} scope and {@code resources} language.
Resource*
+
+ @Deprecated(since = "4.0.0")
+
-
testResources4.0.0+
This element describes all the classpath resources such as properties
files associated with a project's unit tests.
The default value is {@code src/test/resources}.
+
+ @deprecated Replaced by {@code <Source>} with {@code test} scope and {@code resources} language.
Resource*
+
+ @Deprecated(since = "4.0.0")
+ directory
@@ -874,7 +885,6 @@
-
sourceDirectory3.0.0+true
@@ -883,11 +893,15 @@
generated build system will compile the sources from this directory when the project is
built. The path given is relative to the project descriptor.
The default value is {@code src/main/java}.
+
+ @deprecated Replaced by {@code <Source>} with {@code main} scope.
String
+
+ @Deprecated(since = "4.0.0")
+
-
scriptSourceDirectory4.0.0+true
@@ -897,11 +911,15 @@
contents will be copied to the output directory in most cases (since scripts are
interpreted rather than compiled).
The default value is {@code src/main/scripts}.
+
+ @deprecated Replaced by {@code <Source>} with {@code script} language.
String
+
+ @Deprecated(since = "4.0.0")
+
-
testSourceDirectory4.0.0+true
@@ -910,8 +928,13 @@
project. The generated build system will compile these directories when the project is
being tested. The path given is relative to the project descriptor.
The default value is {@code src/test/java}.
+
+ @deprecated Replaced by {@code <Source>} with {@code test} scope.
String
+
+ @Deprecated(since = "4.0.0")
+ outputDirectory
@@ -1044,6 +1067,9 @@
@deprecated Where to send the notification to - eg email address.
+
+ @Deprecated(since = "4.0.0")
+ configuration
@@ -2041,7 +2067,7 @@
]]>
String
- main
+ javamodule
@@ -2143,7 +2169,6 @@
-
ResourceThis element describes all of the classpath resources associated with a project
or unit tests.
@@ -2161,8 +2186,13 @@
element with this value: {@code org/apache/maven/messages}.
This is not required if you simply put the resources in that directory
structure at the source, however.
+
+ @deprecated Replaced by {@code <Source>} with {@code resources} language.
String
+
+ @Deprecated(since = "4.0.0")
+ filtering
@@ -2561,6 +2591,9 @@
@deprecated Unused by Maven.
DOM
+
+ @Deprecated(since = "4.0.0")
+ dependencies
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
index 285935f3fc01..6b7903b6d6ca 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
@@ -122,6 +122,7 @@ public Path getPomPath() {
return nonNull(project.getFile(), "pomPath").toPath();
}
+ @Nonnull
@Override
public Path getBasedir() {
return nonNull(project.getBasedir(), "basedir").toPath();
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
index 59b141d57007..998b606a4627 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
@@ -22,7 +22,6 @@
import javax.inject.Named;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -31,16 +30,17 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.maven.RepositoryUtils;
+import org.apache.maven.api.Language;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.SourceRoot;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.di.SessionScoped;
-import org.apache.maven.api.model.Resource;
import org.apache.maven.api.services.ArtifactManager;
import org.apache.maven.api.services.ProjectManager;
import org.apache.maven.impl.MappedList;
@@ -48,7 +48,6 @@
import org.apache.maven.project.MavenProject;
import org.eclipse.sisu.Typed;
-import static java.util.stream.Collectors.toList;
import static org.apache.maven.impl.Utils.map;
import static org.apache.maven.impl.Utils.nonNull;
@@ -128,60 +127,34 @@ public void attachArtifact(Project project, ProducedArtifact artifact, Path path
artifactManager.setPath(artifact, path);
}
+ @Nonnull
@Override
- public List getCompileSourceRoots(Project project, ProjectScope scope) {
+ public Collection getSourceRoots(@Nonnull Project project) {
MavenProject prj = getMavenProject(nonNull(project, "project"));
- List roots;
- if (nonNull(scope, "scope") == ProjectScope.MAIN) {
- roots = prj.getCompileSourceRoots();
- } else if (scope == ProjectScope.TEST) {
- roots = prj.getTestCompileSourceRoots();
- } else {
- throw new IllegalArgumentException("Unsupported scope " + scope);
- }
- return roots.stream()
- .map(Paths::get)
- .collect(Collectors.collectingAndThen(toList(), Collections::unmodifiableList));
+ return prj.getSourceRoots();
}
+ @Nonnull
@Override
- public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) {
+ public Stream getEnabledSourceRoots(@Nonnull Project project, ProjectScope scope, Language language) {
MavenProject prj = getMavenProject(nonNull(project, "project"));
- String root = nonNull(sourceRoot, "sourceRoot").toAbsolutePath().toString();
- if (nonNull(scope, "scope") == ProjectScope.MAIN) {
- prj.addCompileSourceRoot(root);
- } else if (scope == ProjectScope.TEST) {
- prj.addTestCompileSourceRoot(root);
- } else {
- throw new IllegalArgumentException("Unsupported scope " + scope);
- }
+ return prj.getEnabledSourceRoots(scope, language);
}
@Override
- public List getResources(@Nonnull Project project, @Nonnull ProjectScope scope) {
- Project prj = nonNull(project, "project");
- if (nonNull(scope, "scope") == ProjectScope.MAIN) {
- return prj.getBuild().getResources();
- } else if (scope == ProjectScope.TEST) {
- return prj.getBuild().getTestResources();
- } else {
- throw new IllegalArgumentException("Unsupported scope " + scope);
- }
+ public void addSourceRoot(@Nonnull Project project, @Nonnull SourceRoot source) {
+ MavenProject prj = getMavenProject(nonNull(project, "project"));
+ prj.addSourceRoot(nonNull(source, "source"));
}
@Override
- public void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource) {
- // TODO: we should not modify the underlying model here, but resources should be stored
- // TODO: in a separate field in the project, however, that could break v3 plugins
+ public void addSourceRoot(
+ @Nonnull Project project,
+ @Nonnull ProjectScope scope,
+ @Nonnull Language language,
+ @Nonnull Path directory) {
MavenProject prj = getMavenProject(nonNull(project, "project"));
- org.apache.maven.model.Resource res = new org.apache.maven.model.Resource(nonNull(resource, "resource"));
- if (nonNull(scope, "scope") == ProjectScope.MAIN) {
- prj.addResource(res);
- } else if (scope == ProjectScope.TEST) {
- prj.addTestResource(res);
- } else {
- throw new IllegalArgumentException("Unsupported scope " + scope);
- }
+ prj.addSourceRoot(nonNull(scope, "scope"), nonNull(language, "language"), nonNull(directory, "directory"));
}
@Override
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index a5fc26c60c63..f829ade7a0ad 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -46,6 +46,8 @@
import org.apache.maven.ProjectCycleException;
import org.apache.maven.RepositoryUtils;
+import org.apache.maven.api.Language;
+import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.SessionData;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.Dependency;
@@ -73,6 +75,7 @@
import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.bridge.MavenRepositorySystem;
+import org.apache.maven.impl.DefaultSourceRoot;
import org.apache.maven.impl.InternalSession;
import org.apache.maven.impl.resolver.ArtifactDescriptorUtils;
import org.apache.maven.model.building.DefaultModelProblem;
@@ -576,9 +579,43 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
// only set those on 2nd phase, ignore on 1st pass
if (project.getFile() != null) {
Build build = project.getBuild().getDelegate();
- project.addScriptSourceRoot(build.getScriptSourceDirectory());
- project.addCompileSourceRoot(build.getSourceDirectory());
- project.addTestCompileSourceRoot(build.getTestSourceDirectory());
+ List sources = build.getSources();
+ Path baseDir = project.getBaseDirectory();
+ InternalSession s = InternalSession.from(session);
+ boolean hasScript = false;
+ boolean hasMain = false;
+ boolean hasTest = false;
+ for (var source : sources) {
+ var src = new DefaultSourceRoot(s, baseDir, source);
+ project.addSourceRoot(src);
+ Language language = src.language();
+ if (Language.JAVA_FAMILY.equals(language)) {
+ ProjectScope scope = src.scope();
+ if (ProjectScope.MAIN.equals(scope)) {
+ hasMain = true;
+ } else {
+ hasTest |= ProjectScope.TEST.equals(scope);
+ }
+ } else {
+ hasScript |= Language.SCRIPT.equals(language);
+ }
+ }
+ /*
+ * `sourceDirectory`, `testSourceDirectory` and `scriptSourceDirectory`
+ * are ignored if the POM file contains at least one element
+ * for the corresponding scope and langiage. This rule exists because
+ * Maven provides default values for those elements which may conflict
+ * with user's configuration.
+ */
+ if (!hasScript) {
+ project.addScriptSourceRoot(build.getScriptSourceDirectory());
+ }
+ if (!hasMain) {
+ project.addCompileSourceRoot(build.getSourceDirectory());
+ }
+ if (!hasTest) {
+ project.addTestCompileSourceRoot(build.getTestSourceDirectory());
+ }
}
project.setActiveProfiles(
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index 260945360512..2b5b199eb5ca 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -23,6 +23,7 @@
import java.io.Writer;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -33,8 +34,12 @@
import java.util.Properties;
import java.util.Set;
import java.util.function.Predicate;
+import java.util.stream.Stream;
import org.apache.maven.RepositoryUtils;
+import org.apache.maven.api.Language;
+import org.apache.maven.api.ProjectScope;
+import org.apache.maven.api.SourceRoot;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
@@ -42,6 +47,7 @@
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.impl.DefaultSourceRoot;
import org.apache.maven.lifecycle.internal.DefaultProjectArtifactFactory;
import org.apache.maven.model.Build;
import org.apache.maven.model.CiManagement;
@@ -121,8 +127,10 @@ public class MavenProject implements Cloneable {
private Set pluginArtifacts;
+ @Deprecated
private List remoteArtifactRepositories;
+ @Deprecated
private List pluginArtifactRepositories;
private List remoteProjectRepositories;
@@ -135,20 +143,52 @@ public class MavenProject implements Cloneable {
private List collectedProjects;
- private List compileSourceRoots = new ArrayList<>();
+ /**
+ * A tuple of {@link SourceRoot} properties for which we decide that no duplicated value should exist in a project.
+ * The set of properties that we choose to put in this record may be modified in any future Maven version.
+ * The intent is to detect some configuration errors.
+ */
+ private record SourceKey(ProjectScope scope, Language language, Path directory) {
+ /**
+ * Converts this key into a source root.
+ * Used for adding a new source when no other information is available.
+ *
+ * @return the source root for the properties of this key.
+ */
+ SourceRoot createSource() {
+ return new DefaultSourceRoot(scope, language, directory);
+ }
- private List testCompileSourceRoots = new ArrayList<>();
+ /**
+ * {@return an error message to report when a conflict is detected}.
+ *
+ * @param baseDir value of {@link #getBaseDirectory()}, in order to make the message shorter
+ */
+ String conflictMessage(Path baseDir) {
+ return "Directory " + baseDir.relativize(directory)
+ + " is specified twice for the scope \"" + scope.id()
+ + "\" and language \"" + language.id() + "\".";
+ }
+ }
- private List scriptSourceRoots = new ArrayList<>();
+ /**
+ * All sources of this project. The map includes main and test codes for all languages.
+ * However, we put some restrictions on what information can be repeated.
+ * Those restrictions are expressed in {@link SourceKey}.
+ */
+ private HashMap sources = new LinkedHashMap<>(); // Need access to the `clone()` method.
+ @Deprecated
private ArtifactRepository releaseArtifactRepository;
+ @Deprecated
private ArtifactRepository snapshotArtifactRepository;
private List activeProfiles = new ArrayList<>();
private Map> injectedProfileIds = new LinkedHashMap<>();
+ @Deprecated
private Set dependencyArtifacts;
private Artifact artifact;
@@ -160,12 +200,16 @@ public class MavenProject implements Cloneable {
private Map pluginArtifactMap;
+ @Deprecated
private Set reportArtifacts;
+ @Deprecated
private Map reportArtifactMap;
+ @Deprecated
private Set extensionArtifacts;
+ @Deprecated
private Map extensionArtifactMap;
private Map managedVersionMap;
@@ -266,10 +310,24 @@ public void setPomFile(File file) {
this.file = file;
}
+ /**
+ * @deprecated Replaced by {@link #getBaseDirectory()} for migrating from {@code File} to {@code Path}.
+ */
+ @Deprecated(since = "4.0.0")
public File getBasedir() {
return basedir;
}
+ /**
+ * {@return the base directory of this project}.
+ * All source files are relative to this directory, unless they were specified as absolute paths.
+ *
+ * @since 4.0.0
+ */
+ public Path getBaseDirectory() {
+ return getBasedir().toPath();
+ }
+
public void setDependencies(List dependencies) {
getModel().setDependencies(dependencies);
}
@@ -286,40 +344,147 @@ public DependencyManagement getDependencyManagement() {
// Test and compile source roots.
// ----------------------------------------------------------------------
- private void addPath(List paths, String path) {
- if (path != null) {
- path = path.trim();
- if (!path.isEmpty()) {
- File file = new File(path);
- if (file.isAbsolute()) {
- path = file.getAbsolutePath();
- } else if (".".equals(path)) {
- path = getBasedir().getAbsolutePath();
- } else {
- path = new File(getBasedir(), path).getAbsolutePath();
- }
+ /**
+ * Adds the given source. If a source already exists for the given scope, language and directory,
+ * then this method either does nothing if all other properties are equal, or thrown
+ * {@linkplain IllegalArgumentException} otherwise.
+ *
+ * @param source the source to add
+ * @throws IllegalArgumentException if a source exists for the given language, scope and directory
+ * but with different values for the other properties.
+ *
+ * @see #getSourceRoots()
+ *
+ * @since 4.0.0
+ */
+ public void addSourceRoot(SourceRoot source) {
+ var key = new SourceKey(source.scope(), source.language(), source.directory());
+ SourceRoot current = sources.putIfAbsent(key, source);
+ if (current != null && !current.equals(source)) {
+ throw new IllegalArgumentException(key.conflictMessage(getBaseDirectory()));
+ }
+ }
- if (!paths.contains(path)) {
- paths.add(path);
- }
+ /**
+ * Resolves and adds the given directory as a source with the given scope and language.
+ * First, this method resolves the given root against the {@linkplain #getBaseDirectory() base directory},
+ * then normalizes the path. If a source already exists for the same scope, language and normalized directory,
+ * this method does nothing. Otherwise, the normalized directory is added as a new {@link SourceRoot} element.
+ *
+ * @param scope scope (main or test) of the directory to add
+ * @param language language of the files contained in the directory to add
+ * @param directory the directory to add if not already present in the source
+ *
+ * @see #getEnabledSourceRoots(ProjectScope, Language)
+ *
+ * @since 4.0.0
+ */
+ public void addSourceRoot(ProjectScope scope, Language language, Path directory) {
+ directory = getBaseDirectory().resolve(directory).normalize();
+ var key = new SourceKey(scope, language, directory);
+ sources.computeIfAbsent(key, SourceKey::createSource);
+ }
+
+ /**
+ * Resolves and adds the given directory as a source with the given scope and language.
+ * If the given directory is null, blank or already in the sources, then this method does nothing.
+ * Otherwise, the directory is converted to a path, resolved, normalized and finally added as a new
+ * {@link SourceRoot} element if no source exists for these scope, language and normalized directory.
+ *
+ * @param scope scope (main or test) of the directory to add
+ * @param language language of the files contained in the directory to add
+ * @param directory the directory to add if not already present in the source, or null
+ *
+ * @since 4.0.0
+ */
+ public void addSourceRoot(ProjectScope scope, Language language, String directory) {
+ if (directory != null) {
+ directory = directory.trim();
+ if (!directory.isBlank()) {
+ Path path = getBaseDirectory().resolve(directory).normalize();
+ var key = new SourceKey(scope, language, path);
+ sources.computeIfAbsent(key, SourceKey::createSource);
}
}
}
+ /**
+ * @deprecated Replaced by {@code addSourceRoot(ProjectScope.MAIN, Language.JAVA_FAMILY, path)}.
+ */
+ @Deprecated(since = "4.0.0")
public void addCompileSourceRoot(String path) {
- addPath(getCompileSourceRoots(), path);
+ addSourceRoot(ProjectScope.MAIN, Language.JAVA_FAMILY, path);
}
+ /**
+ * @deprecated Replaced by {@code addSourceRoot(ProjectScope.TEST, Language.JAVA_FAMILY, path)}.
+ */
+ @Deprecated(since = "4.0.0")
public void addTestCompileSourceRoot(String path) {
- addPath(getTestCompileSourceRoots(), path);
+ addSourceRoot(ProjectScope.TEST, Language.JAVA_FAMILY, path);
+ }
+
+ /**
+ * {@return all source root directories, including the disabled ones, for all languages and scopes}.
+ * The iteration order is the order in which the sources are declared in the POM file.
+ * The returned collection is unmodifiable.
+ *
+ * @see #addSourceRoot(SourceRoot)
+ */
+ public Collection getSourceRoots() {
+ return Collections.unmodifiableCollection(sources.values());
+ }
+
+ /**
+ * {@return all enabled sources that provide files in the given language for the given scope}.
+ * If the given scope is {@code null}, then this method returns the enabled sources for all scopes.
+ * If the given language is {@code null}, then this method returns the enabled sources for all languages.
+ * The iteration order is the order in which the sources are declared in the POM file.
+ *
+ * @param scope the scope of the sources to return, or {@code null} for all scopes
+ * @param language the language of the sources to return, or {@code null} for all languages
+ *
+ * @see #addSourceRoot(ProjectScope, Language, Path)
+ *
+ * @since 4.0.0
+ */
+ public Stream getEnabledSourceRoots(ProjectScope scope, Language language) {
+ Stream s = sources.values().stream().filter(SourceRoot::enabled);
+ if (scope != null) {
+ s = s.filter((source) -> scope.equals(source.scope()));
+ }
+ if (language != null) {
+ s = s.filter((source) -> language.equals(source.language()));
+ }
+ return s;
+ }
+
+ /**
+ * Returns a list of paths for the given scope.
+ *
+ * @deprecated Used only for the implementation of deprecated methods.
+ */
+ @Deprecated
+ private List getSourceRootDirs(ProjectScope scope, Language language) {
+ return getEnabledSourceRoots(scope, language)
+ .map((source) -> source.directory().toString())
+ .toList();
}
+ /**
+ * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.JAVA_FAMILY)}.
+ */
+ @Deprecated(since = "4.0.0")
public List getCompileSourceRoots() {
- return compileSourceRoots;
+ return getSourceRootDirs(ProjectScope.MAIN, Language.JAVA_FAMILY);
}
+ /**
+ * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.JAVA_FAMILY)}.
+ */
+ @Deprecated(since = "4.0.0")
public List getTestCompileSourceRoots() {
- return testCompileSourceRoots;
+ return getSourceRootDirs(ProjectScope.TEST, Language.JAVA_FAMILY);
}
// TODO let the scope handler deal with this
@@ -600,20 +765,38 @@ public Build getBuild() {
return getModelBuild();
}
+ /**
+ * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)}.
+ */
+ @Deprecated(since = "4.0.0")
public List getResources() {
return getBuild().getResources();
}
+ /**
+ * @deprecated Replaced by {@code getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)}.
+ */
+ @Deprecated(since = "4.0.0")
public List getTestResources() {
return getBuild().getTestResources();
}
+ /**
+ * @deprecated {@link Resource} is replaced by {@link SourceRoot}.
+ */
+ @Deprecated(since = "4.0.0")
public void addResource(Resource resource) {
getBuild().addResource(resource);
+ addSourceRoot(new DefaultSourceRoot(getBaseDirectory(), ProjectScope.MAIN, resource.getDelegate()));
}
+ /**
+ * @deprecated {@link Resource} is replaced by {@link SourceRoot}.
+ */
+ @Deprecated(since = "4.0.0")
public void addTestResource(Resource testResource) {
getBuild().addTestResource(testResource);
+ addSourceRoot(new DefaultSourceRoot(getBaseDirectory(), ProjectScope.TEST, testResource.getDelegate()));
}
public void setLicenses(List licenses) {
@@ -737,11 +920,13 @@ private Build getModelBuild() {
return build;
}
+ @Deprecated
public void setRemoteArtifactRepositories(List remoteArtifactRepositories) {
this.remoteArtifactRepositories = remoteArtifactRepositories;
this.remoteProjectRepositories = RepositoryUtils.toRepos(getRemoteArtifactRepositories());
}
+ @Deprecated
public List getRemoteArtifactRepositories() {
if (remoteArtifactRepositories == null) {
remoteArtifactRepositories = new ArrayList<>();
@@ -750,6 +935,7 @@ public List getRemoteArtifactRepositories() {
return remoteArtifactRepositories;
}
+ @Deprecated
public void setPluginArtifactRepositories(List pluginArtifactRepositories) {
this.pluginArtifactRepositories = pluginArtifactRepositories;
this.remotePluginRepositories = RepositoryUtils.toRepos(getPluginArtifactRepositories());
@@ -759,6 +945,7 @@ public void setPluginArtifactRepositories(List pluginArtifac
* @return a list of ArtifactRepository objects constructed from the Repository objects returned by
* getPluginRepositories.
*/
+ @Deprecated
public List getPluginArtifactRepositories() {
if (pluginArtifactRepositories == null) {
pluginArtifactRepositories = new ArrayList<>();
@@ -767,6 +954,7 @@ public List getPluginArtifactRepositories() {
return pluginArtifactRepositories;
}
+ @Deprecated
public ArtifactRepository getDistributionManagementArtifactRepository() {
return getArtifact().isSnapshot() && (getSnapshotArtifactRepository() != null)
? getSnapshotArtifactRepository()
@@ -912,10 +1100,12 @@ public void setDependencyArtifacts(Set dependencyArtifacts) {
this.dependencyArtifacts = dependencyArtifacts;
}
+ @Deprecated
public void setReleaseArtifactRepository(ArtifactRepository releaseArtifactRepository) {
this.releaseArtifactRepository = releaseArtifactRepository;
}
+ @Deprecated
public void setSnapshotArtifactRepository(ArtifactRepository snapshotArtifactRepository) {
this.snapshotArtifactRepository = snapshotArtifactRepository;
}
@@ -1043,12 +1233,32 @@ protected void setAttachedArtifacts(List attachedArtifacts) {
this.attachedArtifacts = attachedArtifacts;
}
+ /**
+ * @deprecated Used only for the implementation of deprecated methods.
+ */
+ @Deprecated
+ private void setSourceRootDirs(ProjectScope scope, Language language, List roots) {
+ sources.values().removeIf((source) -> scope.equals(source.scope()) && language.equals(source.language()));
+ Path directory = getBaseDirectory();
+ for (String root : roots) {
+ addSourceRoot(new DefaultSourceRoot(scope, language, directory.resolve(root)));
+ }
+ }
+
+ /**
+ * @deprecated Replaced by {@link #addSourceRoot(ProjectScope, Language, String)}.
+ */
+ @Deprecated(since = "4.0.0")
protected void setCompileSourceRoots(List compileSourceRoots) {
- this.compileSourceRoots = compileSourceRoots;
+ setSourceRootDirs(ProjectScope.MAIN, Language.JAVA_FAMILY, compileSourceRoots);
}
+ /**
+ * @deprecated Replaced by {@link #addSourceRoot(ProjectScope, Language, String)}.
+ */
+ @Deprecated(since = "4.0.0")
protected void setTestCompileSourceRoots(List testCompileSourceRoots) {
- this.testCompileSourceRoots = testCompileSourceRoots;
+ setSourceRootDirs(ProjectScope.TEST, Language.JAVA_FAMILY, testCompileSourceRoots);
}
protected ArtifactRepository getReleaseArtifactRepository() {
@@ -1111,18 +1321,10 @@ private void deepCopy(MavenProject project) {
setAttachedArtifacts(new ArrayList<>(project.getAttachedArtifacts()));
}
- if (project.getCompileSourceRoots() != null) {
- // clone source roots
- setCompileSourceRoots((new ArrayList<>(project.getCompileSourceRoots())));
- }
-
- if (project.getTestCompileSourceRoots() != null) {
- setTestCompileSourceRoots((new ArrayList<>(project.getTestCompileSourceRoots())));
- }
-
- if (project.getScriptSourceRoots() != null) {
- setScriptSourceRoots((new ArrayList<>(project.getScriptSourceRoots())));
- }
+ // This property is not handled like others as we don't use public API.
+ // The whole implementation of this `deepCopy` method may need revision,
+ // but it would be the topic for a separated commit.
+ sources = (HashMap) project.sources.clone();
if (project.getModel() != null) {
setModel(project.getModel().clone());
@@ -1346,24 +1548,17 @@ public Set createArtifacts(ArtifactFactory artifactFactory, String inh
@Deprecated
protected void setScriptSourceRoots(List scriptSourceRoots) {
- this.scriptSourceRoots = scriptSourceRoots;
+ setSourceRootDirs(ProjectScope.MAIN, Language.SCRIPT, scriptSourceRoots);
}
@Deprecated
public void addScriptSourceRoot(String path) {
- if (path != null) {
- path = path.trim();
- if (path.length() != 0) {
- if (!getScriptSourceRoots().contains(path)) {
- getScriptSourceRoots().add(path);
- }
- }
- }
+ addSourceRoot(ProjectScope.MAIN, Language.SCRIPT, path);
}
@Deprecated
public List getScriptSourceRoots() {
- return scriptSourceRoots;
+ return getSourceRootDirs(ProjectScope.MAIN, Language.SCRIPT);
}
@Deprecated
@@ -1590,7 +1785,6 @@ public Map getReportArtifactMap() {
@Deprecated
public void setExtensionArtifacts(Set extensionArtifacts) {
this.extensionArtifacts = extensionArtifacts;
-
extensionArtifactMap = null;
}
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
new file mode 100644
index 000000000000..8dadf02f058f
--- /dev/null
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
@@ -0,0 +1,297 @@
+/*
+ * 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.impl;
+
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.maven.api.Language;
+import org.apache.maven.api.ProjectScope;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.SourceRoot;
+import org.apache.maven.api.Version;
+import org.apache.maven.api.model.Resource;
+import org.apache.maven.api.model.Source;
+
+/**
+ * A default implementation of {@code SourceRoot} built from the model.
+ */
+public final class DefaultSourceRoot implements SourceRoot {
+ private final Path directory;
+
+ private final List includes;
+
+ private final List excludes;
+
+ private final ProjectScope scope;
+
+ private final Language language;
+
+ private final String moduleName;
+
+ private final Version targetVersion;
+
+ private final Path targetPath;
+
+ private final boolean stringFiltering;
+
+ private final boolean enabled;
+
+ /**
+ * Creates a new instance from the given model.
+ *
+ * @param session the session of resolving extensible enumerations
+ * @param baseDir the base directory for resolving relative paths
+ * @param source a source element from the model
+ */
+ public DefaultSourceRoot(final Session session, final Path baseDir, final Source source) {
+ String value = nonBlank(source.getDirectory());
+ if (value == null) {
+ throw new IllegalArgumentException("Source declaration without directory value.");
+ }
+ directory = baseDir.resolve(value);
+ FileSystem fs = directory.getFileSystem();
+ includes = matchers(fs, source.getIncludes());
+ excludes = matchers(fs, source.getExcludes());
+ stringFiltering = source.isStringFiltering();
+ enabled = source.isEnabled();
+ moduleName = nonBlank(source.getModule());
+
+ value = nonBlank(source.getScope());
+ scope = (value != null) ? session.requireProjectScope(value) : ProjectScope.MAIN;
+
+ value = nonBlank(source.getLang());
+ language = (value != null) ? session.requireLanguage(value) : Language.JAVA_FAMILY;
+
+ value = nonBlank(source.getTargetVersion());
+ targetVersion = (value != null) ? session.parseVersion(value) : null;
+
+ value = nonBlank(source.getTargetPath());
+ targetPath = (value != null) ? baseDir.resolve(value) : null;
+ }
+
+ /**
+ * Creates a new instance from the given resource.
+ * This is used for migration from the previous way of declaring resources.
+ *
+ * @param baseDir the base directory for resolving relative paths
+ * @param scope the scope of the resource (main or test)
+ * @param resource a resource element from the model
+ */
+ public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) {
+ String value = nonBlank(resource.getDirectory());
+ if (value == null) {
+ throw new IllegalArgumentException("Source declaration without directory value.");
+ }
+ directory = baseDir.resolve(value).normalize();
+ FileSystem fs = directory.getFileSystem();
+ includes = matchers(fs, resource.getIncludes());
+ excludes = matchers(fs, resource.getExcludes());
+ stringFiltering = Boolean.parseBoolean(resource.getFiltering());
+ enabled = true;
+ moduleName = null;
+ this.scope = scope;
+ language = Language.RESOURCES;
+ targetVersion = null;
+ targetPath = null;
+ }
+
+ /**
+ * Creates a new instance for the given directory and scope. The language is assumed Java.
+ *
+ * @param scope scope of source code (main or test)
+ * @param language language of the source code
+ * @param directory directory of the source code
+ */
+ public DefaultSourceRoot(final ProjectScope scope, final Language language, final Path directory) {
+ this.scope = Objects.requireNonNull(scope);
+ this.language = language;
+ this.directory = Objects.requireNonNull(directory);
+ includes = List.of();
+ excludes = List.of();
+ moduleName = null;
+ targetVersion = null;
+ targetPath = null;
+ stringFiltering = false;
+ enabled = true;
+ }
+
+ /**
+ * {@return the given value as a trimmed non-blank string, or null otherwise}.
+ */
+ private static String nonBlank(String value) {
+ if (value != null) {
+ value = value.trim();
+ if (value.isBlank()) {
+ value = null;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Creates a path matcher for each pattern.
+ * The path separator is {@code /} on all platforms, including Windows.
+ * The prefix before the {@code :} character is the syntax.
+ * If no syntax is specified, {@code "glob"} is assumed.
+ *
+ * @param fs the file system of the root directory
+ * @param patterns the patterns for which to create path matcher
+ * @return a path matcher for each pattern
+ */
+ private static List matchers(FileSystem fs, List patterns) {
+ final var matchers = new PathMatcher[patterns.size()];
+ for (int i = 0; i < matchers.length; i++) {
+ String pattern = patterns.get(i);
+ if (pattern.indexOf(':') < 0) {
+ pattern = "glob:" + pattern;
+ }
+ matchers[i] = fs.getPathMatcher(pattern);
+ }
+ return List.of(matchers);
+ }
+
+ /**
+ * {@return the root directory where the sources are stored}.
+ */
+ @Override
+ public Path directory() {
+ return directory;
+ }
+
+ /**
+ * {@return the list of pattern matchers for the files to include}.
+ */
+ @Override
+ @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
+ public List includes() {
+ return includes;
+ }
+
+ /**
+ * {@return the list of pattern matchers for the files to exclude}.
+ */
+ @Override
+ @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
+ public List excludes() {
+ return excludes;
+ }
+
+ /**
+ * {@return in which context the source files will be used}.
+ */
+ @Override
+ public ProjectScope scope() {
+ return scope;
+ }
+
+ /**
+ * {@return the language of the source files}.
+ */
+ @Override
+ public Language language() {
+ return language;
+ }
+
+ /**
+ * {@return the name of the Java module (or other language-specific module) which is built by the sources}.
+ */
+ @Override
+ public Optional module() {
+ return Optional.ofNullable(moduleName);
+ }
+
+ /**
+ * {@return the version of the platform where the code will be executed}.
+ */
+ @Override
+ public Optional targetVersion() {
+ return Optional.ofNullable(targetVersion);
+ }
+
+ /**
+ * {@return an explicit target path, overriding the default value}.
+ */
+ @Override
+ public Optional targetPath() {
+ return Optional.ofNullable(targetPath);
+ }
+
+ /**
+ * {@return whether resources are filtered to replace tokens with parameterized values}.
+ */
+ @Override
+ public boolean stringFiltering() {
+ return stringFiltering;
+ }
+
+ /**
+ * {@return whether the directory described by this source element should be included in the build}.
+ */
+ @Override
+ public boolean enabled() {
+ return enabled;
+ }
+
+ /**
+ * {@return a hash code value computed from all properties}.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ directory,
+ includes,
+ excludes,
+ scope,
+ language,
+ moduleName,
+ targetVersion,
+ targetPath,
+ stringFiltering,
+ enabled);
+ }
+
+ /**
+ * {@return whether the two objects are of the same class with equal property values}.
+ *
+ * @param obj the other object to compare with this object, or {@code null}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultSourceRoot other) {
+ return directory.equals(other.directory)
+ && includes.equals(other.includes)
+ && excludes.equals(other.excludes)
+ && Objects.equals(scope, other.scope)
+ && Objects.equals(language, other.language)
+ && Objects.equals(moduleName, other.moduleName)
+ && Objects.equals(targetVersion, other.targetVersion)
+ && stringFiltering == other.stringFiltering
+ && enabled == other.enabled;
+ }
+ return false;
+ }
+}
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java
index 5be25fad7236..9fdfe53e7082 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/ExtensibleEnumRegistries.java
@@ -80,7 +80,7 @@ public static class DefaultLanguageRegistry extends DefaultExtensibleEnumRegistr
@Inject
public DefaultLanguageRegistry(List providers) {
- super(providers, Language.NONE, Language.JAVA_FAMILY);
+ super(providers, Language.NONE, Language.RESOURCES, Language.SCRIPT, Language.JAVA_FAMILY);
}
}
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java
index 8b78e00b5671..ad4135d94b96 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java
@@ -22,7 +22,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.function.UnaryOperator;
+import java.util.function.BiFunction;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
@@ -31,6 +31,7 @@
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Reporting;
import org.apache.maven.api.model.Resource;
+import org.apache.maven.api.model.Source;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.model.ModelPathTranslator;
import org.apache.maven.api.services.model.PathTranslator;
@@ -60,13 +61,14 @@ public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest
Build newBuild = null;
if (build != null) {
newBuild = Build.newBuilder(build)
+ .sources(map(build.getSources(), this::alignToBaseDirectory, basedir))
.directory(alignToBaseDirectory(build.getDirectory(), basedir))
.sourceDirectory(alignToBaseDirectory(build.getSourceDirectory(), basedir))
.testSourceDirectory(alignToBaseDirectory(build.getTestSourceDirectory(), basedir))
.scriptSourceDirectory(alignToBaseDirectory(build.getScriptSourceDirectory(), basedir))
- .resources(map(build.getResources(), r -> alignToBaseDirectory(r, basedir)))
- .testResources(map(build.getTestResources(), r -> alignToBaseDirectory(r, basedir)))
- .filters(map(build.getFilters(), s -> alignToBaseDirectory(s, basedir)))
+ .resources(map(build.getResources(), this::alignToBaseDirectory, basedir))
+ .testResources(map(build.getTestResources(), this::alignToBaseDirectory, basedir))
+ .filters(map(build.getFilters(), this::alignToBaseDirectory, basedir))
.outputDirectory(alignToBaseDirectory(build.getOutputDirectory(), basedir))
.testOutputDirectory(alignToBaseDirectory(build.getTestOutputDirectory(), basedir))
.build();
@@ -88,12 +90,22 @@ public Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest
return model;
}
- private List map(List resources, UnaryOperator mapper) {
+ /**
+ * Replaces in a new list all elements of the given list using the given function.
+ * If no list element has changed, then this method returns the previous list instance.
+ * The given list is never modified.
+ *
+ * @param resource the list for which to replace elements
+ * @param mapper one of the {@code this::alignToBaseDirectory} methods
+ * @param basedir the new base directory
+ * @return list with modified elements, or {@code resources} if there is no change
+ */
+ private List map(List resources, BiFunction mapper, Path basedir) {
List newResources = null;
if (resources != null) {
for (int i = 0; i < resources.size(); i++) {
T resource = resources.get(i);
- T newResource = mapper.apply(resource);
+ T newResource = mapper.apply(resource, basedir);
if (newResource != resource) {
if (newResources == null) {
newResources = new ArrayList<>(resources);
@@ -105,6 +117,32 @@ private List map(List resources, UnaryOperator mapper) {
return newResources;
}
+ /**
+ * Returns a source with all properties identical to the given source, except the paths
+ * which are resolved according the given {@code basedir}. If the paths are unchanged,
+ * then this method returns the previous instance.
+ *
+ * @param source the source to relocate, or {@code null}
+ * @param basedir the new base directory
+ * @return relocated source, or {@code null} if the given source was null
+ */
+ @SuppressWarnings("StringEquality") // Identity comparison is ok in this method.
+ private Source alignToBaseDirectory(Source source, Path basedir) {
+ if (source != null) {
+ String oldDir = source.getDirectory();
+ String newDir = alignToBaseDirectory(oldDir, basedir);
+ if (newDir != oldDir) {
+ source = source.withDirectory(newDir);
+ }
+ oldDir = source.getTargetPath();
+ newDir = alignToBaseDirectory(oldDir, basedir);
+ if (newDir != oldDir) {
+ source = source.withTargetPath(newDir);
+ }
+ }
+ return source;
+ }
+
/**
* Returns a resource with all properties identical to the given resource, except the paths
* which are resolved according the given {@code basedir}. If the paths are unchanged, then
@@ -120,7 +158,7 @@ private Resource alignToBaseDirectory(Resource resource, Path basedir) {
String oldDir = resource.getDirectory();
String newDir = alignToBaseDirectory(oldDir, basedir);
if (newDir != oldDir) {
- return resource.with().directory(newDir).build();
+ return resource.withDirectory(newDir);
}
}
return resource;