This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git

commit f92a8ee5536801ad4e296e4a2b6e2877c246a3db
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Oct 25 15:44:10 2025 +0200

    Refactor `DefaultSourceRoot` as a record. It removes about a third of the 
code and forces
    us to be more consistent since all constructions must pass by the canonical 
constructor.
    This refactored class should have the same behavior as the previous class 
(no new feature).
---
 .../maven/project/DefaultProjectBuilder.java       |   2 +-
 .../org/apache/maven/impl/DefaultSourceRoot.java   | 333 +++++++--------------
 .../apache/maven/impl/DefaultSourceRootTest.java   |  10 +-
 3 files changed, 122 insertions(+), 223 deletions(-)

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 08f3bd53b2..f18d590b61 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
@@ -654,7 +654,7 @@ private void initProject(MavenProject project, 
ModelBuilderResult result) {
                 boolean hasMain = false;
                 boolean hasTest = false;
                 for (var source : sources) {
-                    var src = new DefaultSourceRoot(session, baseDir, source);
+                    var src = DefaultSourceRoot.fromModel(session, baseDir, 
source);
                     project.addSourceRoot(src);
                     Language language = src.language();
                     if (Language.JAVA_FAMILY.equals(language)) {
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
index dad9d3cdae..57994c0fe1 100644
--- 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
@@ -30,32 +30,86 @@
 import org.apache.maven.api.Session;
 import org.apache.maven.api.SourceRoot;
 import org.apache.maven.api.Version;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.model.Resource;
 import org.apache.maven.api.model.Source;
 
 /**
  * A default implementation of {@code SourceRoot} built from the model.
+ *
+ * @param scope               in which context the source files will be used 
(main or test)
+ * @param language            language of the source files
+ * @param moduleName          name of the Java module which is built by the 
sources
+ * @param targetVersionOrNull version of the platform where the code will be 
executed
+ * @param directory           root directory where the sources are stored
+ * @param includes            patterns for the files to include, or empty if 
unspecified
+ * @param excludes            patterns for the files to exclude, or empty if 
nothing to exclude
+ * @param stringFiltering     whether resources are filtered to replace tokens 
with parameterized values
+ * @param targetPathOrNull    an explicit target path, overriding the default 
value
+ * @param enabled             whether the directory described by this source 
element should be included in the build
  */
-public final class DefaultSourceRoot implements SourceRoot {
-    private final Path directory;
-
-    private final List<String> includes;
-
-    private final List<String> 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;
+public record DefaultSourceRoot(
+        @Nonnull ProjectScope scope,
+        @Nonnull Language language,
+        @Nullable String moduleName,
+        @Nullable Version targetVersionOrNull,
+        @Nonnull Path directory,
+        @Nonnull List<String> includes,
+        @Nonnull List<String> excludes,
+        boolean stringFiltering,
+        @Nullable Path targetPathOrNull,
+        boolean enabled)
+        implements SourceRoot {
+
+    /**
+     * Creates a simple instance with no Java module, no target version, and 
no include or exclude pattern.
+     *
+     * @param scope     in which context the source files will be used (main 
or test)
+     * @param language  the language of the source files
+     * @param directory the root directory where the sources are stored
+     */
+    public DefaultSourceRoot(ProjectScope scope, Language language, Path 
directory) {
+        this(scope, language, null, null, directory, null, null, false, null, 
true);
+    }
 
-    private final boolean enabled;
+    /**
+     * Canonical constructor.
+     *
+     * @param scope               in which context the source files will be 
used (main or test)
+     * @param language            language of the source files
+     * @param moduleName          name of the Java module which is built by 
the sources
+     * @param targetVersionOrNull version of the platform where the code will 
be executed
+     * @param directory           root directory where the sources are stored
+     * @param includes            patterns for the files to include, or {@code 
null} or empty if unspecified
+     * @param excludes            patterns for the files to exclude, or {@code 
null} or empty if nothing to exclude
+     * @param stringFiltering     whether resources are filtered to replace 
tokens with parameterized values
+     * @param targetPathOrNull    an explicit target path, overriding the 
default value
+     * @param enabled             whether the directory described by this 
source element should be included in the build
+     */
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    public DefaultSourceRoot(
+            @Nonnull ProjectScope scope,
+            @Nonnull Language language,
+            @Nullable String moduleName,
+            @Nullable Version targetVersionOrNull,
+            @Nullable Path directory,
+            @Nullable List<String> includes,
+            @Nullable List<String> excludes,
+            boolean stringFiltering,
+            @Nullable Path targetPathOrNull,
+            boolean enabled) {
+        this.scope = Objects.requireNonNull(scope);
+        this.language = Objects.requireNonNull(language);
+        this.moduleName = nonBlank(moduleName).orElse(null);
+        this.targetVersionOrNull = targetVersionOrNull;
+        this.directory = directory.normalize();
+        this.includes = (includes != null) ? List.copyOf(includes) : List.of();
+        this.excludes = (excludes != null) ? List.copyOf(excludes) : List.of();
+        this.stringFiltering = stringFiltering;
+        this.targetPathOrNull = (targetPathOrNull != null) ? 
targetPathOrNull.normalize() : null;
+        this.enabled = enabled;
+    }
 
     /**
      * Creates a new instance from the given model.
@@ -64,35 +118,29 @@ public final class DefaultSourceRoot implements SourceRoot 
{
      * @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) {
-        includes = source.getIncludes();
-        excludes = source.getExcludes();
-        stringFiltering = source.isStringFiltering();
-        enabled = source.isEnabled();
-        moduleName = nonBlank(source.getModule());
-
-        String 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.getDirectory());
-        if (value != null) {
-            directory = baseDir.resolve(value);
-        } else {
-            Path src = baseDir.resolve("src");
-            if (moduleName != null) {
-                src = src.resolve(moduleName);
-            }
-            directory = src.resolve(scope.id()).resolve(language.id());
-        }
-
-        value = nonBlank(source.getTargetVersion());
-        targetVersion = (value != null) ? session.parseVersion(value) : null;
-
-        value = nonBlank(source.getTargetPath());
-        targetPath = (value != null) ? baseDir.resolve(value) : null;
+    public static DefaultSourceRoot fromModel(final Session session, final 
Path baseDir, final Source source) {
+        ProjectScope scope =
+                
nonBlank(source.getScope()).map(session::requireProjectScope).orElse(ProjectScope.MAIN);
+        Language language =
+                
nonBlank(source.getLang()).map(session::requireLanguage).orElse(Language.JAVA_FAMILY);
+        String moduleName = nonBlank(source.getModule()).orElse(null);
+        return new DefaultSourceRoot(
+                scope,
+                language,
+                moduleName,
+                
nonBlank(source.getTargetVersion()).map(session::parseVersion).orElse(null),
+                
nonBlank(source.getDirectory()).map(baseDir::resolve).orElseGet(() -> {
+                    Path src = baseDir.resolve("src");
+                    if (moduleName != null) {
+                        src = src.resolve(moduleName);
+                    }
+                    return src.resolve(scope.id()).resolve(language.id());
+                }),
+                source.getIncludes(),
+                source.getExcludes(),
+                source.isStringFiltering(),
+                
nonBlank(source.getTargetPath()).map(baseDir::resolve).orElse(null),
+                source.isEnabled());
     }
 
     /**
@@ -104,107 +152,32 @@ public DefaultSourceRoot(final Session session, final 
Path baseDir, final Source
      * @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();
-        includes = resource.getIncludes();
-        excludes = resource.getExcludes();
-        stringFiltering = Boolean.parseBoolean(resource.getFiltering());
-        enabled = true;
-        moduleName = null;
-        this.scope = scope;
-        language = Language.RESOURCES;
-        targetVersion = null;
-        value = nonBlank(resource.getTargetPath());
-        targetPath = (value != null) ? baseDir.resolve(value).normalize() : 
null;
-    }
-
-    /**
-     * Creates a new instance for the given directory and scope.
-     *
-     * @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 = Objects.requireNonNull(language);
-        this.directory = Objects.requireNonNull(directory);
-        includes = List.of();
-        excludes = List.of();
-        moduleName = null;
-        targetVersion = null;
-        targetPath = null;
-        stringFiltering = false;
-        enabled = true;
-    }
-
-    /**
-     * Creates a new instance for the given directory and scope.
-     *
-     * @param scope scope of source code (main or test)
-     * @param language language of the source code
-     * @param directory directory of the source code
-     * @param includes patterns for the files to include, or {@code null} or 
empty if unspecified
-     * @param excludes patterns for the files to exclude, or {@code null} or 
empty if nothing to exclude
-     */
-    public DefaultSourceRoot(
-            final ProjectScope scope,
-            final Language language,
-            final Path directory,
-            List<String> includes,
-            List<String> excludes) {
-        this.scope = Objects.requireNonNull(scope);
-        this.language = language;
-        this.directory = Objects.requireNonNull(directory);
-        this.includes = includes != null ? List.copyOf(includes) : List.of();
-        this.excludes = excludes != null ? List.copyOf(excludes) : List.of();
-        moduleName = null;
-        targetVersion = null;
-        targetPath = null;
-        stringFiltering = false;
-        enabled = true;
+        this(
+                scope,
+                Language.RESOURCES,
+                null,
+                null,
+                baseDir.resolve(nonBlank(resource.getDirectory())
+                        .orElseThrow(
+                                () -> new IllegalArgumentException("Source 
declaration without directory value."))),
+                resource.getIncludes(),
+                resource.getExcludes(),
+                Boolean.parseBoolean(resource.getFiltering()),
+                
nonBlank(resource.getTargetPath()).map(baseDir::resolve).orElse(null),
+                true);
     }
 
     /**
-     * {@return the given value as a trimmed non-blank string, or null 
otherwise}.
+     * {@return the given value as a trimmed non-blank string, or empty 
otherwise}
      */
-    private static String nonBlank(String value) {
+    private static Optional<String> nonBlank(String value) {
         if (value != null) {
             value = value.trim();
-            if (value.isBlank()) {
-                value = null;
+            if (!value.isBlank()) {
+                return Optional.of(value);
             }
         }
-        return value;
-    }
-
-    /**
-     * {@return the root directory where the sources are stored}.
-     */
-    @Override
-    public Path directory() {
-        return directory;
-    }
-
-    /**
-     * {@return the patterns for the files to include}.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because 
unmodifiable
-    public List<String> includes() {
-        return includes;
-    }
-
-    /**
-     * {@return the patterns for the files to exclude}.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because 
unmodifiable
-    public List<String> excludes() {
-        return excludes;
+        return Optional.empty();
     }
 
     /**
@@ -223,22 +196,6 @@ public PathMatcher matcher(Collection<String> 
defaultIncludes, boolean useDefaul
         return PathSelector.of(directory(), actual, excludes(), 
useDefaultExcludes);
     }
 
-    /**
-     * {@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}
      */
@@ -252,7 +209,7 @@ public Optional<String> module() {
      */
     @Override
     public Optional<Version> targetVersion() {
-        return Optional.ofNullable(targetVersion);
+        return Optional.ofNullable(targetVersionOrNull);
     }
 
     /**
@@ -260,64 +217,6 @@ public Optional<Version> targetVersion() {
      */
     @Override
     public Optional<Path> 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;
+        return Optional.ofNullable(targetPathOrNull);
     }
 }
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
index e27cfa3109..446a0315e9 100644
--- 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
@@ -58,7 +58,7 @@ public void setup() {
 
     @Test
     void testMainJavaDirectory() {
-        var source = new DefaultSourceRoot(
+        var source = DefaultSourceRoot.fromModel(
                 session, Path.of("myproject"), Source.newBuilder().build());
 
         assertTrue(source.module().isEmpty());
@@ -70,7 +70,7 @@ void testMainJavaDirectory() {
 
     @Test
     void testTestJavaDirectory() {
-        var source = new DefaultSourceRoot(
+        var source = DefaultSourceRoot.fromModel(
                 session, Path.of("myproject"), 
Source.newBuilder().scope("test").build());
 
         assertTrue(source.module().isEmpty());
@@ -82,7 +82,7 @@ void testTestJavaDirectory() {
 
     @Test
     void testTestResourceDirectory() {
-        var source = new DefaultSourceRoot(
+        var source = DefaultSourceRoot.fromModel(
                 session,
                 Path.of("myproject"),
                 Source.newBuilder().scope("test").lang("resources").build());
@@ -96,7 +96,7 @@ void testTestResourceDirectory() {
 
     @Test
     void testModuleMainDirectory() {
-        var source = new DefaultSourceRoot(
+        var source = DefaultSourceRoot.fromModel(
                 session,
                 Path.of("myproject"),
                 Source.newBuilder().module("org.foo.bar").build());
@@ -110,7 +110,7 @@ void testModuleMainDirectory() {
 
     @Test
     void testModuleTestDirectory() {
-        var source = new DefaultSourceRoot(
+        var source = DefaultSourceRoot.fromModel(
                 session,
                 Path.of("myproject"),
                 
Source.newBuilder().module("org.foo.bar").scope("test").build());

Reply via email to