diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
index cdcb385a7b5d..118ba1ac1dbd 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementImporter.java
@@ -45,4 +45,21 @@ Model importManagement(
List extends DependencyManagement> sources,
ModelBuilderRequest request,
ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #importManagement}.
+ *
+ * @since 4.0.0
+ */
+ default void importManagement(
+ Model.Builder builder,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = importManagement(built, sources, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
index fb91b232cf60..fc29dc218e58 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/DependencyManagementInjector.java
@@ -38,4 +38,24 @@ public interface DependencyManagementInjector {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that operates on a {@link Model.Builder} directly,
+ * avoiding an intermediate {@code Model.build()} between pipeline stages.
+ *
+ * The default implementation bridges to {@link #injectManagement(Model, ModelBuilderRequest, ModelProblemCollector)}
+ * by building the model, processing it, and resetting the builder to the result.
+ *
+ * @param builder The model builder to modify in place, must not be {@code null}.
+ * @param request The model building request, must not be {@code null}.
+ * @param problems The container used to collect problems, must not be {@code null}.
+ * @since 4.0.0
+ */
+ default void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectManagement(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
index 59b24f370d04..3fdb1d3a6e81 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/InheritanceAssembler.java
@@ -42,4 +42,18 @@ public interface InheritanceAssembler {
*/
Model assembleModelInheritance(
Model child, Model parent, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that merges parent values into the child builder directly.
+ *
+ * @since 4.0.0
+ */
+ default void assembleModelInheritance(
+ Model.Builder childBuilder, Model parent, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = childBuilder.build();
+ Model result = assembleModelInheritance(built, parent, request, problems);
+ if (result != built) {
+ childBuilder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
index c24a8a4d7f1e..4d678b21b4d3 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelInterpolator.java
@@ -51,4 +51,21 @@ Model interpolateModel(
@Nullable Path projectDir,
@Nonnull ModelBuilderRequest request,
@Nonnull ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #interpolateModel}.
+ *
+ * @since 4.0.0
+ */
+ default void interpolateModel(
+ @Nonnull Model.Builder builder,
+ @Nullable Path projectDir,
+ @Nonnull ModelBuilderRequest request,
+ @Nonnull ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = interpolateModel(built, projectDir, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
index 50225fd417a5..a8b19a4cd9cb 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelNormalizer.java
@@ -48,4 +48,31 @@ public interface ModelNormalizer {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectDefaultValues(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #mergeDuplicates}.
+ *
+ * @since 4.0.0
+ */
+ default void mergeDuplicates(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = mergeDuplicates(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
+
+ /**
+ * Builder-accepting variant of {@link #injectDefaultValues}.
+ *
+ * @since 4.0.0
+ */
+ default void injectDefaultValues(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectDefaultValues(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
index c2ec4ce522ac..eee49ecf486e 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelPathTranslator.java
@@ -40,4 +40,17 @@ public interface ModelPathTranslator {
* @since 4.0.0
*/
Model alignToBaseDirectory(Model model, Path basedir, ModelBuilderRequest request);
+
+ /**
+ * Builder-accepting variant of {@link #alignToBaseDirectory}.
+ *
+ * @since 4.0.0
+ */
+ default void alignToBaseDirectory(Model.Builder builder, Path basedir, ModelBuilderRequest request) {
+ Model built = builder.build();
+ Model result = alignToBaseDirectory(built, basedir, request);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
index a216b99d7a86..f89cf80e9c1c 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ModelUrlNormalizer.java
@@ -36,4 +36,17 @@ public interface ModelUrlNormalizer {
* @param request The model building request that holds further settings, must not be {@code null}.
*/
Model normalize(Model model, ModelBuilderRequest request);
+
+ /**
+ * Builder-accepting variant of {@link #normalize}.
+ *
+ * @since 4.0.0
+ */
+ default void normalize(Model.Builder builder, ModelBuilderRequest request) {
+ Model built = builder.build();
+ Model result = normalize(built, request);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
index bdd249489df0..a5f15cb111ea 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginConfigurationExpander.java
@@ -37,4 +37,18 @@ public interface PluginConfigurationExpander {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #expandPluginConfiguration}.
+ *
+ * @since 4.0.0
+ */
+ default void expandPluginConfiguration(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = expandPluginConfiguration(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
index 36d7f7e19f86..a2e39c63ca40 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/PluginManagementInjector.java
@@ -38,4 +38,17 @@ public interface PluginManagementInjector {
* @param problems The container used to collect problems that were encountered, must not be {@code null}.
*/
Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant of {@link #injectManagement}.
+ *
+ * @since 4.0.0
+ */
+ default void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectManagement(built, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
index 7361318f9801..256ecabdbf17 100644
--- a/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
+++ b/api/maven-api-spi/src/main/java/org/apache/maven/api/services/model/ProfileInjector.java
@@ -57,4 +57,21 @@ default Model injectProfile(
*/
Model injectProfiles(
Model model, List profiles, ModelBuilderRequest request, ModelProblemCollector problems);
+
+ /**
+ * Builder-accepting variant that injects profile values into the model builder directly.
+ *
+ * @since 4.0.0
+ */
+ default void injectProfiles(
+ Model.Builder builder,
+ List profiles,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Model built = builder.build();
+ Model result = injectProfiles(built, profiles, request, problems);
+ if (result != built) {
+ builder.reset(result);
+ }
+ }
}
diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
index 640579cb8871..b5b785c7cb7f 100644
--- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
+++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileToRawModelMergerTest.java
@@ -40,6 +40,10 @@ class FileToRawModelMergerTest {
void testOverriddenMergeMethods() {
List methodNames = Stream.of(MavenMerger.class.getDeclaredMethods())
.filter(m -> m.getName().startsWith("merge"))
+ // Exclude *ToBuilder variants and void methods whose first parameter
+ // is a Builder — only the object-returning merge methods need overriding
+ .filter(m -> !m.getName().endsWith("ToBuilder"))
+ .filter(m -> !m.getParameterTypes()[0].getSimpleName().equals("Builder"))
.filter(m -> {
String baseName = m.getName().substring(5 /* merge */);
String entity = baseName.substring(baseName.indexOf('_') + 1);
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelUrlNormalizer.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelUrlNormalizer.java
index 36bdb746b59a..fe82c6d41990 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelUrlNormalizer.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultModelUrlNormalizer.java
@@ -52,26 +52,35 @@ public Model normalize(Model model, ModelBuilderRequest request) {
}
Model.Builder builder = Model.newBuilder(model);
- builder.url(normalize(model.getUrl()));
+ normalizeBuilder(builder);
+ return builder.build();
+ }
+
+ @Override
+ public void normalize(Model.Builder builder, ModelBuilderRequest request) {
+ normalizeBuilder(builder);
+ }
+
+ private void normalizeBuilder(Model.Builder builder) {
+ builder.url(normalize(builder.getUrl()));
- Scm scm = model.getScm();
+ Scm scm = builder.getScm();
if (scm != null) {
- builder.scm(Scm.newBuilder(scm)
+ builder.getModifiableScm()
.url(normalize(scm.getUrl()))
.connection(normalize(scm.getConnection()))
- .developerConnection(normalize(scm.getDeveloperConnection()))
- .build());
+ .developerConnection(normalize(scm.getDeveloperConnection()));
}
- DistributionManagement dist = model.getDistributionManagement();
+ DistributionManagement dist = builder.getDistributionManagement();
if (dist != null) {
Site site = dist.getSite();
if (site != null) {
- builder.distributionManagement(dist.withSite(site.withUrl(normalize(site.getUrl()))));
+ builder.getModifiableDistributionManagement()
+ .getModifiableSite()
+ .url(normalize(site.getUrl()));
}
}
-
- return builder.build();
}
private String normalize(String url) {
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
index fc159b8ccd2a..95a4c2f9869c 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultPluginConfigurationExpander.java
@@ -44,6 +44,26 @@
@Singleton
public class DefaultPluginConfigurationExpander implements PluginConfigurationExpander {
+ @Override
+ public void expandPluginConfiguration(
+ Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+
+ // Mutate the Build sub-builder in place to avoid intermediate immutable allocations
+ Build build = builder.getBuild();
+ if (build != null) {
+ Build.Builder bb = builder.getModifiableBuild();
+ bb.plugins(expandPlugin(build.getPlugins()));
+ PluginManagement pluginManagement = build.getPluginManagement();
+ if (pluginManagement != null) {
+ bb.getModifiablePluginManagement().plugins(expandPlugin(pluginManagement.getPlugins()));
+ }
+ }
+ Reporting reporting = builder.getReporting();
+ if (reporting != null) {
+ expandReport(reporting.getPlugins());
+ }
+ }
+
@Override
public Model expandPluginConfiguration(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
Build build = model.getBuild();
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
index 45de07f83a3c..314b6a57f409 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
@@ -56,43 +56,63 @@ public Model importManagement(
ModelBuilderRequest request,
ModelProblemCollector problems) {
if (sources != null && !sources.isEmpty()) {
- Map dependencies = new LinkedHashMap<>();
+ Map dependencies =
+ collectDependencies(target.getDependencyManagement(), sources, request, problems);
+ return target.withDependencyManagement(
+ target.getDependencyManagement().withDependencies(dependencies.values()));
+ }
+ return target;
+ }
+
+ @Override
+ public void importManagement(
+ Model.Builder builder,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ if (sources != null && !sources.isEmpty()) {
+ Map dependencies =
+ collectDependencies(builder.getDependencyManagement(), sources, request, problems);
+ builder.getModifiableDependencyManagement().dependencies(dependencies.values());
+ }
+ }
- DependencyManagement depMgmt = target.getDependencyManagement();
+ private Map collectDependencies(
+ DependencyManagement depMgmt,
+ List extends DependencyManagement> sources,
+ ModelBuilderRequest request,
+ ModelProblemCollector problems) {
+ Map dependencies = new LinkedHashMap<>();
- if (depMgmt != null) {
- for (Dependency dependency : depMgmt.getDependencies()) {
- dependencies.put(dependency.getManagementKey(), dependency);
- }
- } else {
- depMgmt = DependencyManagement.newInstance();
+ if (depMgmt != null) {
+ for (Dependency dependency : depMgmt.getDependencies()) {
+ dependencies.put(dependency.getManagementKey(), dependency);
}
+ }
- Set directDependencies = new HashSet<>(dependencies.keySet());
-
- for (DependencyManagement source : sources) {
- for (Dependency dependency : source.getDependencies()) {
- String key = dependency.getManagementKey();
- Dependency present = dependencies.putIfAbsent(key, dependency);
- if (present != null && !equals(dependency, present) && !directDependencies.contains(key)) {
- // TODO: https://issues.apache.org/jira/browse/MNG-8004
- problems.add(
- Severity.WARNING,
- Version.V40,
- "Ignored POM import for: " + toString(dependency) + " as already imported "
- + toString(present) + ". Add the conflicting managed dependency directly "
- + "to the dependencyManagement section of the POM.");
- }
- if (present == null && request.isLocationTracking()) {
- Dependency updatedDependency = updateWithImportedFrom(dependency, source);
- dependencies.put(key, updatedDependency);
- }
+ Set directDependencies = new HashSet<>(dependencies.keySet());
+
+ for (DependencyManagement source : sources) {
+ for (Dependency dependency : source.getDependencies()) {
+ String key = dependency.getManagementKey();
+ Dependency present = dependencies.putIfAbsent(key, dependency);
+ if (present != null && !equals(dependency, present) && !directDependencies.contains(key)) {
+ // TODO: https://issues.apache.org/jira/browse/MNG-8004
+ problems.add(
+ Severity.WARNING,
+ Version.V40,
+ "Ignored POM import for: " + toString(dependency) + " as already imported "
+ + toString(present) + ". Add the conflicting managed dependency directly "
+ + "to the dependencyManagement section of the POM.");
+ }
+ if (present == null && request.isLocationTracking()) {
+ Dependency updatedDependency = updateWithImportedFrom(dependency, source);
+ dependencies.put(key, updatedDependency);
}
}
-
- return target.withDependencyManagement(depMgmt.withDependencies(dependencies.values()));
}
- return target;
+
+ return dependencies;
}
private String toString(Dependency dependency) {
@@ -167,6 +187,8 @@ static Dependency updateWithImportedFrom(Dependency dependency, DependencyManage
if (dependencySource == null
|| bomSource == null
|| Objects.equals(dependencySource.getModelId(), bomSource.getModelId())) {
+ // Use forceCopy=true since we only set importedFrom (no field changes that would
+ // trigger copy-on-write), and build immediately as we need the immutable result.
return Dependency.newBuilder(dependency, true)
.importedFrom(bomLocation)
.build();
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
index d5c7d5d3e1b1..1de10c725700 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementInjector.java
@@ -45,6 +45,20 @@ public class DefaultDependencyManagementInjector implements DependencyManagement
private ManagementModelMerger merger = new ManagementModelMerger();
+ @Override
+ public void injectManagement(Model.Builder builder, ModelBuilderRequest request, ModelProblemCollector problems) {
+ // Use builder getters instead of builder.build() to avoid materializing
+ // all model-object lists just to read Dependencies and DependencyManagement
+ DependencyManagement depMgmt = builder.getDependencyManagement();
+ if (depMgmt != null) {
+ List deps = builder.getBuiltDependencies();
+ List merged = merger.computeMergedDependencies(deps, depMgmt);
+ if (merged != null) {
+ builder.dependencies(merged);
+ }
+ }
+ }
+
@Override
public Model injectManagement(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
return merger.mergeManagedDependencies(model);
@@ -55,41 +69,58 @@ public Model injectManagement(Model model, ModelBuilderRequest request, ModelPro
*/
protected static class ManagementModelMerger extends MavenModelMerger {
- public Model mergeManagedDependencies(Model model) {
+ /**
+ * Computes the merged dependency list, or returns {@code null} if no dependencies were modified.
+ */
+ List computeMergedDependencies(Model model) {
DependencyManagement dependencyManagement = model.getDependencyManagement();
if (dependencyManagement != null) {
- Map