userProperties, String key, boolean def) {
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/package-info.java b/api/maven-api-core/src/main/java/org/apache/maven/api/package-info.java
index 7fd2d60b9591..35f25fda6e25 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/package-info.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/package-info.java
@@ -119,9 +119,9 @@
*
* Project aggregation allows building several projects together. This is only
* for projects that are built, hence available on the file system. One project,
- * called the aggregator project lists one or more modules
+ * called the aggregator project lists one or more sub-projects
* which are relative pointers on the file system to other projects. This is done using
- * the {@code /project/modules/module} elements of the POM in the aggregator project.
+ * the {@code /project/subprojects/subproject} elements of the POM in the aggregator project.
* Note that the aggregator project is required to have a {@code pom} packaging.
*
* Project inheritance defines a parent-child relationship between projects.
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java
index 14d2c7a2ef1c..28a7936fed4c 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java
@@ -18,6 +18,10 @@
*/
/**
- * Maven Plugin Annotations.
+ * Provides annotations for Maven plugin development, including mojo configuration,
+ * parameter definitions, and lifecycle bindings. These annotations are used to
+ * generate plugin descriptors and configure plugin behavior.
+ *
+ * @since 4.0.0
*/
package org.apache.maven.api.plugin.annotations;
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ArtifactResolverRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ArtifactResolverRequest.java
index fb012fab30df..7e832a95e41f 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ArtifactResolverRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ArtifactResolverRequest.java
@@ -40,14 +40,11 @@
*/
@Experimental
@Immutable
-public interface ArtifactResolverRequest extends Request {
+public interface ArtifactResolverRequest extends RepositoryAwareRequest {
@Nonnull
Collection extends ArtifactCoordinates> getCoordinates();
- @Nullable
- List getRepositories();
-
@Nonnull
static ArtifactResolverRequestBuilder builder() {
return new ArtifactResolverRequestBuilder();
@@ -127,7 +124,7 @@ private static class DefaultArtifactResolverRequest extends BaseRequest
@Nonnull List repositories) {
super(session, trace);
this.coordinates = List.copyOf(requireNonNull(coordinates, "coordinates cannot be null"));
- this.repositories = repositories;
+ this.repositories = validate(repositories);
}
@Nonnull
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
index f419d7ff60a7..5be250824d75 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/DependencyResolverRequest.java
@@ -34,6 +34,8 @@
import org.apache.maven.api.Project;
import org.apache.maven.api.RemoteRepository;
import org.apache.maven.api.Session;
+import org.apache.maven.api.SourceRoot;
+import org.apache.maven.api.Version;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Immutable;
import org.apache.maven.api.annotations.Nonnull;
@@ -53,7 +55,7 @@
*/
@Experimental
@Immutable
-public interface DependencyResolverRequest extends Request {
+public interface DependencyResolverRequest extends RepositoryAwareRequest {
enum RequestType {
COLLECT,
@@ -95,8 +97,27 @@ enum RequestType {
@Nullable
Predicate getPathTypeFilter();
+ /**
+ * Returns the version of the platform where the code will be executed.
+ * It should be the highest value of the {@code } elements
+ * inside the {@code } elements of a POM file.
+ *
+ * Application to Java
+ * In the context of a Java project, this is the value given to the {@code --release} compiler option.
+ * This value can determine whether a dependency will be placed on the class-path or on the module-path.
+ * For example, if the {@code module-info.class} entry of a JAR file exists only in the
+ * {@code META-INF/versions/17/} sub-directory, then the default location of that dependency will be
+ * the module-path only if the {@code --release} option is equal or greater than 17.
+ *
+ * If this value is not provided, then the default value in the context of Java projects
+ * is the Java version on which Maven is running, as given by {@link Runtime#version()}.
+ *
+ * @return version of the platform where the code will be executed, or {@code null} for default
+ *
+ * @see SourceRoot#targetVersion()
+ */
@Nullable
- List getRepositories();
+ Version getTargetVersion();
@Nonnull
static DependencyResolverRequestBuilder builder() {
@@ -181,6 +202,7 @@ class DependencyResolverRequestBuilder {
boolean verbose;
PathScope pathScope;
Predicate pathTypeFilter;
+ Version targetVersion;
List repositories;
DependencyResolverRequestBuilder() {}
@@ -345,6 +367,18 @@ public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Collection ext
return pathTypeFilter(desiredTypes::contains);
}
+ /**
+ * Sets the version of the platform where the code will be executed.
+ *
+ * @param target version of the platform where the code will be executed, or {@code null} for the default
+ * @return {@code this} for method call chaining
+ */
+ @Nonnull
+ public DependencyResolverRequestBuilder targetVersion(@Nullable Version target) {
+ targetVersion = target;
+ return this;
+ }
+
@Nonnull
public DependencyResolverRequestBuilder repositories(@Nonnull List repositories) {
this.repositories = repositories;
@@ -365,6 +399,7 @@ public DependencyResolverRequest build() {
verbose,
pathScope,
pathTypeFilter,
+ targetVersion,
repositories);
}
@@ -404,6 +439,7 @@ public String toString() {
private final boolean verbose;
private final PathScope pathScope;
private final Predicate pathTypeFilter;
+ private final Version targetVersion;
private final List repositories;
/**
@@ -426,6 +462,7 @@ public String toString() {
boolean verbose,
@Nullable PathScope pathScope,
@Nullable Predicate pathTypeFilter,
+ @Nullable Version targetVersion,
@Nullable List repositories) {
super(session, trace);
this.requestType = requireNonNull(requestType, "requestType cannot be null");
@@ -438,7 +475,8 @@ public String toString() {
this.verbose = verbose;
this.pathScope = requireNonNull(pathScope, "pathScope cannot be null");
this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : DEFAULT_FILTER;
- this.repositories = repositories;
+ this.targetVersion = targetVersion;
+ this.repositories = validate(repositories);
if (verbose && requestType != RequestType.COLLECT) {
throw new IllegalArgumentException("verbose cannot only be true when collecting dependencies");
}
@@ -495,6 +533,11 @@ public Predicate getPathTypeFilter() {
return pathTypeFilter;
}
+ @Override
+ public Version getTargetVersion() {
+ return targetVersion;
+ }
+
@Override
public List getRepositories() {
return repositories;
@@ -512,6 +555,7 @@ public boolean equals(Object o) {
&& Objects.equals(managedDependencies, that.managedDependencies)
&& Objects.equals(pathScope, that.pathScope)
&& Objects.equals(pathTypeFilter, that.pathTypeFilter)
+ && Objects.equals(targetVersion, that.targetVersion)
&& Objects.equals(repositories, that.repositories);
}
@@ -527,6 +571,7 @@ public int hashCode() {
verbose,
pathScope,
pathTypeFilter,
+ targetVersion,
repositories);
}
@@ -541,7 +586,8 @@ public String toString() {
+ managedDependencies + ", verbose="
+ verbose + ", pathScope="
+ pathScope + ", pathTypeFilter="
- + pathTypeFilter + ", repositories="
+ + pathTypeFilter + ", targetVersion="
+ + targetVersion + ", repositories="
+ repositories + ']';
}
}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
index 14141a6d0c6c..bb8f524749da 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java
@@ -43,7 +43,7 @@
*/
@Experimental
@Immutable
-public interface ModelBuilderRequest extends Request {
+public interface ModelBuilderRequest extends RepositoryAwareRequest {
/**
* The possible request types for building a model.
@@ -133,9 +133,6 @@ enum RepositoryMerging {
@Nonnull
RepositoryMerging getRepositoryMerging();
- @Nullable
- List getRepositories();
-
@Nullable
ModelTransformer getLifecycleBindingsInjector();
@@ -335,10 +332,10 @@ private static class DefaultModelBuilderRequest extends BaseRequest imp
this.activeProfileIds = activeProfileIds != null ? List.copyOf(activeProfileIds) : List.of();
this.inactiveProfileIds = inactiveProfileIds != null ? List.copyOf(inactiveProfileIds) : List.of();
this.systemProperties =
- systemProperties != null ? Map.copyOf(systemProperties) : session.getSystemProperties();
- this.userProperties = userProperties != null ? Map.copyOf(userProperties) : session.getUserProperties();
+ Map.copyOf(systemProperties != null ? systemProperties : session.getSystemProperties());
+ this.userProperties = Map.copyOf(userProperties != null ? userProperties : session.getUserProperties());
this.repositoryMerging = repositoryMerging;
- this.repositories = repositories != null ? List.copyOf(repositories) : null;
+ this.repositories = repositories != null ? List.copyOf(validate(repositories)) : null;
this.lifecycleBindingsInjector = lifecycleBindingsInjector;
}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
index 4b15818cf033..854f8dcc01d9 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderResult.java
@@ -19,6 +19,7 @@
package org.apache.maven.api.services;
import java.util.List;
+import java.util.Map;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
@@ -81,6 +82,27 @@ public interface ModelBuilderResult extends Result {
@Nonnull
List getActivePomProfiles();
+ /**
+ * Gets the profiles that were active during model building for a specific model in the hierarchy.
+ * This allows tracking which profiles came from which model (parent vs child).
+ *
+ * @param modelId The identifier of the model (groupId:artifactId:version) or empty string for the super POM.
+ * @return The active profiles for the specified model or an empty list if the model has no active profiles.
+ * @since 4.0.0
+ */
+ @Nonnull
+ List getActivePomProfiles(String modelId);
+
+ /**
+ * Gets a map of all active POM profiles organized by model ID.
+ * The map keys are model IDs (groupId:artifactId:version) and values are lists of active profiles for each model.
+ *
+ * @return A map of model IDs to their active profiles, never {@code null}.
+ * @since 4.0.0
+ */
+ @Nonnull
+ Map> getActivePomProfilesByModel();
+
/**
* Gets the external profiles that were active during model building. External profiles are those that were
* contributed by {@link ModelBuilderRequest#getProfiles()}.
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathMatcherFactory.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathMatcherFactory.java
new file mode 100644
index 000000000000..9f83e2e0f8bf
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/PathMatcherFactory.java
@@ -0,0 +1,163 @@
+/*
+ * 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.services;
+
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.maven.api.Service;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * Service for creating {@link PathMatcher} objects that can be used to filter files
+ * based on include/exclude patterns. This service provides a clean API for plugins
+ * to create path matchers without directly depending on implementation classes.
+ *
+ * The path matchers created by this service support Maven's traditional include/exclude
+ * pattern syntax, which is compatible with the behavior of Maven 3 plugins like
+ * maven-compiler-plugin and maven-clean-plugin.
+ *
+ * Pattern syntax supports:
+ *
+ * Standard glob patterns with {@code *}, {@code ?}, and {@code **} wildcards
+ * Explicit syntax prefixes like {@code "glob:"} or {@code "regex:"}
+ * Maven 3 compatible behavior for patterns without explicit syntax
+ * Default exclusion patterns for SCM files when requested
+ *
+ *
+ * @since 4.0.0
+ * @see PathMatcher
+ */
+@Experimental
+public interface PathMatcherFactory extends Service {
+
+ /**
+ * Creates a path matcher for filtering files based on include and exclude patterns.
+ *
+ * The pathnames used for matching will be relative to the specified base directory
+ * and use {@code '/'} as separator, regardless of the hosting operating system.
+ *
+ * @param baseDirectory the base directory for relativizing paths during matching
+ * @param includes the patterns of files to include, or null/empty for including all files
+ * @param excludes the patterns of files to exclude, or null/empty for no exclusion
+ * @param useDefaultExcludes whether to augment excludes with default SCM exclusion patterns
+ * @return a PathMatcher that can be used to test if paths should be included
+ * @throws NullPointerException if baseDirectory is null
+ */
+ @Nonnull
+ PathMatcher createPathMatcher(
+ @Nonnull Path baseDirectory,
+ Collection includes,
+ Collection excludes,
+ boolean useDefaultExcludes);
+
+ /**
+ * Creates a path matcher for filtering files based on include and exclude patterns,
+ * without using default exclusion patterns.
+ *
+ * This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
+ * with {@code useDefaultExcludes = false}.
+ *
+ * @param baseDirectory the base directory for relativizing paths during matching
+ * @param includes the patterns of files to include, or null/empty for including all files
+ * @param excludes the patterns of files to exclude, or null/empty for no exclusion
+ * @return a PathMatcher that can be used to test if paths should be included
+ * @throws NullPointerException if baseDirectory is null
+ */
+ @Nonnull
+ default PathMatcher createPathMatcher(
+ @Nonnull Path baseDirectory, Collection includes, Collection excludes) {
+ return createPathMatcher(baseDirectory, includes, excludes, false);
+ }
+
+ /**
+ * Creates a path matcher that includes all files except those matching the exclude patterns.
+ *
+ * This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
+ * with {@code includes = null}.
+ *
+ * @param baseDirectory the base directory for relativizing paths during matching
+ * @param excludes the patterns of files to exclude, or null/empty for no exclusion
+ * @param useDefaultExcludes whether to augment excludes with default SCM exclusion patterns
+ * @return a PathMatcher that can be used to test if paths should be included
+ * @throws NullPointerException if baseDirectory is null
+ */
+ @Nonnull
+ default PathMatcher createExcludeOnlyMatcher(
+ @Nonnull Path baseDirectory, Collection excludes, boolean useDefaultExcludes) {
+ return createPathMatcher(baseDirectory, null, excludes, useDefaultExcludes);
+ }
+
+ /**
+ * Creates a path matcher that only includes files matching the include patterns.
+ *
+ * This is equivalent to calling {@link #createPathMatcher(Path, Collection, Collection, boolean)}
+ * with {@code excludes = null} and {@code useDefaultExcludes = false}.
+ *
+ * @param baseDirectory the base directory for relativizing paths during matching
+ * @param includes the patterns of files to include, or null/empty for including all files
+ * @return a PathMatcher that can be used to test if paths should be included
+ * @throws NullPointerException if baseDirectory is null
+ */
+ @Nonnull
+ default PathMatcher createIncludeOnlyMatcher(@Nonnull Path baseDirectory, Collection includes) {
+ return createPathMatcher(baseDirectory, includes, null, false);
+ }
+
+ /**
+ * Returns a filter for directories that may contain paths accepted by the given matcher.
+ * The given path matcher should be an instance created by this service.
+ * The path matcher returned by this method expects directory paths.
+ * If that matcher returns {@code false}, then the directory will definitively not contain
+ * the paths selected by the matcher given in argument to this method.
+ * In such case, the whole directory and all its sub-directories can be skipped.
+ * In case of doubt, or if the matcher given in argument is not recognized by this method,
+ * then the matcher returned by this method will return {@code true}.
+ *
+ * @param fileMatcher a matcher created by one of the other methods of this interface
+ * @return filter for directories that may contain the selected files
+ * @throws NullPointerException if fileMatcher is null
+ */
+ @Nonnull
+ PathMatcher deriveDirectoryMatcher(@Nonnull PathMatcher fileMatcher);
+
+ /**
+ * Returns the path matcher that unconditionally returns {@code true} for all files.
+ * It should be the matcher returned by the other methods of this interface when the
+ * given patterns match all files.
+ *
+ * @return path matcher that unconditionally returns {@code true} for all files
+ */
+ @Nonnull
+ PathMatcher includesAll();
+
+ /**
+ * {@return whether the given matcher includes all files}.
+ * This method may conservatively returns {@code false} if case of doubt.
+ * A return value of {@code true} means that the pattern is certain to match all files.
+ *
+ * @param matcher the matcher to test
+ */
+ default boolean isIncludesAll(@Nonnull PathMatcher matcher) {
+ return Objects.requireNonNull(matcher) == includesAll();
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectBuilderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectBuilderRequest.java
index 82129b4f1b69..307ee1955947 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectBuilderRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectBuilderRequest.java
@@ -43,7 +43,7 @@
*/
@Experimental
@Immutable
-public interface ProjectBuilderRequest extends Request {
+public interface ProjectBuilderRequest extends RepositoryAwareRequest {
/**
* Gets the path to the project to build.
@@ -265,7 +265,7 @@ private static class DefaultProjectBuilderRequest extends BaseRequest
this.allowStubModel = allowStubModel;
this.recursive = recursive;
this.processPlugins = processPlugins;
- this.repositories = repositories;
+ this.repositories = validate(repositories);
}
@Nonnull
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/RepositoryAwareRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/RepositoryAwareRequest.java
new file mode 100644
index 000000000000..f948ecdea460
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/RepositoryAwareRequest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.services;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Immutable;
+import org.apache.maven.api.annotations.Nullable;
+
+/**
+ * Base interface for service requests that involve remote repository operations.
+ * This interface provides common functionality for requests that need to specify
+ * and validate remote repositories for artifact resolution, dependency collection,
+ * model building, and other Maven operations.
+ *
+ * Implementations of this interface can specify a list of remote repositories
+ * to be used during the operation. If no repositories are specified (null),
+ * the session's default remote repositories will be used. The repositories
+ * are validated to ensure they don't contain duplicates or null entries.
+ *
+ *
Remote repositories are used for:
+ *
+ * Resolving artifacts and their metadata
+ * Downloading parent POMs and dependency POMs
+ * Retrieving version information and ranges
+ * Accessing plugin artifacts and their dependencies
+ *
+ *
+ * Repository validation ensures data integrity by:
+ *
+ * Preventing duplicate repositories that could cause confusion
+ * Rejecting null repository entries that would cause failures
+ * Maintaining consistent repository ordering for reproducible builds
+ *
+ *
+ * @since 4.0.0
+ * @see RemoteRepository
+ * @see Session#getRemoteRepositories()
+ */
+@Experimental
+@Immutable
+public interface RepositoryAwareRequest extends Request {
+
+ /**
+ * Returns the list of remote repositories to be used for this request.
+ *
+ * If this method returns {@code null}, the session's default remote repositories
+ * will be used. If a non-null list is returned, it will be used instead of the
+ * session's repositories, allowing for request-specific repository configuration.
+ *
+ *
The returned list should not contain duplicate repositories (based on their
+ * equality) or null entries, as these will cause validation failures when the
+ * request is processed.
+ *
+ * @return the list of remote repositories to use, or {@code null} to use session defaults
+ * @see Session#getRemoteRepositories()
+ */
+ @Nullable
+ List getRepositories();
+
+ /**
+ * Validates a list of remote repositories to ensure data integrity.
+ *
+ * This method performs the following validations:
+ *
+ * Allows null input (returns null)
+ * Ensures no duplicate repositories exist in the list
+ * Ensures no null repository entries exist in the list
+ *
+ *
+ * Duplicate detection is based on the {@code RemoteRepository#equals(Object)}
+ * method, which typically compares repository IDs and URLs.
+ *
+ * @param repositories the list of repositories to validate, may be {@code null}
+ * @return the same list if validation passes, or {@code null} if input was {@code null}
+ * @throws IllegalArgumentException if the list contains duplicate repositories
+ * @throws IllegalArgumentException if the list contains null repository entries
+ */
+ default List validate(List repositories) {
+ if (repositories == null) {
+ return null;
+ }
+ HashSet set = new HashSet<>(repositories);
+ if (repositories.size() != set.size()) {
+ throw new IllegalArgumentException(
+ "Repository list contains duplicate entries. Each repository must be unique based on its ID and URL. "
+ + "Found " + repositories.size() + " repositories but only " + set.size()
+ + " unique entries.");
+ }
+ if (repositories.stream().anyMatch(Objects::isNull)) {
+ throw new IllegalArgumentException(
+ "Repository list contains null entries. All repository entries must be non-null RemoteRepository instances.");
+ }
+ return repositories;
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/RequestTrace.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/RequestTrace.java
index 6dafc3aeaf57..ac67cb64509e 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/RequestTrace.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/RequestTrace.java
@@ -50,7 +50,10 @@
* object being processed or any application-specific state information. May be null if no
* additional data is needed.
*/
-public record RequestTrace(@Nullable String context, @Nullable RequestTrace parent, @Nullable Object data) {
+public record RequestTrace(
+ @Nullable String context,
+ @Nullable RequestTrace parent,
+ @Nullable Object data) {
public static final String CONTEXT_PLUGIN = "plugin";
public static final String CONTEXT_PROJECT = "project";
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionRangeResolverRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionRangeResolverRequest.java
index 52abe9e89a49..50de8e9a804f 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionRangeResolverRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionRangeResolverRequest.java
@@ -32,86 +32,210 @@
import static java.util.Objects.requireNonNull;
/**
+ * A request to resolve a version range to a list of matching versions.
+ * This request is used by {@link VersionRangeResolver} to expand version ranges
+ * (e.g., "[3.8,4.0)") into concrete versions available in the configured repositories.
*
* @since 4.0.0
*/
@Experimental
-public interface VersionRangeResolverRequest extends Request {
+public interface VersionRangeResolverRequest extends RepositoryAwareRequest {
+ /**
+ * Specifies which type of repositories to query when resolving version ranges.
+ * This controls whether to search in release repositories, snapshot repositories, or both.
+ *
+ * @since 4.0.0
+ */
+ enum Nature {
+ /**
+ * Query only release repositories to discover versions.
+ */
+ RELEASE,
+ /**
+ * Query only snapshot repositories to discover versions.
+ */
+ SNAPSHOT,
+ /**
+ * Query both release and snapshot repositories to discover versions.
+ * This is the default behavior.
+ */
+ RELEASE_OR_SNAPSHOT
+ }
+
+ /**
+ * Gets the artifact coordinates whose version range should be resolved.
+ * The coordinates may contain a version range (e.g., "[1.0,2.0)") or a single version.
+ *
+ * @return the artifact coordinates, never {@code null}
+ */
@Nonnull
ArtifactCoordinates getArtifactCoordinates();
- @Nullable
- List getRepositories();
+ /**
+ * Gets the nature of repositories to query when resolving the version range.
+ * This determines whether to search in release repositories, snapshot repositories, or both.
+ *
+ * @return the repository nature, never {@code null}
+ */
+ @Nonnull
+ Nature getNature();
+ /**
+ * Creates a version range resolver request using the session's repositories.
+ *
+ * @param session the session to use, must not be {@code null}
+ * @param artifactCoordinates the artifact coordinates whose version range should be resolved, must not be {@code null}
+ * @return the version range resolver request, never {@code null}
+ */
@Nonnull
static VersionRangeResolverRequest build(
@Nonnull Session session, @Nonnull ArtifactCoordinates artifactCoordinates) {
- return build(session, artifactCoordinates, null);
+ return build(session, artifactCoordinates, null, null);
}
+ /**
+ * Creates a version range resolver request.
+ *
+ * @param session the session to use, must not be {@code null}
+ * @param artifactCoordinates the artifact coordinates whose version range should be resolved, must not be {@code null}
+ * @param repositories the repositories to use, or {@code null} to use the session's repositories
+ * @return the version range resolver request, never {@code null}
+ */
@Nonnull
static VersionRangeResolverRequest build(
@Nonnull Session session,
@Nonnull ArtifactCoordinates artifactCoordinates,
@Nullable List repositories) {
+ return build(session, artifactCoordinates, repositories, null);
+ }
+
+ /**
+ * Creates a version range resolver request.
+ *
+ * @param session the session to use, must not be {@code null}
+ * @param artifactCoordinates the artifact coordinates whose version range should be resolved, must not be {@code null}
+ * @param repositories the repositories to use, or {@code null} to use the session's repositories
+ * @param nature the nature of repositories to query when resolving the version range, or {@code null} to use the default
+ * @return the version range resolver request, never {@code null}
+ */
+ @Nonnull
+ static VersionRangeResolverRequest build(
+ @Nonnull Session session,
+ @Nonnull ArtifactCoordinates artifactCoordinates,
+ @Nullable List repositories,
+ @Nullable Nature nature) {
return builder()
.session(requireNonNull(session, "session cannot be null"))
.artifactCoordinates(requireNonNull(artifactCoordinates, "artifactCoordinates cannot be null"))
.repositories(repositories)
+ .nature(nature)
.build();
}
+ /**
+ * Creates a new builder for version range resolver requests.
+ *
+ * @return a new builder, never {@code null}
+ */
@Nonnull
static VersionResolverRequestBuilder builder() {
return new VersionResolverRequestBuilder();
}
+ /**
+ * Builder for {@link VersionRangeResolverRequest}.
+ */
@NotThreadSafe
class VersionResolverRequestBuilder {
Session session;
RequestTrace trace;
ArtifactCoordinates artifactCoordinates;
List repositories;
+ Nature nature = Nature.RELEASE_OR_SNAPSHOT;
+ /**
+ * Sets the session to use for the request.
+ *
+ * @param session the session, must not be {@code null}
+ * @return this builder, never {@code null}
+ */
public VersionResolverRequestBuilder session(Session session) {
this.session = session;
return this;
}
+ /**
+ * Sets the request trace for debugging and diagnostics.
+ *
+ * @param trace the request trace, may be {@code null}
+ * @return this builder, never {@code null}
+ */
public VersionResolverRequestBuilder trace(RequestTrace trace) {
this.trace = trace;
return this;
}
+ /**
+ * Sets the artifact coordinates whose version range should be resolved.
+ *
+ * @param artifactCoordinates the artifact coordinates, must not be {@code null}
+ * @return this builder, never {@code null}
+ */
public VersionResolverRequestBuilder artifactCoordinates(ArtifactCoordinates artifactCoordinates) {
this.artifactCoordinates = artifactCoordinates;
return this;
}
+ /**
+ * Sets the nature of repositories to query when resolving the version range.
+ * If {@code null} is provided, defaults to {@link Nature#RELEASE_OR_SNAPSHOT}.
+ *
+ * @param nature the repository nature, or {@code null} to use the default
+ * @return this builder, never {@code null}
+ */
+ public VersionResolverRequestBuilder nature(Nature nature) {
+ this.nature = Objects.requireNonNullElse(nature, Nature.RELEASE_OR_SNAPSHOT);
+ return this;
+ }
+
+ /**
+ * Sets the repositories to use for resolving the version range.
+ *
+ * @param repositories the repositories, or {@code null} to use the session's repositories
+ * @return this builder, never {@code null}
+ */
public VersionResolverRequestBuilder repositories(List repositories) {
this.repositories = repositories;
return this;
}
+ /**
+ * Builds the version range resolver request.
+ *
+ * @return the version range resolver request, never {@code null}
+ */
public VersionRangeResolverRequest build() {
- return new DefaultVersionResolverRequest(session, trace, artifactCoordinates, repositories);
+ return new DefaultVersionResolverRequest(session, trace, artifactCoordinates, repositories, nature);
}
private static class DefaultVersionResolverRequest extends BaseRequest
implements VersionRangeResolverRequest {
private final ArtifactCoordinates artifactCoordinates;
private final List repositories;
+ private final Nature nature;
@SuppressWarnings("checkstyle:ParameterNumber")
DefaultVersionResolverRequest(
@Nonnull Session session,
@Nullable RequestTrace trace,
@Nonnull ArtifactCoordinates artifactCoordinates,
- @Nullable List repositories) {
+ @Nullable List repositories,
+ @Nonnull Nature nature) {
super(session, trace);
- this.artifactCoordinates = artifactCoordinates;
- this.repositories = repositories;
+ this.artifactCoordinates = requireNonNull(artifactCoordinates);
+ this.repositories = validate(repositories);
+ this.nature = requireNonNull(nature);
}
@Nonnull
@@ -126,23 +250,31 @@ public List getRepositories() {
return repositories;
}
+ @Nonnull
+ @Override
+ public Nature getNature() {
+ return nature;
+ }
+
@Override
public boolean equals(Object o) {
return o instanceof DefaultVersionResolverRequest that
&& Objects.equals(artifactCoordinates, that.artifactCoordinates)
- && Objects.equals(repositories, that.repositories);
+ && Objects.equals(repositories, that.repositories)
+ && nature == that.nature;
}
@Override
public int hashCode() {
- return Objects.hash(artifactCoordinates, repositories);
+ return Objects.hash(artifactCoordinates, repositories, nature);
}
@Override
public String toString() {
return "VersionResolverRequest[" + "artifactCoordinates="
+ artifactCoordinates + ", repositories="
- + repositories + ']';
+ + repositories + ", nature="
+ + nature + ']';
}
}
}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionResolverRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionResolverRequest.java
index c8dee58a8fcf..b510dcc2de17 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionResolverRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/VersionResolverRequest.java
@@ -36,14 +36,11 @@
* @since 4.0.0
*/
@Experimental
-public interface VersionResolverRequest extends Request {
+public interface VersionResolverRequest extends RepositoryAwareRequest {
@Nonnull
ArtifactCoordinates getArtifactCoordinates();
- @Nullable
- List getRepositories();
-
@Nonnull
static VersionResolverRequest build(@Nonnull Session session, @Nonnull ArtifactCoordinates artifactCoordinates) {
return builder()
@@ -113,7 +110,7 @@ private static class DefaultVersionResolverRequest extends BaseRequest
@Nullable List repositories) {
super(session, trace);
this.artifactCoordinates = artifactCoordinates;
- this.repositories = repositories;
+ this.repositories = validate(repositories);
}
@Nonnull
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/XmlReaderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/XmlReaderRequest.java
index d6fc50e911ad..41733eb08bf3 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/XmlReaderRequest.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/XmlReaderRequest.java
@@ -208,7 +208,7 @@ public Path getRootDirectory() {
@Override
public URL getURL() {
- return null;
+ return url;
}
@Override
diff --git a/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
new file mode 100644
index 000000000000..a316550aee89
--- /dev/null
+++ b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.Collection;
+import java.util.Optional;
+
+import org.apache.maven.api.model.Build;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SourceRootTest implements SourceRoot {
+ private ProjectScope scope;
+
+ private Language language;
+
+ private String moduleName;
+
+ @Override
+ public ProjectScope scope() {
+ return (scope != null) ? scope : SourceRoot.super.scope();
+ }
+
+ @Override
+ public Language language() {
+ return (language != null) ? language : SourceRoot.super.language();
+ }
+
+ @Override
+ public Optional module() {
+ return Optional.ofNullable(moduleName);
+ }
+
+ @Override
+ public PathMatcher matcher(Collection defaultIncludes, boolean useDefaultExcludes) {
+ return null; // Not used for this test.
+ }
+
+ @Test
+ void testDirectory() {
+ assertEquals(Path.of("src", "main", "java"), directory());
+
+ scope = ProjectScope.TEST;
+ assertEquals(Path.of("src", "test", "java"), directory());
+
+ moduleName = "org.foo";
+ assertEquals(Path.of("src", "org.foo", "test", "java"), directory());
+ }
+
+ @Test
+ void testTargetPath() {
+ Build build = mock(Build.class);
+ when(build.getDirectory()).thenReturn("target");
+ when(build.getOutputDirectory()).thenReturn("target/classes");
+ when(build.getTestOutputDirectory()).thenReturn("target/test-classes");
+
+ Project project = mock(Project.class);
+ when(project.getBuild()).thenReturn(build);
+ when(project.getBasedir()).thenReturn(Path.of("myproject"));
+ when(project.getOutputDirectory(any(ProjectScope.class))).thenCallRealMethod();
+
+ assertEquals(Path.of("myproject", "target", "classes"), targetPath(project));
+
+ scope = ProjectScope.TEST;
+ assertEquals(Path.of("myproject", "target", "test-classes"), targetPath(project));
+ }
+}
diff --git a/api/maven-api-di/pom.xml b/api/maven-api-di/pom.xml
index 984a496d0d90..c2cc62e36f48 100644
--- a/api/maven-api-di/pom.xml
+++ b/api/maven-api-di/pom.xml
@@ -23,7 +23,7 @@
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-di
diff --git a/api/maven-api-di/src/main/java/org/apache/maven/api/di/package-info.java b/api/maven-api-di/src/main/java/org/apache/maven/api/di/package-info.java
index 8cda82936a36..8ba28e948811 100644
--- a/api/maven-api-di/src/main/java/org/apache/maven/api/di/package-info.java
+++ b/api/maven-api-di/src/main/java/org/apache/maven/api/di/package-info.java
@@ -1,3 +1,22 @@
+/*
+ * 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.
+ */
+
/**
* A dependency injection framework for Maven that provides JSR-330 style annotations
* for managing object lifecycle and dependencies within Maven's build process.
diff --git a/api/maven-api-metadata/pom.xml b/api/maven-api-metadata/pom.xml
index 0db48a3692c3..174816e59ecf 100644
--- a/api/maven-api-metadata/pom.xml
+++ b/api/maven-api-metadata/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-metadata
diff --git a/api/maven-api-model/pom.xml b/api/maven-api-model/pom.xml
index 75534c1c597e..37e0555cbe46 100644
--- a/api/maven-api-model/pom.xml
+++ b/api/maven-api-model/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-model
diff --git a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocation.java b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocation.java
index 2e65dea793fd..9ecfb400ff9c 100644
--- a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocation.java
+++ b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocation.java
@@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
/**
* Represents the location of an element within a model source file.
@@ -30,16 +31,28 @@
* This class tracks the line and column numbers of elements in source files like POM files.
* It's used for error reporting and debugging to help identify where specific model elements
* are defined in the source files.
+ *
+ * Note: Starting with Maven 4.0.0, it is recommended to use the static factory methods
+ * {@code of(...)} instead of constructors. The constructors are deprecated and will be
+ * removed in a future version.
*
* @since 4.0.0
*/
-public class InputLocation implements Serializable, InputLocationTracker {
+public final class InputLocation implements Serializable, InputLocationTracker {
private final int lineNumber;
private final int columnNumber;
private final InputSource source;
private final Map locations;
private final InputLocation importedFrom;
+ /**
+ * Creates an InputLocation with only a source, no line/column information.
+ * The line and column numbers will be set to -1 (unknown).
+ *
+ * @param source the input source where this location originates from
+ * @deprecated since 4.0.0-rc-6, use {@link #of(InputSource)} instead
+ */
+ @Deprecated
public InputLocation(InputSource source) {
this.lineNumber = -1;
this.columnNumber = -1;
@@ -48,14 +61,41 @@ public InputLocation(InputSource source) {
this.importedFrom = null;
}
+ /**
+ * Creates an InputLocation with line and column numbers but no source.
+ *
+ * @param lineNumber the line number in the source file (1-based)
+ * @param columnNumber the column number in the source file (1-based)
+ * @deprecated since 4.0.0-rc-6, use {@link #of(int, int)} instead
+ */
+ @Deprecated
public InputLocation(int lineNumber, int columnNumber) {
this(lineNumber, columnNumber, null, null);
}
+ /**
+ * Creates an InputLocation with line number, column number, and source.
+ *
+ * @param lineNumber the line number in the source file (1-based)
+ * @param columnNumber the column number in the source file (1-based)
+ * @param source the input source where this location originates from
+ * @deprecated since 4.0.0-rc-6, use {@link #of(int, int, InputSource)} instead
+ */
+ @Deprecated
public InputLocation(int lineNumber, int columnNumber, InputSource source) {
this(lineNumber, columnNumber, source, null);
}
+ /**
+ * Creates an InputLocation with line number, column number, source, and a self-location key.
+ *
+ * @param lineNumber the line number in the source file (1-based)
+ * @param columnNumber the column number in the source file (1-based)
+ * @param source the input source where this location originates from
+ * @param selfLocationKey the key to map this location to itself in the locations map
+ * @deprecated since 4.0.0-rc-6, use {@link #of(int, int, InputSource, Object)} instead
+ */
+ @Deprecated
public InputLocation(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
@@ -65,6 +105,16 @@ public InputLocation(int lineNumber, int columnNumber, InputSource source, Objec
this.importedFrom = null;
}
+ /**
+ * Creates an InputLocation with line number, column number, source, and a complete locations map.
+ *
+ * @param lineNumber the line number in the source file (1-based)
+ * @param columnNumber the column number in the source file (1-based)
+ * @param source the input source where this location originates from
+ * @param locations a map of keys to InputLocation instances for nested elements
+ * @deprecated since 4.0.0-rc-6, use {@link #of(int, int, InputSource, Map)} instead
+ */
+ @Deprecated
public InputLocation(int lineNumber, int columnNumber, InputSource source, Map locations) {
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
@@ -73,6 +123,13 @@ public InputLocation(int lineNumber, int columnNumber, InputSource source, Map locations) {
+ return new InputLocation(lineNumber, columnNumber, source, locations);
+ }
+
+ /**
+ * Gets the one-based line number where this element is located in the source file.
+ *
+ * @return the line number, or -1 if unknown
+ */
public int getLineNumber() {
return lineNumber;
}
+ /**
+ * Gets the one-based column number where this element is located in the source file.
+ *
+ * @return the column number, or -1 if unknown
+ */
public int getColumnNumber() {
return columnNumber;
}
+ /**
+ * Gets the input source where this location originates from.
+ *
+ * @return the input source, or null if unknown
+ */
public InputSource getSource() {
return source;
}
+ /**
+ * Gets the InputLocation for a specific nested element key.
+ *
+ * @param key the key to look up
+ * @return the InputLocation for the specified key, or null if not found
+ */
@Override
public InputLocation getLocation(Object key) {
+ Objects.requireNonNull(key, "key");
return locations != null ? locations.get(key) : null;
}
+ /**
+ * Gets the map of nested element locations within this location.
+ *
+ * @return an immutable map of keys to InputLocation instances for nested elements
+ */
public Map getLocations() {
return locations;
}
@@ -142,7 +297,7 @@ public static InputLocation merge(InputLocation target, InputLocation source, bo
locations.putAll(sourceDominant ? sourceLocations : targetLocations);
}
- return new InputLocation(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
+ return InputLocation.of(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
} // -- InputLocation merge( InputLocation, InputLocation, boolean )
/**
@@ -181,7 +336,7 @@ public static InputLocation merge(InputLocation target, InputLocation source, Co
}
}
- return new InputLocation(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
+ return InputLocation.of(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
} // -- InputLocation merge( InputLocation, InputLocation, java.util.Collection )
/**
diff --git a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocationTracker.java b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocationTracker.java
index 8b2958a35cc6..65d43007d007 100644
--- a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocationTracker.java
+++ b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocationTracker.java
@@ -18,7 +18,23 @@
*/
package org.apache.maven.api.model;
+/**
+ * Tracks input source locations for model fields.
+ *
+ * Implementations provide a mapping from keys (typically field names or indices) to
+ * {@link InputLocation} instances to support precise error reporting and diagnostics.
+ * Keys must be non-null.
+ *
+ * @since 4.0.0
+ */
public interface InputLocationTracker {
+ /**
+ * Gets the location of the specified field in the input source.
+ *
+ * @param field the key of the field, must not be {@code null}
+ * @return the location of the field in the input source or {@code null} if unknown
+ * @throws NullPointerException if {@code field} is {@code null}
+ */
InputLocation getLocation(Object field);
/**
diff --git a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputSource.java b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputSource.java
index f4d5e7fc67bf..09043542ad83 100644
--- a/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputSource.java
+++ b/api/maven-api-model/src/main/java/org/apache/maven/api/model/InputSource.java
@@ -34,7 +34,7 @@
*
* @since 4.0.0
*/
-public class InputSource implements Serializable {
+public final class InputSource implements Serializable {
private final String modelId;
private final String location;
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo
index 48df570aec9f..a5f907635c7f 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -54,8 +54,8 @@
This is a reference for the Maven project descriptor used in Maven.
An XSD is available at:
]]>
@@ -92,7 +92,8 @@
modelVersion
4.0.0+
true
- Declares to which version of project descriptor this POM conforms.
+ Declares to which version of project descriptor this POM conforms to:
+ {@code 4.0.0} for Maven 3 or {@code 4.1.0} for Maven 4.
String
@@ -1353,11 +1354,12 @@
private volatile String managementKey;
/**
- * @return the management key as {@code groupId:artifactId:type}
+ * @return the management key as {@code groupId:artifactId:type[:classifier]}
*/
public String getManagementKey() {
if (managementKey == null) {
- managementKey = (getGroupId() + ":" + getArtifactId() + ":" + getType() + (getClassifier() != null ? ":" + getClassifier() : "")).intern();
+ managementKey = (getGroupId() + ":" + getArtifactId() + ":" + getType()
+ + (getClassifier() != null && !getClassifier().isEmpty() ? ":" + getClassifier() : "")).intern();
}
return managementKey;
}
@@ -2128,8 +2130,16 @@
+ {@code ${project.build.outputDirectory}} (typically {@code target/classes}) if {@code scope} is "main",
+ {@code ${project.build.testOutputDirectory}} (typically {@code target/test-classes}) if {@code scope} is "test",
+ {@code ${project.build.directory}} (typically {@code target}) otherwise.
+
+
+ If this property is specified but is a relative path,
+ then the path is resolved against the above-cited default value.
When a target path is explicitly specified, the values of the {@code module} and {@code targetVersion}
elements are not used for inferring the path (they are still used as compiler options however).
@@ -2363,12 +2373,12 @@
Repository
4.0.0+
Deployment repository contains the information needed for deploying to the remote
- repository, which adds uniqueVersion property to usual repositories for download.
+ repository, which adds {@code uniqueVersion} property to usual repository information for download.
uniqueVersion
Whether to assign snapshots a unique version comprised of the timestamp and
- build number, or to use the same version each time
+ build number, or to use the same version each time, when deploying to repository
boolean
true
4.0.0+
@@ -2379,7 +2389,7 @@
RepositoryPolicy
4.0.0+
- Download policy.
+ Repository download policy.
enabled
diff --git a/api/maven-api-model/src/site/apt/index.apt b/api/maven-api-model/src/site/apt/index.apt
index e64b4fb211a9..5720df24da9f 100644
--- a/api/maven-api-model/src/site/apt/index.apt
+++ b/api/maven-api-model/src/site/apt/index.apt
@@ -33,4 +33,4 @@ Maven 4 API - Immutable Maven Model
* {{{./apidocs/index.html}Java sources}} with <<>> inner classes for immutable instances creation.
- See also corresponding {{{../../maven-model/index.html}Maven classical POM model documentation}}.
+ See also corresponding {{{../../compat/maven-model/index.html}Maven classical POM model documentation}}.
diff --git a/api/maven-api-plugin/pom.xml b/api/maven-api-plugin/pom.xml
index 37ed030feaf2..ecc36001df35 100644
--- a/api/maven-api-plugin/pom.xml
+++ b/api/maven-api-plugin/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-plugin
diff --git a/api/maven-api-plugin/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java b/api/maven-api-plugin/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java
deleted file mode 100644
index 28a7936fed4c..000000000000
--- a/api/maven-api-plugin/src/main/java/org/apache/maven/api/plugin/annotations/package-info.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Provides annotations for Maven plugin development, including mojo configuration,
- * parameter definitions, and lifecycle bindings. These annotations are used to
- * generate plugin descriptors and configure plugin behavior.
- *
- * @since 4.0.0
- */
-package org.apache.maven.api.plugin.annotations;
diff --git a/api/maven-api-settings/pom.xml b/api/maven-api-settings/pom.xml
index df4242455509..426a03af582f 100644
--- a/api/maven-api-settings/pom.xml
+++ b/api/maven-api-settings/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-settings
diff --git a/api/maven-api-settings/src/site/apt/index.apt b/api/maven-api-settings/src/site/apt/index.apt
index ca71c0d7f736..b650c8e1c9ce 100644
--- a/api/maven-api-settings/src/site/apt/index.apt
+++ b/api/maven-api-settings/src/site/apt/index.apt
@@ -31,5 +31,5 @@ Maven 4 API - Immutable Settings Model
* {{{./apidocs/index.html}Java sources}} with <<>> inner classes for immutable instances creation.
- See also corresponding {{{../../maven-settings/index.html}Maven classical settings model documentation}}.
+ See also corresponding {{{../../compat/maven-settings/index.html}Maven classical settings model documentation}}.
\ No newline at end of file
diff --git a/api/maven-api-spi/pom.xml b/api/maven-api-spi/pom.xml
index 28d3364a3bcb..9e11c42fe587 100644
--- a/api/maven-api-spi/pom.xml
+++ b/api/maven-api-spi/pom.xml
@@ -23,7 +23,7 @@
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-spi
diff --git a/api/maven-api-toolchain/pom.xml b/api/maven-api-toolchain/pom.xml
index 568bd10f7261..108275549d1c 100644
--- a/api/maven-api-toolchain/pom.xml
+++ b/api/maven-api-toolchain/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-toolchain
diff --git a/api/maven-api-toolchain/src/main/java/org/apache/maven/api/toolchain/package-info.java b/api/maven-api-toolchain/src/main/java/org/apache/maven/api/toolchain/package-info.java
index 04661e78d20e..1609356422af 100644
--- a/api/maven-api-toolchain/src/main/java/org/apache/maven/api/toolchain/package-info.java
+++ b/api/maven-api-toolchain/src/main/java/org/apache/maven/api/toolchain/package-info.java
@@ -1,4 +1,22 @@
-// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+
/**
* Provides classes for managing Maven toolchains, which allow projects to use specific
* tool installations (like JDKs, compilers, or other build tools) across different
diff --git a/api/maven-api-toolchain/src/site/apt/index.apt b/api/maven-api-toolchain/src/site/apt/index.apt
index 689b0443307e..f1a76e9c98bd 100644
--- a/api/maven-api-toolchain/src/site/apt/index.apt
+++ b/api/maven-api-toolchain/src/site/apt/index.apt
@@ -31,5 +31,5 @@ Maven 4 API - Immutable Toolchains Model
* {{{./apidocs/index.html}Java sources}} with <<>> inner classes for immutable instances creation.
- See also corresponding {{{../../maven-toolchain-model/index.html}Maven classical toolchains model documentation}}.
+ See also corresponding {{{../../compat/maven-toolchain-model/index.html}Maven classical toolchains model documentation}}.
\ No newline at end of file
diff --git a/api/maven-api-xml/pom.xml b/api/maven-api-xml/pom.xml
index 323e8f1a9c9e..5ce4808d448e 100644
--- a/api/maven-api-xml/pom.xml
+++ b/api/maven-api-xml/pom.xml
@@ -23,7 +23,7 @@
org.apache.maven
maven-api
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api-xml
diff --git a/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java b/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
index 54a6c3443bfc..7e9004395a0b 100644
--- a/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
+++ b/api/maven-api-xml/src/main/java/org/apache/maven/api/xml/XmlNode.java
@@ -155,6 +155,24 @@ public interface XmlNode {
@Nullable
String attribute(@Nonnull String name);
+ /**
+ * Returns the namespace context for this node — a map of namespace prefix to URI
+ * for all namespace bindings in scope, including those declared on this element
+ * and those inherited from ancestor elements.
+ *
+ * This is used by the write side to properly resolve prefixed attributes.
+ * For example, if an attribute {@code mvn:combine.children} exists on a child element
+ * but {@code xmlns:mvn} was declared on the root element, this map will contain
+ * the {@code mvn → http://maven.apache.org/POM/4.0.0} binding.
+ *
+ * @return map of namespace prefix to URI, never {@code null}
+ * @since 4.1.0
+ */
+ @Nonnull
+ default Map namespaces() {
+ return Map.of();
+ }
+
/**
* Returns an immutable list of all child nodes.
*
@@ -354,6 +372,7 @@ class Builder {
private String namespaceUri;
private String prefix;
private Map attributes;
+ private Map namespaces;
private List children;
private Object inputLocation;
@@ -417,6 +436,21 @@ public Builder attributes(Map attributes) {
return this;
}
+ /**
+ * Sets the namespace context for this node.
+ *
+ * This map contains all namespace prefix to URI bindings in scope,
+ * including inherited ones from ancestor elements.
+ *
+ * @param namespaces the map of namespace prefix to URI
+ * @return this builder instance
+ * @since 4.1.0
+ */
+ public Builder namespaces(Map namespaces) {
+ this.namespaces = namespaces;
+ return this;
+ }
+
/**
* Sets the child nodes of the XML node.
*
@@ -450,7 +484,7 @@ public Builder inputLocation(Object inputLocation) {
* @throws NullPointerException if name has not been set
*/
public XmlNode build() {
- return new Impl(prefix, namespaceUri, name, value, attributes, children, inputLocation);
+ return new Impl(prefix, namespaceUri, name, value, attributes, namespaces, children, inputLocation);
}
private record Impl(
@@ -459,6 +493,7 @@ private record Impl(
@Nonnull String name,
String value,
@Nonnull Map attributes,
+ @Nonnull Map namespaces,
@Nonnull List children,
Object inputLocation)
implements XmlNode, Serializable {
@@ -469,6 +504,7 @@ private record Impl(
namespaceUri = namespaceUri == null ? "" : namespaceUri;
name = Objects.requireNonNull(name);
attributes = ImmutableCollections.copy(attributes);
+ namespaces = ImmutableCollections.copy(namespaces);
children = ImmutableCollections.copy(children);
}
@@ -493,17 +529,12 @@ public XmlNode child(String name) {
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Impl that = (Impl) o;
- return Objects.equals(this.name, that.name)
- && Objects.equals(this.value, that.value)
- && Objects.equals(this.attributes, that.attributes)
- && Objects.equals(this.children, that.children);
+ return this == o
+ || o instanceof XmlNode that
+ && Objects.equals(this.name, that.name())
+ && Objects.equals(this.value, that.value())
+ && Objects.equals(this.attributes, that.attributes())
+ && Objects.equals(this.children, that.children());
}
@Override
diff --git a/api/pom.xml b/api/pom.xml
index dc33c533cf81..ba5c47f3a199 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -23,7 +23,7 @@
org.apache.maven
maven
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-api
diff --git a/compat/maven-artifact/pom.xml b/compat/maven-artifact/pom.xml
index 4457c79f8c30..381253f437a5 100644
--- a/compat/maven-artifact/pom.xml
+++ b/compat/maven-artifact/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-artifact
diff --git a/compat/maven-builder-support/pom.xml b/compat/maven-builder-support/pom.xml
index 54e91e3c7978..2297ad4d7205 100644
--- a/compat/maven-builder-support/pom.xml
+++ b/compat/maven-builder-support/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-builder-support
diff --git a/compat/maven-compat/pom.xml b/compat/maven-compat/pom.xml
index d33bcaeba11f..17a9254bab23 100644
--- a/compat/maven-compat/pom.xml
+++ b/compat/maven-compat/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-compat
@@ -115,6 +115,10 @@ under the License.
org.apache.maven.resolver
maven-resolver-util
+
+ org.apache.maven.resolver
+ maven-resolver-impl
+
org.codehaus.plexus
@@ -150,23 +154,33 @@ under the License.
javax.inject
javax.inject
- provided
+
+ compile
+
+
+ aopalliance
+ aopalliance
+ 1.0
+
+
org.eclipse.sisu
org.eclipse.sisu.inject
- provided
+
+ compile
org.codehaus.plexus
plexus-component-annotations
- 2.1.0
+ 2.2.0
org.eclipse.sisu
org.eclipse.sisu.plexus
- provided
+
+ compile
@@ -183,7 +197,8 @@ under the License.
com.google.inject
guice
classes
- test
+
+ compile
org.codehaus.plexus
@@ -205,11 +220,6 @@ under the License.
maven-resolver-spi
test
-
- org.apache.maven.resolver
- maven-resolver-impl
- test
-
org.apache.maven.resolver
maven-resolver-connector-basic
diff --git a/compat/maven-compat/src/main/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManager.java b/compat/maven-compat/src/main/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManager.java
index d43758c99773..3db0d7bcd077 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManager.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManager.java
@@ -18,17 +18,13 @@
*/
package org.apache.maven.repository.legacy;
+import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
import java.util.Date;
+import java.util.HashMap;
import java.util.Properties;
import org.apache.maven.artifact.Artifact;
@@ -39,6 +35,7 @@
import org.apache.maven.repository.Proxy;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.logging.Logger;
+import org.eclipse.aether.internal.impl.TrackingFileManager;
/**
* DefaultUpdateCheckManager
@@ -47,13 +44,21 @@
@Singleton
@Deprecated
public class DefaultUpdateCheckManager extends AbstractLogEnabled implements UpdateCheckManager {
+ private final TrackingFileManager trackingFileManager;
private static final String ERROR_KEY_SUFFIX = ".error";
- public DefaultUpdateCheckManager() {}
+ @Inject
+ public DefaultUpdateCheckManager(TrackingFileManager trackingFileManager) {
+ this.trackingFileManager = trackingFileManager;
+ }
- public DefaultUpdateCheckManager(Logger logger) {
+ /**
+ * For testing purposes.
+ */
+ public DefaultUpdateCheckManager(Logger logger, TrackingFileManager trackingFileManager) {
enableLogging(logger);
+ this.trackingFileManager = trackingFileManager;
}
public static final String LAST_UPDATE_TAG = ".lastUpdated";
@@ -156,7 +161,7 @@ public void touch(Artifact artifact, ArtifactRepository repository, String error
File touchfile = getTouchfile(artifact);
if (file.exists()) {
- touchfile.delete();
+ trackingFileManager.delete(touchfile);
} else {
writeLastUpdated(touchfile, getRepositoryKey(repository), error);
}
@@ -201,70 +206,10 @@ String getRepositoryKey(ArtifactRepository repository) {
}
private void writeLastUpdated(File touchfile, String key, String error) {
- synchronized (touchfile.getAbsolutePath().intern()) {
- if (!touchfile.getParentFile().exists()
- && !touchfile.getParentFile().mkdirs()) {
- getLogger()
- .debug("Failed to create directory: " + touchfile.getParent()
- + " for tracking artifact metadata resolution.");
- return;
- }
-
- FileChannel channel = null;
- FileLock lock = null;
- try {
- Properties props = new Properties();
-
- channel = new RandomAccessFile(touchfile, "rw").getChannel();
- lock = channel.lock();
-
- if (touchfile.canRead()) {
- getLogger().debug("Reading resolution-state from: " + touchfile);
- props.load(Channels.newInputStream(channel));
- }
-
- props.setProperty(key, Long.toString(System.currentTimeMillis()));
-
- if (error != null) {
- props.setProperty(key + ERROR_KEY_SUFFIX, error);
- } else {
- props.remove(key + ERROR_KEY_SUFFIX);
- }
-
- getLogger().debug("Writing resolution-state to: " + touchfile);
- channel.truncate(0);
- props.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
-
- lock.release();
- lock = null;
-
- channel.close();
- channel = null;
- } catch (IOException e) {
- getLogger()
- .debug(
- "Failed to record lastUpdated information for resolution.\nFile: " + touchfile
- + "; key: " + key,
- e);
- } finally {
- if (lock != null) {
- try {
- lock.release();
- } catch (IOException e) {
- getLogger()
- .debug("Error releasing exclusive lock for resolution tracking file: " + touchfile, e);
- }
- }
-
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- getLogger().debug("Error closing FileChannel for resolution tracking file: " + touchfile, e);
- }
- }
- }
- }
+ HashMap update = new HashMap<>();
+ update.put(key, Long.toString(System.currentTimeMillis()));
+ update.put(key + ERROR_KEY_SUFFIX, error); // error==null => remove mapping
+ trackingFileManager.update(touchfile, update);
}
Date readLastUpdated(File touchfile, String key) {
@@ -293,30 +238,7 @@ private String getError(File touchFile, String key) {
}
private Properties read(File touchfile) {
- if (!touchfile.canRead()) {
- getLogger().debug("Skipped unreadable resolution tracking file: " + touchfile);
- return null;
- }
-
- synchronized (touchfile.getAbsolutePath().intern()) {
- try {
- Properties props = new Properties();
-
- try (FileInputStream in = new FileInputStream(touchfile)) {
- try (FileLock lock = in.getChannel().lock(0, Long.MAX_VALUE, true)) {
- getLogger().debug("Reading resolution-state from: " + touchfile);
- props.load(in);
-
- return props;
- }
- }
-
- } catch (IOException e) {
- getLogger().debug("Failed to read resolution tracking file: " + touchfile, e);
-
- return null;
- }
- }
+ return trackingFileManager.read(touchfile);
}
File getTouchfile(Artifact artifact) {
diff --git a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/ToolchainManagerFactory.java b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/ToolchainManagerFactory.java
index 422334a7d0be..5123ff11d081 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/ToolchainManagerFactory.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/ToolchainManagerFactory.java
@@ -72,21 +72,64 @@ DefaultToolchainManagerV4 v4Manager() {
return new DefaultToolchainManagerV4();
}
- private org.apache.maven.impl.DefaultToolchainManager getDelegate() {
- return getToolchainManager(lookup, logger);
+ @Provides
+ @Typed(ToolchainFactory.class)
+ @Named("jdk")
+ ToolchainFactory jdkFactory() {
+ return createV3FactoryBridge("jdk");
+ }
+
+ /**
+ * Creates a v3 ToolchainFactory bridge that wraps a v4 ToolchainFactory.
+ */
+ public ToolchainFactory createV3FactoryBridge(String type) {
+ try {
+ org.apache.maven.api.services.ToolchainFactory v4Factory =
+ lookup.lookup(org.apache.maven.api.services.ToolchainFactory.class, type);
+ if (v4Factory == null) {
+ return null;
+ }
+ return createV3FactoryBridgeForV4Factory(v4Factory);
+ } catch (Exception e) {
+ // If lookup fails, no v4 factory exists for this type
+ return null;
+ }
}
- private org.apache.maven.impl.DefaultToolchainManager getToolchainManager(Lookup lookup, Logger logger) {
- return getToolchainManager(
- lookup.lookupMap(ToolchainFactory.class),
- lookup.lookupMap(org.apache.maven.api.services.ToolchainFactory.class),
- logger);
+ /**
+ * Creates a v3 ToolchainFactory bridge that wraps a specific v4 ToolchainFactory instance.
+ */
+ public ToolchainFactory createV3FactoryBridgeForV4Factory(
+ org.apache.maven.api.services.ToolchainFactory v4Factory) {
+ return new ToolchainFactory() {
+ @Override
+ public ToolchainPrivate createToolchain(ToolchainModel model) throws MisconfiguredToolchainException {
+ try {
+ org.apache.maven.api.Toolchain v4Toolchain = v4Factory.createToolchain(model.getDelegate());
+ return getToolchainV3(v4Toolchain);
+ } catch (ToolchainFactoryException e) {
+ throw new MisconfiguredToolchainException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public ToolchainPrivate createDefaultToolchain() {
+ try {
+ return v4Factory
+ .createDefaultToolchain()
+ .map(ToolchainManagerFactory.this::getToolchainV3)
+ .orElse(null);
+ } catch (ToolchainFactoryException e) {
+ return null;
+ }
+ }
+ };
}
- private org.apache.maven.impl.DefaultToolchainManager getToolchainManager(
- Map v3Factories,
- Map v4Factories,
- Logger logger) {
+ private org.apache.maven.impl.DefaultToolchainManager getDelegate() {
+ Map v3Factories = lookup.lookupMap(ToolchainFactory.class);
+ Map v4Factories =
+ lookup.lookupMap(org.apache.maven.api.services.ToolchainFactory.class);
Map allFactories = new HashMap<>();
for (Map.Entry entry : v3Factories.entrySet()) {
ToolchainFactory v3Factory = entry.getValue();
diff --git a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchain.java b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchain.java
index 6080f00f4d79..300f80f75f2e 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchain.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchain.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.toolchain.java;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.toolchain.Toolchain;
/**
@@ -27,4 +28,8 @@
* @deprecated Use {@link org.apache.maven.api.JavaToolchain} instead.
*/
@Deprecated(since = "4.0.0")
-public interface JavaToolchain extends Toolchain {}
+public interface JavaToolchain extends Toolchain {
+ String getJavaHome();
+
+ ArtifactVersion getJavaVersion();
+}
diff --git a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainFactory.java b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainFactory.java
index f22884ce6494..3802b45538c4 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainFactory.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainFactory.java
@@ -21,9 +21,12 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.toolchain.MisconfiguredToolchainException;
import org.apache.maven.toolchain.RequirementMatcher;
import org.apache.maven.toolchain.RequirementMatcherFactory;
@@ -93,6 +96,14 @@ public ToolchainPrivate createToolchain(ToolchainModel model) throws Misconfigur
"Non-existing JDK home configuration at " + normal.toAbsolutePath());
}
+ ArtifactVersion javaVersion = model.getProvides().entrySet().stream()
+ .filter(entry -> "version".equals(entry.getKey()))
+ .map(Map.Entry::getValue)
+ .map(v -> new DefaultArtifactVersion((String) v))
+ .findAny()
+ .orElse(null);
+
+ jtc.setJavaVersion(javaVersion);
return jtc;
}
diff --git a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainImpl.java b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainImpl.java
index 661ffa642455..c4a837c07b2b 100644
--- a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainImpl.java
+++ b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/java/JavaToolchainImpl.java
@@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.toolchain.DefaultToolchain;
import org.apache.maven.toolchain.model.ToolchainModel;
import org.apache.maven.utils.Os;
@@ -39,6 +40,8 @@ public class JavaToolchainImpl extends DefaultToolchain implements JavaToolchain
public static final String KEY_JAVAHOME = "jdkHome"; // NOI18N
+ private ArtifactVersion javaVersion;
+
JavaToolchainImpl(ToolchainModel model, Logger logger) {
super(model, "jdk", logger);
}
@@ -47,6 +50,15 @@ public String getJavaHome() {
return javaHome;
}
+ @Override
+ public ArtifactVersion getJavaVersion() {
+ return javaVersion;
+ }
+
+ public void setJavaVersion(ArtifactVersion javaVersion) {
+ this.javaVersion = javaVersion;
+ }
+
public void setJavaHome(String javaHome) {
this.javaHome = javaHome;
}
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/artifact/AbstractArtifactComponentTestCase.java b/compat/maven-compat/src/test/java/org/apache/maven/artifact/AbstractArtifactComponentTestCase.java
index 14f394c0c1cd..206fc6245c9b 100644
--- a/compat/maven-compat/src/test/java/org/apache/maven/artifact/AbstractArtifactComponentTestCase.java
+++ b/compat/maven-compat/src/test/java/org/apache/maven/artifact/AbstractArtifactComponentTestCase.java
@@ -69,8 +69,8 @@
import org.eclipse.aether.util.graph.selector.AndDependencySelector;
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
+import org.eclipse.aether.util.graph.transformer.ConfigurableVersionSelector;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
-import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
import org.junit.jupiter.api.BeforeEach;
@@ -310,7 +310,7 @@ protected DefaultRepositorySystemSession initRepoSession() throws Exception {
DependencyTraverser depTraverser = new FatArtifactTraverser();
session.setDependencyTraverser(depTraverser);
- DependencyManager depManager = new ClassicDependencyManager(true, session.getScopeManager());
+ DependencyManager depManager = new ClassicDependencyManager(session.getScopeManager());
session.setDependencyManager(depManager);
DependencySelector depFilter = new AndDependencySelector(
@@ -323,7 +323,7 @@ protected DefaultRepositorySystemSession initRepoSession() throws Exception {
ScopeManagerImpl scopeManager = new ScopeManagerImpl(Maven4ScopeManagerConfiguration.INSTANCE);
session.setScopeManager(scopeManager);
DependencyGraphTransformer transformer = new ConflictResolver(
- new NearestVersionSelector(), new ManagedScopeSelector(scopeManager),
+ new ConfigurableVersionSelector(), new ManagedScopeSelector(scopeManager),
new SimpleOptionalitySelector(), new ManagedScopeDeriver(scopeManager));
transformer =
new ChainedDependencyGraphTransformer(transformer, new ManagedDependencyContextRefiner(scopeManager));
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/repository/LegacyRepositorySystemTest.java b/compat/maven-compat/src/test/java/org/apache/maven/repository/LegacyRepositorySystemTest.java
index 8f9709a175c0..91f30c80606f 100644
--- a/compat/maven-compat/src/test/java/org/apache/maven/repository/LegacyRepositorySystemTest.java
+++ b/compat/maven-compat/src/test/java/org/apache/maven/repository/LegacyRepositorySystemTest.java
@@ -61,6 +61,7 @@
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider;
import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager;
+import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory;
import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
@@ -151,7 +152,9 @@ void testThatASystemScopedDependencyIsNotResolvedFromRepositories() throws Excep
new SimpleLookup(List.of(
new DefaultRequestCacheFactory(),
new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
- new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())),
+ new DefaultUpdatePolicyAnalyzer(),
+ new DefaultChecksumPolicyProvider(),
+ new DefaultRepositoryKeyFunctionFactory())),
new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme())),
new DefaultArtifactCoordinatesFactory(),
new DefaultArtifactResolver(),
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManagerTest.java b/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManagerTest.java
index e06318c1db31..4566f9f80a4f 100644
--- a/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManagerTest.java
+++ b/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/DefaultUpdateCheckManagerTest.java
@@ -30,6 +30,7 @@
import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLogger;
+import org.eclipse.aether.internal.impl.LegacyTrackingFileManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -57,7 +58,8 @@ protected String component() {
public void setUp() throws Exception {
super.setUp();
- updateCheckManager = new DefaultUpdateCheckManager(new ConsoleLogger(Logger.LEVEL_DEBUG, "test"));
+ updateCheckManager = new DefaultUpdateCheckManager(
+ new ConsoleLogger(Logger.LEVEL_DEBUG, "test"), new LegacyTrackingFileManager());
}
@Test
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/resolver/conflict/AbstractConflictResolverTest.java b/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/resolver/conflict/AbstractConflictResolverTest.java
index 2bbfc00915fd..851ee6113294 100644
--- a/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/resolver/conflict/AbstractConflictResolverTest.java
+++ b/compat/maven-compat/src/test/java/org/apache/maven/repository/legacy/resolver/conflict/AbstractConflictResolverTest.java
@@ -69,11 +69,6 @@ public AbstractConflictResolverTest(String roleHint) throws Exception {
this.roleHint = roleHint;
}
- // TestCase methods -------------------------------------------------------
-
- /*
- * @see junit.framework.TestCase#setUp()
- */
@BeforeEach
public void setUp() throws Exception {
conflictResolver = (ConflictResolver) container.lookup(ConflictResolver.ROLE, roleHint);
diff --git a/compat/maven-compat/src/test/java/org/apache/maven/toolchain/java/JavaToolchainFactoryTest.java b/compat/maven-compat/src/test/java/org/apache/maven/toolchain/java/JavaToolchainFactoryTest.java
new file mode 100644
index 000000000000..8589426f1b08
--- /dev/null
+++ b/compat/maven-compat/src/test/java/org/apache/maven/toolchain/java/JavaToolchainFactoryTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.toolchain.java;
+
+import org.apache.maven.toolchain.MisconfiguredToolchainException;
+import org.apache.maven.toolchain.model.ToolchainModel;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SuppressWarnings("deprecation")
+class JavaToolchainFactoryTest {
+
+ private JavaToolchainFactory factory;
+
+ @BeforeEach
+ void setup() {
+ factory = new JavaToolchainFactory();
+ }
+
+ @Test
+ void defaultToolchainShouldReturnEmpty() {
+ assertNull(factory.createDefaultToolchain());
+ }
+
+ @Test
+ void missingJdkHomeShouldThrowException() {
+ ToolchainModel toolchainModel = new ToolchainModel();
+ MisconfiguredToolchainException exception = Assertions.assertThrows(
+ MisconfiguredToolchainException.class, () -> factory.createToolchain(toolchainModel));
+ assertEquals("Java toolchain without the jdkHome configuration element.", exception.getMessage());
+ }
+
+ @Test
+ void nonExistingJdkHomeShouldThrowException() {
+ ToolchainModel toolchainModel = new ToolchainModel();
+ Xpp3Dom jdkHome = new Xpp3Dom("jdkHome");
+ jdkHome.setValue("/not-exist/jdk/home");
+ Xpp3Dom configuration = new Xpp3Dom("configuration");
+ configuration.addChild(jdkHome);
+ toolchainModel.setConfiguration(configuration);
+
+ MisconfiguredToolchainException exception = Assertions.assertThrows(
+ MisconfiguredToolchainException.class, () -> factory.createToolchain(toolchainModel));
+ assertTrue(
+ exception.getMessage().contains("Non-existing JDK home configuration at"),
+ "Not expected exception message: '" + exception.getMessage());
+ assertTrue(
+ exception.getMessage().contains("not-exist"),
+ "Not expected exception message: '" + exception.getMessage() + "', should contain: 'not-exist'");
+ }
+
+ @Test
+ void properToolchainShouldReturnJavaToolchain() throws Exception {
+ String javaHome = System.getProperty("java.home");
+ String javaVersion = System.getProperty("java.version");
+
+ ToolchainModel toolchainModel = new ToolchainModel();
+ Xpp3Dom jdkHome = new Xpp3Dom("jdkHome");
+ jdkHome.setValue(javaHome);
+ Xpp3Dom configuration = new Xpp3Dom("configuration");
+ configuration.addChild(jdkHome);
+ toolchainModel.setConfiguration(configuration);
+
+ toolchainModel.addProvide("version", javaVersion);
+
+ JavaToolchain toolchain = (JavaToolchain) factory.createToolchain(toolchainModel);
+ assertNotNull(toolchain);
+ assertEquals(javaHome, toolchain.getJavaHome());
+ assertEquals(javaVersion, toolchain.getJavaVersion().toString());
+ }
+}
diff --git a/compat/maven-embedder/pom.xml b/compat/maven-embedder/pom.xml
index cc8fe73d74d5..7c1045dfcfb2 100644
--- a/compat/maven-embedder/pom.xml
+++ b/compat/maven-embedder/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-embedder
@@ -168,11 +168,6 @@ under the License.
logback-classic
true
-
- org.slf4j
- slf4j-simple
- true
-
org.jline
jansi-core
diff --git a/compat/maven-embedder/src/examples/simple-project/pom.xml b/compat/maven-embedder/src/examples/simple-project/pom.xml
index 0f8deb0dadba..d466107ebdaf 100644
--- a/compat/maven-embedder/src/examples/simple-project/pom.xml
+++ b/compat/maven-embedder/src/examples/simple-project/pom.xml
@@ -26,12 +26,12 @@ under the License.
simple-project
http://maven.apache.org
-
- junit
- junit
- 4.13.1
- test
-
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.14.0
+ test
+
development
diff --git a/compat/maven-embedder/src/examples/simple-project/src/test/java/org/apache/maven/embedder/AppTest.java b/compat/maven-embedder/src/examples/simple-project/src/test/java/org/apache/maven/embedder/AppTest.java
index f51498061130..a0305a23aa25 100644
--- a/compat/maven-embedder/src/examples/simple-project/src/test/java/org/apache/maven/embedder/AppTest.java
+++ b/compat/maven-embedder/src/examples/simple-project/src/test/java/org/apache/maven/embedder/AppTest.java
@@ -19,39 +19,19 @@
package org.apache.maven.embedder;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Unit test for simple App.
*/
-public class AppTest
- extends TestCase
-{
- /**
- * Create the test case
- *
- * @param testName name of the test case
- */
- public AppTest( String testName )
- {
- super( testName );
- }
+public class AppTest {
/**
- * @return the suite of tests being tested
+ * Rigourous Test :-)
*/
- public static Test suite()
- {
- return new TestSuite( AppTest.class );
- }
-
- /**
- * Rigorous Test :-)
- */
- public void testApp()
- {
- assertTrue( true );
+ @Test
+ public void testApp() {
+ assertTrue(true);
}
}
+
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index d36824bc60d6..7f6065acf8c0 100644
--- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -27,7 +27,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -140,8 +140,42 @@
@Deprecated
public class MavenCli {
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Constants#MAVEN_REPO_LOCAL} instead
+ */
+ public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
+
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Session#getRootDirectory()} instead
+ */
public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory";
+ /**
+ * @deprecated Use {@link System#getProperty(String)} with "user.home" instead
+ */
+ public static final String USER_HOME = System.getProperty("user.home");
+
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Constants#MAVEN_USER_CONF} instead
+ */
+ public static final File USER_MAVEN_CONFIGURATION_HOME = new File(USER_HOME, ".m2");
+
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Constants#MAVEN_USER_TOOLCHAINS} instead
+ */
+ public static final File DEFAULT_USER_TOOLCHAINS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "toolchains.xml");
+
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Constants#MAVEN_INSTALLATION_TOOLCHAINS} instead
+ */
+ public static final File DEFAULT_GLOBAL_TOOLCHAINS_FILE =
+ new File(System.getProperty("maven.conf"), "toolchains.xml");
+
+ /**
+ * @deprecated Use {@link org.apache.maven.api.Constants#MAVEN_STYLE_COLOR_PROPERTY} instead
+ */
+ public static final String STYLE_COLOR_PROPERTY = "style.color";
+
private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config";
private ClassWorld classWorld;
@@ -384,7 +418,7 @@ void cli(CliRequest cliRequest) throws Exception {
File configFile = new File(cliRequest.multiModuleProjectDirectory, MVN_MAVEN_CONFIG);
if (configFile.isFile()) {
- try (Stream lines = Files.lines(configFile.toPath(), Charset.defaultCharset())) {
+ try (Stream lines = Files.lines(configFile.toPath(), StandardCharsets.UTF_8)) {
String[] args = lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#"))
.toArray(String[]::new);
mavenConfig = cliManager.parse(args);
@@ -544,9 +578,10 @@ void logging(CliRequest cliRequest) throws ExitException {
switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) {
case "warn", "warning" -> LogLevelRecorder.Level.WARN;
case "error" -> LogLevelRecorder.Level.ERROR;
- default -> throw new IllegalArgumentException(
- logLevelThreshold
- + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.");
+ default ->
+ throw new IllegalArgumentException(
+ logLevelThreshold
+ + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.");
};
recorder.setMaxLevelAllowed(level);
slf4jLogger.info("Enabled to break the build on log level {}.", logLevelThreshold);
@@ -916,7 +951,7 @@ private List parseExtClasspath(CliRequest cliRequest) {
slf4jLogger.warn(
"The property '{}' has been set using a JVM system property which is deprecated. "
+ "The property can be passed as a Maven argument or in the Maven project configuration file,"
- + "usually located at ${session.rootDirectory}/.mvn/maven.properties.",
+ + "usually located at ${session.rootDirectory}/.mvn/maven-user.properties.",
Constants.MAVEN_EXT_CLASS_PATH);
}
}
@@ -964,6 +999,7 @@ private void encryption(CliRequest cliRequest) throws Exception {
private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulationException {
MavenExecutionRequest request = executionRequestPopulator.populateDefaults(cliRequest.request);
+ request.setRepositoryCache(new DefaultRepositoryCache()); // reset caches
if (cliRequest.request.getRepositoryCache() == null) {
cliRequest.request.setRepositoryCache(new DefaultRepositoryCache());
@@ -1409,7 +1445,7 @@ private String determineLocalRepositoryPath(final MavenExecutionRequest request)
slf4jLogger.warn(
"The property '{}' has been set using a JVM system property which is deprecated. "
+ "The property can be passed as a Maven argument or in the Maven project configuration file,"
- + "usually located at ${session.rootDirectory}/.mvn/maven.properties.",
+ + "usually located at ${session.rootDirectory}/.mvn/maven-user.properties.",
Constants.MAVEN_REPO_LOCAL);
}
}
@@ -1669,8 +1705,13 @@ void populateProperties(
} else {
mavenConf = fileSystem.getPath("");
}
- Path propertiesFile = mavenConf.resolve("maven.properties");
- MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
+ Path systemPropertiesFile = mavenConf.resolve("maven-system.properties");
+ MavenPropertiesLoader.loadProperties(systemProperties, systemPropertiesFile, callback, false);
+ Path userPropertiesFile = mavenConf.resolve("maven-user.properties");
+ MavenPropertiesLoader.loadProperties(userProperties, userPropertiesFile, callback, false);
+
+ // Warn about deprecated maven.properties files
+ warnAboutDeprecatedPropertiesFiles(systemProperties);
// ----------------------------------------------------------------------
// I'm leaving the setting of system properties here as not to break
@@ -1717,11 +1758,7 @@ private static String stripLeadingAndTrailingQuotes(String str) {
}
private static Path getCanonicalPath(Path path) {
- try {
- return path.toRealPath();
- } catch (IOException e) {
- return getCanonicalPath(path.getParent()).resolve(path.getFileName());
- }
+ return path.toAbsolutePath().normalize();
}
static class ExitException extends Exception {
@@ -1750,6 +1787,29 @@ protected ModelProcessor createModelProcessor(PlexusContainer container) throws
return container.lookup(ModelProcessor.class);
}
+ private void warnAboutDeprecatedPropertiesFiles(Properties systemProperties) {
+ // Check for deprecated ~/.m2/maven.properties
+ String userConfig = systemProperties.getProperty("maven.user.conf");
+ Path userMavenProperties = userConfig != null ? Path.of(userConfig).resolve("maven.properties") : null;
+ if (userMavenProperties != null && Files.exists(userMavenProperties)) {
+ slf4jLogger.warn(
+ "Loading deprecated properties file: {}. " + "Please rename to 'maven-user.properties'. "
+ + "Support for 'maven.properties' will be removed in Maven 4.1.0.",
+ userMavenProperties);
+ }
+
+ // Check for deprecated .mvn/maven.properties in project directory
+ String projectConfig = systemProperties.getProperty("maven.project.conf");
+ Path projectMavenProperties =
+ projectConfig != null ? Path.of(projectConfig).resolve("maven.properties") : null;
+ if (projectMavenProperties != null && Files.exists(projectMavenProperties)) {
+ slf4jLogger.warn(
+ "Loading deprecated properties file: {}. " + "Please rename to 'maven-user.properties'. "
+ + "Support for 'maven.properties' will be removed in Maven 4.1.0.",
+ projectMavenProperties);
+ }
+ }
+
public void setFileSystem(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java
index 321bde249d82..9b4e7c819a22 100644
--- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java
@@ -73,6 +73,7 @@
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider;
import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager;
+import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory;
import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.WorkspaceReader;
@@ -272,7 +273,9 @@ public T getService(Class clazz) throws NoSuchElementExce
return (T) new DefaultArtifactManager(this);
} else if (clazz == RepositoryFactory.class) {
return (T) new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
- new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()));
+ new DefaultUpdatePolicyAnalyzer(),
+ new DefaultChecksumPolicyProvider(),
+ new DefaultRepositoryKeyFunctionFactory()));
} else if (clazz == Interpolator.class) {
return (T) new DefaultInterpolator();
// } else if (clazz == ModelResolver.class) {
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java
index 56a601901f13..87e62f8360a4 100644
--- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java
@@ -18,7 +18,7 @@
*/
package org.apache.maven.cli.internal;
-import org.apache.maven.api.cli.extensions.CoreExtension;
+import org.apache.maven.cli.internal.extension.model.CoreExtension;
/**
* Exception occurring trying to resolve a plugin.
@@ -37,6 +37,28 @@ public ExtensionResolutionException(CoreExtension extension, Throwable cause) {
this.extension = extension;
}
+ /**
+ * Constructor accepting the new API type for internal use.
+ *
+ * @param extension the new API extension
+ * @param cause the cause
+ */
+ public ExtensionResolutionException(org.apache.maven.api.cli.extensions.CoreExtension extension, Throwable cause) {
+ super(
+ "Extension " + extension.getId() + " or one of its dependencies could not be resolved: "
+ + cause.getMessage(),
+ cause);
+ // Convert to old type
+ CoreExtension oldExtension = new CoreExtension();
+ oldExtension.setGroupId(extension.getGroupId());
+ oldExtension.setArtifactId(extension.getArtifactId());
+ oldExtension.setVersion(extension.getVersion());
+ if (extension.getClassLoadingStrategy() != null) {
+ oldExtension.setClassLoadingStrategy(extension.getClassLoadingStrategy());
+ }
+ this.extension = oldExtension;
+ }
+
public CoreExtension getExtension() {
return extension;
}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtension.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtension.java
new file mode 100644
index 000000000000..c5ece38537d8
--- /dev/null
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtension.java
@@ -0,0 +1,155 @@
+/*
+ * 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.cli.internal.extension.model;
+
+/**
+ * Describes a build extension to utilise.
+ *
+ * @deprecated Use {@link org.apache.maven.api.cli.extensions.CoreExtension} instead
+ */
+@Deprecated
+@SuppressWarnings("all")
+public class CoreExtension implements java.io.Serializable {
+
+ // --------------------------/
+ // - Class/Member Variables -/
+ // --------------------------/
+
+ /**
+ * The group ID of the extension's artifact.
+ */
+ private String groupId;
+
+ /**
+ * The artifact ID of the extension.
+ */
+ private String artifactId;
+
+ /**
+ * The version of the extension.
+ */
+ private String version;
+
+ /**
+ * The class loading strategy: 'self-first' (the default),
+ * 'parent-first' (loads classes from the parent, then from the
+ * extension) or 'plugin' (follows the rules from extensions
+ * defined as plugins).
+ */
+ private String classLoadingStrategy = "self-first";
+
+ // -----------/
+ // - Methods -/
+ // -----------/
+
+ /**
+ * Get the artifact ID of the extension.
+ *
+ * @return String
+ */
+ public String getArtifactId() {
+ return this.artifactId;
+ } // -- String getArtifactId()
+
+ /**
+ * Get the class loading strategy: 'self-first' (the default),
+ * 'parent-first' (loads classes from the parent, then from the
+ * extension) or 'plugin' (follows the rules from extensions
+ * defined as plugins).
+ *
+ * @return String
+ */
+ public String getClassLoadingStrategy() {
+ return this.classLoadingStrategy;
+ } // -- String getClassLoadingStrategy()
+
+ /**
+ * Get the group ID of the extension's artifact.
+ *
+ * @return String
+ */
+ public String getGroupId() {
+ return this.groupId;
+ } // -- String getGroupId()
+
+ /**
+ * Get the version of the extension.
+ *
+ * @return String
+ */
+ public String getVersion() {
+ return this.version;
+ } // -- String getVersion()
+
+ /**
+ * Set the artifact ID of the extension.
+ *
+ * @param artifactId a artifactId object.
+ */
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ } // -- void setArtifactId( String )
+
+ /**
+ * Set the class loading strategy: 'self-first' (the default),
+ * 'parent-first' (loads classes from the parent, then from the
+ * extension) or 'plugin' (follows the rules from extensions
+ * defined as plugins).
+ *
+ * @param classLoadingStrategy a classLoadingStrategy object.
+ */
+ public void setClassLoadingStrategy(String classLoadingStrategy) {
+ this.classLoadingStrategy = classLoadingStrategy;
+ } // -- void setClassLoadingStrategy( String )
+
+ /**
+ * Set the group ID of the extension's artifact.
+ *
+ * @param groupId a groupId object.
+ */
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ } // -- void setGroupId( String )
+
+ /**
+ * Set the version of the extension.
+ *
+ * @param version a version object.
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ } // -- void setVersion( String )
+
+ /**
+ * Gets the identifier of the extension.
+ *
+ * @return The extension id in the form {@code ::}, never {@code null}.
+ */
+ public String getId() {
+ StringBuilder id = new StringBuilder(128);
+
+ id.append((getGroupId() == null) ? "[unknown-group-id]" : getGroupId());
+ id.append(":");
+ id.append((getArtifactId() == null) ? "[unknown-artifact-id]" : getArtifactId());
+ id.append(":");
+ id.append((getVersion() == null) ? "[unknown-version]" : getVersion());
+
+ return id.toString();
+ }
+}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtensions.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtensions.java
new file mode 100644
index 000000000000..6a9f88636db7
--- /dev/null
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/CoreExtensions.java
@@ -0,0 +1,109 @@
+/*
+ * 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.cli.internal.extension.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Extensions to load.
+ *
+ * @deprecated Use {@link org.apache.maven.api.cli.extensions.CoreExtension} instead
+ */
+@Deprecated
+@SuppressWarnings("all")
+public class CoreExtensions implements Serializable {
+
+ // --------------------------/
+ // - Class/Member Variables -/
+ // --------------------------/
+
+ /**
+ * Field extensions.
+ */
+ private List extensions;
+
+ /**
+ * Field modelEncoding.
+ */
+ private String modelEncoding = "UTF-8";
+
+ // -----------/
+ // - Methods -/
+ // -----------/
+
+ /**
+ * Method addExtension.
+ *
+ * @param coreExtension a coreExtension object.
+ */
+ public void addExtension(CoreExtension coreExtension) {
+ getExtensions().add(coreExtension);
+ } // -- void addExtension( CoreExtension )
+
+ /**
+ * Method getExtensions.
+ *
+ * @return List
+ */
+ public List getExtensions() {
+ if (this.extensions == null) {
+ this.extensions = new ArrayList();
+ }
+
+ return this.extensions;
+ } // -- List getExtensions()
+
+ /**
+ * Get the modelEncoding field.
+ *
+ * @return String
+ */
+ public String getModelEncoding() {
+ return this.modelEncoding;
+ } // -- String getModelEncoding()
+
+ /**
+ * Method removeExtension.
+ *
+ * @param coreExtension a coreExtension object.
+ */
+ public void removeExtension(CoreExtension coreExtension) {
+ getExtensions().remove(coreExtension);
+ } // -- void removeExtension( CoreExtension )
+
+ /**
+ * Set a set of build extensions to use from this project.
+ *
+ * @param extensions a extensions object.
+ */
+ public void setExtensions(List extensions) {
+ this.extensions = extensions;
+ } // -- void setExtensions( List )
+
+ /**
+ * Set the modelEncoding field.
+ *
+ * @param modelEncoding a modelEncoding object.
+ */
+ public void setModelEncoding(String modelEncoding) {
+ this.modelEncoding = modelEncoding;
+ } // -- void setModelEncoding( String )
+}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Reader.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Reader.java
new file mode 100644
index 000000000000..04eb952da494
--- /dev/null
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Reader.java
@@ -0,0 +1,692 @@
+/*
+ * 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.cli.internal.extension.model.io.xpp3;
+
+// ---------------------------------/
+// - Imported classes and packages -/
+// ---------------------------------/
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.text.DateFormat;
+
+import org.apache.maven.cli.internal.extension.model.CoreExtension;
+import org.apache.maven.cli.internal.extension.model.CoreExtensions;
+import org.codehaus.plexus.util.xml.XmlStreamReader;
+import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
+import org.codehaus.plexus.util.xml.pull.MXParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+/**
+ * Class CoreExtensionsXpp3Reader.
+ *
+ * @deprecated use {@code org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader}
+ */
+@Deprecated
+@SuppressWarnings("all")
+public class CoreExtensionsXpp3Reader {
+
+ // --------------------------/
+ // - Class/Member Variables -/
+ // --------------------------/
+
+ /**
+ * If set the parser will be loaded with all single characters
+ * from the XHTML specification.
+ * The entities used:
+ *
+ * http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
+ * http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
+ * http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
+ *
+ */
+ private boolean addDefaultEntities = true;
+
+ /**
+ * Field contentTransformer.
+ */
+ public final ContentTransformer contentTransformer;
+
+ // ----------------/
+ // - Constructors -/
+ // ----------------/
+
+ public CoreExtensionsXpp3Reader() {
+ this(new ContentTransformer() {
+ public String transform(String source, String fieldName) {
+ return source;
+ }
+ });
+ } // -- org.apache.maven.cli.internal.extension.model.io.xpp3.CoreExtensionsXpp3Reader()
+
+ public CoreExtensionsXpp3Reader(ContentTransformer contentTransformer) {
+ this.contentTransformer = contentTransformer;
+ } // -- org.apache.maven.cli.internal.extension.model.io.xpp3.CoreExtensionsXpp3Reader(ContentTransformer)
+
+ // -----------/
+ // - Methods -/
+ // -----------/
+
+ /**
+ * Method checkFieldWithDuplicate.
+ *
+ * @param parser a parser object.
+ * @param parsed a parsed object.
+ * @param alias a alias object.
+ * @param tagName a tagName object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return boolean
+ */
+ private boolean checkFieldWithDuplicate(
+ XmlPullParser parser, String tagName, String alias, java.util.Set parsed)
+ throws XmlPullParserException {
+ if (!(parser.getName().equals(tagName) || parser.getName().equals(alias))) {
+ return false;
+ }
+ if (!parsed.add(tagName)) {
+ throw new XmlPullParserException("Duplicated tag: '" + tagName + "'", parser, null);
+ }
+ return true;
+ } // -- boolean checkFieldWithDuplicate( XmlPullParser, String, String, java.util.Set )
+
+ /**
+ * Method checkUnknownAttribute.
+ *
+ * @param parser a parser object.
+ * @param strict a strict object.
+ * @param tagName a tagName object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @throws IOException IOException if any.
+ */
+ private void checkUnknownAttribute(XmlPullParser parser, String attribute, String tagName, boolean strict)
+ throws XmlPullParserException, IOException {
+ // strictXmlAttributes = true for model: if strict == true, not only elements are checked but attributes too
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unknown attribute '" + attribute + "' for tag '" + tagName + "'", parser, null);
+ }
+ } // -- void checkUnknownAttribute( XmlPullParser, String, String, boolean )
+
+ /**
+ * Method checkUnknownElement.
+ *
+ * @param parser a parser object.
+ * @param strict a strict object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @throws IOException IOException if any.
+ */
+ private void checkUnknownElement(XmlPullParser parser, boolean strict) throws XmlPullParserException, IOException {
+ if (strict) {
+ throw new XmlPullParserException("Unrecognised tag: '" + parser.getName() + "'", parser, null);
+ }
+
+ for (int unrecognizedTagCount = 1; unrecognizedTagCount > 0; ) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ unrecognizedTagCount++;
+ } else if (eventType == XmlPullParser.END_TAG) {
+ unrecognizedTagCount--;
+ }
+ }
+ } // -- void checkUnknownElement( XmlPullParser, boolean )
+
+ /**
+ * Returns the state of the "add default entities" flag.
+ *
+ * @return boolean
+ */
+ public boolean getAddDefaultEntities() {
+ return addDefaultEntities;
+ } // -- boolean getAddDefaultEntities()
+
+ /**
+ * Method getBooleanValue.
+ *
+ * @param s a s object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return boolean
+ */
+ private boolean getBooleanValue(String s, String attribute, XmlPullParser parser) throws XmlPullParserException {
+ return getBooleanValue(s, attribute, parser, null);
+ } // -- boolean getBooleanValue( String, String, XmlPullParser )
+
+ /**
+ * Method getBooleanValue.
+ *
+ * @param s a s object.
+ * @param defaultValue a defaultValue object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return boolean
+ */
+ private boolean getBooleanValue(String s, String attribute, XmlPullParser parser, String defaultValue)
+ throws XmlPullParserException {
+ if (s != null && s.length() != 0) {
+ return Boolean.valueOf(s).booleanValue();
+ }
+ if (defaultValue != null) {
+ return Boolean.valueOf(defaultValue).booleanValue();
+ }
+ return false;
+ } // -- boolean getBooleanValue( String, String, XmlPullParser, String )
+
+ /**
+ * Method getByteValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return byte
+ */
+ private byte getByteValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Byte.valueOf(s).byteValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be a byte", parser, nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- byte getByteValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getCharacterValue.
+ *
+ * @param s a s object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return char
+ */
+ private char getCharacterValue(String s, String attribute, XmlPullParser parser) throws XmlPullParserException {
+ if (s != null) {
+ return s.charAt(0);
+ }
+ return 0;
+ } // -- char getCharacterValue( String, String, XmlPullParser )
+
+ /**
+ * Method getDateValue.
+ *
+ * @param s a s object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return Date
+ */
+ private java.util.Date getDateValue(String s, String attribute, XmlPullParser parser)
+ throws XmlPullParserException {
+ return getDateValue(s, attribute, null, parser);
+ } // -- java.util.Date getDateValue( String, String, XmlPullParser )
+
+ /**
+ * Method getDateValue.
+ *
+ * @param s a s object.
+ * @param parser a parser object.
+ * @param dateFormat a dateFormat object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return Date
+ */
+ private java.util.Date getDateValue(String s, String attribute, String dateFormat, XmlPullParser parser)
+ throws XmlPullParserException {
+ if (s != null) {
+ String effectiveDateFormat = dateFormat;
+ if (dateFormat == null) {
+ effectiveDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ }
+ if ("long".equals(effectiveDateFormat)) {
+ try {
+ return new java.util.Date(Long.parseLong(s));
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(e.getMessage(), parser, e);
+ }
+ } else {
+ try {
+ DateFormat dateParser = new java.text.SimpleDateFormat(effectiveDateFormat, java.util.Locale.US);
+ return dateParser.parse(s);
+ } catch (java.text.ParseException e) {
+ throw new XmlPullParserException(e.getMessage(), parser, e);
+ }
+ }
+ }
+ return null;
+ } // -- java.util.Date getDateValue( String, String, String, XmlPullParser )
+
+ /**
+ * Method getDoubleValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return double
+ */
+ private double getDoubleValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Double.valueOf(s).doubleValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be a floating point number",
+ parser,
+ nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- double getDoubleValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getFloatValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return float
+ */
+ private float getFloatValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Float.valueOf(s).floatValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be a floating point number",
+ parser,
+ nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- float getFloatValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getIntegerValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return int
+ */
+ private int getIntegerValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Integer.valueOf(s).intValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be an integer", parser, nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- int getIntegerValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getLongValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return long
+ */
+ private long getLongValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Long.valueOf(s).longValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be a long integer", parser, nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- long getLongValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getRequiredAttributeValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return String
+ */
+ private String getRequiredAttributeValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s == null) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Missing required value for attribute '" + attribute + "'", parser, null);
+ }
+ }
+ return s;
+ } // -- String getRequiredAttributeValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getShortValue.
+ *
+ * @param s a s object.
+ * @param strict a strict object.
+ * @param parser a parser object.
+ * @param attribute a attribute object.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return short
+ */
+ private short getShortValue(String s, String attribute, XmlPullParser parser, boolean strict)
+ throws XmlPullParserException {
+ if (s != null) {
+ try {
+ return Short.valueOf(s).shortValue();
+ } catch (NumberFormatException nfe) {
+ if (strict) {
+ throw new XmlPullParserException(
+ "Unable to parse element '" + attribute + "', must be a short integer", parser, nfe);
+ }
+ }
+ }
+ return 0;
+ } // -- short getShortValue( String, String, XmlPullParser, boolean )
+
+ /**
+ * Method getTrimmedValue.
+ *
+ * @param s a s object.
+ * @return String
+ */
+ private String getTrimmedValue(String s) {
+ if (s != null) {
+ s = s.trim();
+ }
+ return s;
+ } // -- String getTrimmedValue( String )
+
+ /**
+ * Method interpolatedTrimmed.
+ *
+ * @param value a value object.
+ * @param context a context object.
+ * @return String
+ */
+ private String interpolatedTrimmed(String value, String context) {
+ return getTrimmedValue(contentTransformer.transform(value, context));
+ } // -- String interpolatedTrimmed( String, String )
+
+ /**
+ * Method nextTag.
+ *
+ * @param parser a parser object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return int
+ */
+ private int nextTag(XmlPullParser parser) throws IOException, XmlPullParserException {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.TEXT) {
+ eventType = parser.next();
+ }
+ if (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException(
+ "expected START_TAG or END_TAG not " + XmlPullParser.TYPES[eventType], parser, null);
+ }
+ return eventType;
+ } // -- int nextTag( XmlPullParser )
+
+ /**
+ * Method read.
+ *
+ * @param parser a parser object.
+ * @param strict a strict object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ public CoreExtensions read(XmlPullParser parser, boolean strict) throws IOException, XmlPullParserException {
+ CoreExtensions coreExtensions = null;
+ int eventType = parser.getEventType();
+ boolean parsed = false;
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (strict && !"extensions".equals(parser.getName())) {
+ throw new XmlPullParserException(
+ "Expected root element 'extensions' but found '" + parser.getName() + "'", parser, null);
+ } else if (parsed) {
+ // fallback, already expected a XmlPullParserException due to invalid XML
+ throw new XmlPullParserException("Duplicated tag: 'extensions'", parser, null);
+ }
+ coreExtensions = parseCoreExtensions(parser, strict);
+ coreExtensions.setModelEncoding(parser.getInputEncoding());
+ parsed = true;
+ }
+ eventType = parser.next();
+ }
+ if (parsed) {
+ return coreExtensions;
+ }
+ throw new XmlPullParserException(
+ "Expected root element 'extensions' but found no element at all: invalid XML document", parser, null);
+ } // -- CoreExtensions read( XmlPullParser, boolean )
+
+ /**
+ * @see XmlStreamReader
+ *
+ * @param reader a reader object.
+ * @param strict a strict object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ public CoreExtensions read(Reader reader, boolean strict) throws IOException, XmlPullParserException {
+ XmlPullParser parser =
+ addDefaultEntities ? new MXParser(EntityReplacementMap.defaultEntityReplacementMap) : new MXParser();
+
+ parser.setInput(reader);
+
+ return read(parser, strict);
+ } // -- CoreExtensions read( Reader, boolean )
+
+ /**
+ * @see XmlStreamReader
+ *
+ * @param reader a reader object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ public CoreExtensions read(Reader reader) throws IOException, XmlPullParserException {
+ return read(reader, true);
+ } // -- CoreExtensions read( Reader )
+
+ /**
+ * Method read.
+ *
+ * @param in a in object.
+ * @param strict a strict object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ public CoreExtensions read(InputStream in, boolean strict) throws IOException, XmlPullParserException {
+ return read(new XmlStreamReader(in), strict);
+ } // -- CoreExtensions read( InputStream, boolean )
+
+ /**
+ * Method read.
+ *
+ * @param in a in object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ public CoreExtensions read(InputStream in) throws IOException, XmlPullParserException {
+ return read(new XmlStreamReader(in));
+ } // -- CoreExtensions read( InputStream )
+
+ /**
+ * Method parseCoreExtension.
+ *
+ * @param parser a parser object.
+ * @param strict a strict object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtension
+ */
+ private CoreExtension parseCoreExtension(XmlPullParser parser, boolean strict)
+ throws IOException, XmlPullParserException {
+ String tagName = parser.getName();
+ CoreExtension coreExtension = new CoreExtension();
+ for (int i = parser.getAttributeCount() - 1; i >= 0; i--) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if (name.indexOf(':') >= 0) {
+ // just ignore attributes with non-default namespace (for example: xmlns:xsi)
+ } else {
+ checkUnknownAttribute(parser, name, tagName, strict);
+ }
+ }
+ java.util.Set parsed = new java.util.HashSet();
+ while ((strict ? parser.nextTag() : nextTag(parser)) == XmlPullParser.START_TAG) {
+ if (checkFieldWithDuplicate(parser, "groupId", null, parsed)) {
+ coreExtension.setGroupId(interpolatedTrimmed(parser.nextText(), "groupId"));
+ } else if (checkFieldWithDuplicate(parser, "artifactId", null, parsed)) {
+ coreExtension.setArtifactId(interpolatedTrimmed(parser.nextText(), "artifactId"));
+ } else if (checkFieldWithDuplicate(parser, "version", null, parsed)) {
+ coreExtension.setVersion(interpolatedTrimmed(parser.nextText(), "version"));
+ } else if (checkFieldWithDuplicate(parser, "classLoadingStrategy", null, parsed)) {
+ coreExtension.setClassLoadingStrategy(interpolatedTrimmed(parser.nextText(), "classLoadingStrategy"));
+ } else {
+ checkUnknownElement(parser, strict);
+ }
+ }
+ return coreExtension;
+ } // -- CoreExtension parseCoreExtension( XmlPullParser, boolean )
+
+ /**
+ * Method parseCoreExtensions.
+ *
+ * @param parser a parser object.
+ * @param strict a strict object.
+ * @throws IOException IOException if any.
+ * @throws XmlPullParserException XmlPullParserException if
+ * any.
+ * @return CoreExtensions
+ */
+ private CoreExtensions parseCoreExtensions(XmlPullParser parser, boolean strict)
+ throws IOException, XmlPullParserException {
+ String tagName = parser.getName();
+ CoreExtensions coreExtensions = new CoreExtensions();
+ for (int i = parser.getAttributeCount() - 1; i >= 0; i--) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if (name.indexOf(':') >= 0) {
+ // just ignore attributes with non-default namespace (for example: xmlns:xsi)
+ } else if ("xmlns".equals(name)) {
+ // ignore xmlns attribute in root class, which is a reserved attribute name
+ } else {
+ checkUnknownAttribute(parser, name, tagName, strict);
+ }
+ }
+ java.util.Set parsed = new java.util.HashSet();
+ while ((strict ? parser.nextTag() : nextTag(parser)) == XmlPullParser.START_TAG) {
+ if ("extension".equals(parser.getName())) {
+ java.util.List extensions = coreExtensions.getExtensions();
+ if (extensions == null) {
+ extensions = new java.util.ArrayList();
+ }
+ extensions.add(parseCoreExtension(parser, strict));
+ coreExtensions.setExtensions(extensions);
+ } else {
+ checkUnknownElement(parser, strict);
+ }
+ }
+ return coreExtensions;
+ } // -- CoreExtensions parseCoreExtensions( XmlPullParser, boolean )
+
+ /**
+ * Sets the state of the "add default entities" flag.
+ *
+ * @param addDefaultEntities a addDefaultEntities object.
+ */
+ public void setAddDefaultEntities(boolean addDefaultEntities) {
+ this.addDefaultEntities = addDefaultEntities;
+ } // -- void setAddDefaultEntities( boolean )
+
+ public static interface ContentTransformer {
+ /**
+ * Interpolate the value read from the xpp3 document
+ * @param source The source value
+ * @param fieldName A description of the field being interpolated. The implementation may use this to
+ * log stuff.
+ * @return The interpolated value.
+ */
+ String transform(String source, String fieldName);
+ }
+}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Writer.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Writer.java
new file mode 100644
index 000000000000..95fa069f02df
--- /dev/null
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/extension/model/io/xpp3/CoreExtensionsXpp3Writer.java
@@ -0,0 +1,173 @@
+/*
+ * 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.cli.internal.extension.model.io.xpp3;
+
+// ---------------------------------/
+// - Imported classes and packages -/
+// ---------------------------------/
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.Iterator;
+
+import org.apache.maven.cli.internal.extension.model.CoreExtension;
+import org.apache.maven.cli.internal.extension.model.CoreExtensions;
+import org.codehaus.plexus.util.xml.pull.MXSerializer;
+import org.codehaus.plexus.util.xml.pull.XmlSerializer;
+
+/**
+ * Class CoreExtensionsXpp3Writer.
+ *
+ * @deprecated use {@code org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxWriter}
+ */
+@Deprecated
+@SuppressWarnings("all")
+public class CoreExtensionsXpp3Writer {
+
+ // --------------------------/
+ // - Class/Member Variables -/
+ // --------------------------/
+
+ /**
+ * Field NAMESPACE.
+ */
+ private static final String NAMESPACE = null;
+
+ /**
+ * Field fileComment.
+ */
+ private String fileComment = null;
+
+ // -----------/
+ // - Methods -/
+ // -----------/
+
+ /**
+ * Method setFileComment.
+ *
+ * @param fileComment a fileComment object.
+ */
+ public void setFileComment(String fileComment) {
+ this.fileComment = fileComment;
+ } // -- void setFileComment( String )
+
+ /**
+ * Method write.
+ *
+ * @param writer a writer object.
+ * @param coreExtensions a coreExtensions object.
+ * @throws IOException IOException if any.
+ */
+ public void write(Writer writer, CoreExtensions coreExtensions) throws IOException {
+ XmlSerializer serializer = new MXSerializer();
+ serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", " ");
+ serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", "\n");
+ serializer.setOutput(writer);
+ serializer.startDocument(coreExtensions.getModelEncoding(), null);
+ writeCoreExtensions(coreExtensions, "extensions", serializer);
+ serializer.endDocument();
+ } // -- void write( Writer, CoreExtensions )
+
+ /**
+ * Method write.
+ *
+ * @param stream a stream object.
+ * @param coreExtensions a coreExtensions object.
+ * @throws IOException IOException if any.
+ */
+ public void write(OutputStream stream, CoreExtensions coreExtensions) throws IOException {
+ XmlSerializer serializer = new MXSerializer();
+ serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", " ");
+ serializer.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", "\n");
+ serializer.setOutput(stream, coreExtensions.getModelEncoding());
+ serializer.startDocument(coreExtensions.getModelEncoding(), null);
+ writeCoreExtensions(coreExtensions, "extensions", serializer);
+ serializer.endDocument();
+ } // -- void write( OutputStream, CoreExtensions )
+
+ /**
+ * Method writeCoreExtension.
+ *
+ * @param coreExtension a coreExtension object.
+ * @param serializer a serializer object.
+ * @param tagName a tagName object.
+ * @throws IOException IOException if any.
+ */
+ private void writeCoreExtension(CoreExtension coreExtension, String tagName, XmlSerializer serializer)
+ throws IOException {
+ serializer.startTag(NAMESPACE, tagName);
+ if (coreExtension.getGroupId() != null) {
+ serializer
+ .startTag(NAMESPACE, "groupId")
+ .text(coreExtension.getGroupId())
+ .endTag(NAMESPACE, "groupId");
+ }
+ if (coreExtension.getArtifactId() != null) {
+ serializer
+ .startTag(NAMESPACE, "artifactId")
+ .text(coreExtension.getArtifactId())
+ .endTag(NAMESPACE, "artifactId");
+ }
+ if (coreExtension.getVersion() != null) {
+ serializer
+ .startTag(NAMESPACE, "version")
+ .text(coreExtension.getVersion())
+ .endTag(NAMESPACE, "version");
+ }
+ if ((coreExtension.getClassLoadingStrategy() != null)
+ && !coreExtension.getClassLoadingStrategy().equals("self-first")) {
+ serializer
+ .startTag(NAMESPACE, "classLoadingStrategy")
+ .text(coreExtension.getClassLoadingStrategy())
+ .endTag(NAMESPACE, "classLoadingStrategy");
+ }
+ serializer.endTag(NAMESPACE, tagName);
+ } // -- void writeCoreExtension( CoreExtension, String, XmlSerializer )
+
+ /**
+ * Method writeCoreExtensions.
+ *
+ * @param coreExtensions a coreExtensions object.
+ * @param serializer a serializer object.
+ * @param tagName a tagName object.
+ * @throws IOException IOException if any.
+ */
+ private void writeCoreExtensions(CoreExtensions coreExtensions, String tagName, XmlSerializer serializer)
+ throws IOException {
+ if (this.fileComment != null) {
+ serializer.comment(this.fileComment);
+ }
+ serializer.setPrefix("", "http://maven.apache.org/EXTENSIONS/1.1.0");
+ serializer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ serializer.startTag(NAMESPACE, tagName);
+ serializer.attribute(
+ "",
+ "xsi:schemaLocation",
+ "http://maven.apache.org/EXTENSIONS/1.1.0 https://maven.apache.org/xsd/core-extensions-1.1.0.xsd");
+ if ((coreExtensions.getExtensions() != null)
+ && (coreExtensions.getExtensions().size() > 0)) {
+ for (Iterator iter = coreExtensions.getExtensions().iterator(); iter.hasNext(); ) {
+ CoreExtension o = (CoreExtension) iter.next();
+ writeCoreExtension(o, "extension", serializer);
+ }
+ }
+ serializer.endTag(NAMESPACE, tagName);
+ } // -- void writeCoreExtensions( CoreExtensions, String, XmlSerializer )
+}
diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java
index 8789b9b1e1c9..c259ae14d4d6 100644
--- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java
+++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java
@@ -29,7 +29,11 @@
* making it not very suitable for usage in collections.
*/
@Deprecated
-record TransferResourceIdentifier(String repositoryId, String repositoryUrl, String resourceName, @Nullable File file) {
+record TransferResourceIdentifier(
+ String repositoryId,
+ String repositoryUrl,
+ String resourceName,
+ @Nullable File file) {
TransferResourceIdentifier(TransferResource resource) {
this(resource.getRepositoryId(), resource.getRepositoryUrl(), resource.getResourceName(), resource.getFile());
}
diff --git a/compat/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/compat/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index ae1f439dc751..5867a4537cdf 100644
--- a/compat/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/compat/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -592,14 +592,14 @@ public void testPropertiesInterpolation() throws Exception {
Files.createDirectories(mavenHome);
Path mavenConf = mavenHome.resolve("conf");
Files.createDirectories(mavenConf);
- Path mavenUserProps = mavenConf.resolve("maven.properties");
- Files.writeString(mavenUserProps, "${includes} = ?${session.rootDirectory}/.mvn/maven.properties\n");
+ Path mavenUserProps = mavenConf.resolve("maven-user.properties");
+ Files.writeString(mavenUserProps, "${includes} = ?${session.rootDirectory}/.mvn/maven-user.properties\n");
Path rootDirectory = fs.getPath("C:\\myRootDirectory");
Path topDirectory = rootDirectory.resolve("myTopDirectory");
Path mvn = rootDirectory.resolve(".mvn");
Files.createDirectories(mvn);
Files.writeString(
- mvn.resolve("maven.properties"),
+ mvn.resolve("maven-user.properties"),
"${includes} = env-${envName}.properties\nfro = ${bar}z\n" + "bar = chti${java.version}\n");
Files.writeString(mvn.resolve("env-test.properties"), "\n");
diff --git a/compat/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java b/compat/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java
index 07aa1e8ba664..16724ab44051 100644
--- a/compat/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java
+++ b/compat/maven-embedder/src/test/java/org/apache/maven/cli/props/MavenPropertiesTest.java
@@ -51,8 +51,7 @@ public class MavenPropertiesTest {
private MavenProperties properties;
- static final String TEST_PROPERTIES =
- """
+ static final String TEST_PROPERTIES = """
#
# test.properties
# Used in the PropertiesTest
@@ -60,10 +59,6 @@ public class MavenPropertiesTest {
test=test
""";
- /*
- * (non-Javadoc)
- * @see junit.framework.TestCase#setUp()
- */
@BeforeEach
public void setUp() throws Exception {
properties = new MavenProperties();
diff --git a/compat/maven-model-builder/pom.xml b/compat/maven-model-builder/pom.xml
index 5621ba096b91..8c2719f1f6b9 100644
--- a/compat/maven-model-builder/pom.xml
+++ b/compat/maven-model-builder/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-model-builder
diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index 5e2d47329dda..767054b9bd0c 100644
--- a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -785,10 +785,10 @@ private void validateEffectiveDependencies(
prefix, "version", problems, errOn30, Version.V20, d.getVersion(), d.getManagementKey(), d);
/*
- * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
- * order to don't break backward-compat with those, only warn but don't error out.
+ * Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
+ * order to not break backward-compat with those, only warn but don't error out.
*/
- validateEnum(
+ validateDependencyScope(
prefix,
"scope",
problems,
@@ -797,15 +797,11 @@ private void validateEffectiveDependencies(
d.getScope(),
d.getManagementKey(),
d,
- "provided",
- "compile",
- "runtime",
- "test",
- "system");
+ false);
validateEffectiveModelAgainstDependency(prefix, problems, m, d, request);
} else {
- validateEnum(
+ validateDependencyScope(
prefix,
"scope",
problems,
@@ -814,12 +810,7 @@ private void validateEffectiveDependencies(
d.getScope(),
d.getManagementKey(),
d,
- "provided",
- "compile",
- "runtime",
- "test",
- "system",
- "import");
+ true);
}
}
}
@@ -1462,6 +1453,58 @@ private boolean validateEnum(
return false;
}
+ @SuppressWarnings("checkstyle:parameternumber")
+ private boolean validateDependencyScope(
+ String prefix,
+ String fieldName,
+ ModelProblemCollector problems,
+ Severity severity,
+ Version version,
+ String scope,
+ String sourceHint,
+ InputLocationTracker tracker,
+ boolean isDependencyManagement) {
+ if (scope == null || scope.length() <= 0) {
+ return true;
+ }
+
+ String[] validScopes;
+ if (isDependencyManagement) {
+ validScopes = new String[] {"provided", "compile", "runtime", "test", "system", "import"};
+ } else {
+ validScopes = new String[] {"provided", "compile", "runtime", "test", "system"};
+ }
+
+ List values = Arrays.asList(validScopes);
+
+ if (values.contains(scope)) {
+ return true;
+ }
+
+ // Provide a more helpful error message for the 'import' scope
+ if ("import".equals(scope) && !isDependencyManagement) {
+ addViolation(
+ problems,
+ severity,
+ version,
+ prefix + fieldName,
+ sourceHint,
+ "has scope 'import'. The 'import' scope is only valid in sections.",
+ tracker);
+ } else {
+ addViolation(
+ problems,
+ severity,
+ version,
+ prefix + fieldName,
+ sourceHint,
+ "must be one of " + values + " but is '" + scope + "'.",
+ tracker);
+ }
+
+ return false;
+ }
+
@SuppressWarnings("checkstyle:parameternumber")
private boolean validateModelVersion(
ModelProblemCollector problems, String string, InputLocationTracker tracker, String... validVersions) {
diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java b/compat/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java
index ef189d6a5153..b4d29435b92a 100644
--- a/compat/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java
+++ b/compat/maven-model-builder/src/main/java/org/apache/maven/utils/Os.java
@@ -188,9 +188,10 @@ public static boolean isFamily(String family, String actualOsName) {
case FAMILY_DOS -> File.pathSeparatorChar == ';' && !isFamily(FAMILY_NETWARE, actualOsName) && !isWindows;
case FAMILY_MAC -> actualOsName.contains(FAMILY_MAC) || actualOsName.contains(DARWIN);
case FAMILY_TANDEM -> actualOsName.contains("nonstop_kernel");
- case FAMILY_UNIX -> File.pathSeparatorChar == ':'
- && !isFamily(FAMILY_OPENVMS, actualOsName)
- && (!isFamily(FAMILY_MAC, actualOsName) || actualOsName.endsWith("x"));
+ case FAMILY_UNIX ->
+ File.pathSeparatorChar == ':'
+ && !isFamily(FAMILY_OPENVMS, actualOsName)
+ && (!isFamily(FAMILY_MAC, actualOsName) || actualOsName.endsWith("x"));
case FAMILY_ZOS -> actualOsName.contains(FAMILY_ZOS) || actualOsName.contains(FAMILY_OS390);
case FAMILY_OS400 -> actualOsName.contains(FAMILY_OS400);
case FAMILY_OPENVMS -> actualOsName.contains(FAMILY_OPENVMS);
diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index cdfa9caac1a5..d856b6ae942e 100644
--- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -355,6 +355,10 @@ void testBadDependencyScope() throws Exception {
assertViolations(result, 0, 0, 2);
assertTrue(result.getWarnings().get(0).contains("test:f"));
+ // Check that the import scope error message is more helpful
+ assertTrue(result.getWarnings()
+ .get(0)
+ .contains("has scope 'import'. The 'import' scope is only valid in sections"));
assertTrue(result.getWarnings().get(1).contains("test:g"));
}
diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml
index e39bf70e5cc9..7f5451948d26 100644
--- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml
+++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml
@@ -58,4 +58,20 @@ under the License.
+
+
+
+
+ MNG-5115-2
+
+
+ inherited-append
+
+ from-child
+
+
+
+
+
+
diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml
index b969d4bd7c2c..9c2d0deed66e 100644
--- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml
+++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml
@@ -82,6 +82,18 @@ under the License.
+
+ MNG-5115-2
+
+
+ inherited-append
+
+ from-child
+ to-be-inherited
+
+
+
+
diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml
index 4ecb22475451..dd134650109c 100644
--- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml
+++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml
@@ -84,6 +84,17 @@ under the License.
+
+ MNG-5115-2
+
+
+ inherited-append
+
+ to-be-inherited
+
+
+
+
diff --git a/compat/maven-model/pom.xml b/compat/maven-model/pom.xml
index d5c7dcc7aafb..772ddcec12be 100644
--- a/compat/maven-model/pom.xml
+++ b/compat/maven-model/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-model
@@ -71,7 +71,6 @@ under the License.
org.openjdk.jmh
jmh-core
- 1.37
test
diff --git a/compat/maven-model/src/main/java/org/apache/maven/model/InputLocation.java b/compat/maven-model/src/main/java/org/apache/maven/model/InputLocation.java
index 70ef9bb68864..0fe1e5dc1c81 100644
--- a/compat/maven-model/src/main/java/org/apache/maven/model/InputLocation.java
+++ b/compat/maven-model/src/main/java/org/apache/maven/model/InputLocation.java
@@ -334,17 +334,17 @@ public void setLocations(java.util.Map locations) {
public org.apache.maven.api.model.InputLocation toApiLocation() {
if (locations != null && locations.values().contains(this)) {
if (locations.size() == 1 && locations.values().iterator().next() == this) {
- return new org.apache.maven.api.model.InputLocation(
+ return org.apache.maven.api.model.InputLocation.of(
lineNumber,
columnNumber,
source != null ? source.toApiSource() : null,
locations.keySet().iterator().next());
} else {
- return new org.apache.maven.api.model.InputLocation(
+ return org.apache.maven.api.model.InputLocation.of(
lineNumber, columnNumber, source != null ? source.toApiSource() : null);
}
} else {
- return new org.apache.maven.api.model.InputLocation(
+ return org.apache.maven.api.model.InputLocation.of(
lineNumber,
columnNumber,
source != null ? source.toApiSource() : null,
diff --git a/compat/maven-model/src/site/apt/index.apt b/compat/maven-model/src/site/apt/index.apt
index 680358f0e0fe..0d33ae519441 100644
--- a/compat/maven-model/src/site/apt/index.apt
+++ b/compat/maven-model/src/site/apt/index.apt
@@ -28,7 +28,7 @@
Maven Model
This is strictly the model for Maven POM (Project Object Model) in <<>> package,
- delegating content to {{{../api/maven-api-model/index.html}Maven 4 API immutable model}}. All the effective model
+ delegating content to {{{../../api/maven-api-model/index.html}Maven 4 API immutable model}}. All the effective model
building logic from multiple POMs and building context is done in {{{../maven-model-builder/}Maven Model Builder}}.
The following are generated from this model:
@@ -36,6 +36,6 @@ Maven Model
* {{{./apidocs/index.html}Java sources}} with Reader and Writers for the Xpp3 XML parser, <<>> and <<>> transformers, and <<>> package
for Merger and v4 Reader and Writers for the Xpp3 XML parser,
- * A {{{./maven.html}Descriptor Reference}}
+ * A {{{../../api/maven-api-model/maven.html}Descriptor Reference}}
- * An XSD {{{https://maven.apache.org/xsd/maven-v3_0_0.xsd}for Maven 1.1}} and {{{https://maven.apache.org/xsd/maven-4.0.0.xsd}for Maven 2.0}}.
+ * An XSD {{{https://maven.apache.org/xsd/maven-v3_0_0.xsd}for Maven 1.1}} and {{{https://maven.apache.org/xsd/maven-4.0.0.xsd}for Maven 2 and 3}}.
diff --git a/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java b/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
index 2f872a7bf505..967af237c5cf 100644
--- a/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
+++ b/compat/maven-model/src/test/java/org/apache/maven/model/ModelTest.java
@@ -66,4 +66,17 @@ void testEqualsIdentity() {
void testToStringNullSafe() {
assertNotNull(new Model().toString());
}
+
+ @Test
+ void testPropertiesClear() {
+ // Test for issue #11552: NullPointerException when clearing properties
+ Model model = new Model();
+ model.addProperty("key1", "value1");
+ model.addProperty("key2", "value2");
+ assertEquals(2, model.getProperties().size());
+
+ // This should not throw NullPointerException
+ model.getProperties().clear();
+ assertEquals(0, model.getProperties().size());
+ }
}
diff --git a/compat/maven-plugin-api/pom.xml b/compat/maven-plugin-api/pom.xml
index 31e4b6396d10..dffdb05609b4 100644
--- a/compat/maven-plugin-api/pom.xml
+++ b/compat/maven-plugin-api/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-plugin-api
diff --git a/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java b/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java
index b6edfc04c189..086a49daafeb 100644
--- a/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java
+++ b/compat/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java
@@ -71,7 +71,24 @@ public PluginDescriptor build(Reader reader) throws PlexusConfigurationException
*/
@Deprecated
public PluginDescriptor build(Reader reader, String source) throws PlexusConfigurationException {
- return build(() -> reader, source);
+ try {
+ BufferedReader br = new BufferedReader(reader, BUFFER_SIZE);
+ br.mark(BUFFER_SIZE);
+ XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(br);
+ xsr.nextTag();
+ String nsUri = xsr.getNamespaceURI();
+ br.reset();
+ if (PLUGIN_2_0_0.equals(nsUri)) {
+ xsr = XMLInputFactory.newFactory().createXMLStreamReader(br);
+ return new PluginDescriptor(new PluginDescriptorStaxReader().read(xsr, true));
+ } else {
+ // Call buildConfiguration() for backward compatibility with subclasses that override it
+ PlexusConfiguration cfg = buildConfiguration(br);
+ return build(source, cfg);
+ }
+ } catch (XMLStreamException | IOException e) {
+ throw new PlexusConfigurationException(e.getMessage(), e);
+ }
}
public PluginDescriptor build(ReaderSupplier readerSupplier) throws PlexusConfigurationException {
@@ -98,7 +115,24 @@ public PluginDescriptor build(ReaderSupplier readerSupplier, String source) thro
*/
@Deprecated
public PluginDescriptor build(InputStream input, String source) throws PlexusConfigurationException {
- return build(() -> input, source);
+ try {
+ BufferedInputStream bis = new BufferedInputStream(input, BUFFER_SIZE);
+ bis.mark(BUFFER_SIZE);
+ XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(bis);
+ xsr.nextTag();
+ String nsUri = xsr.getNamespaceURI();
+ bis.reset();
+ if (PLUGIN_2_0_0.equals(nsUri)) {
+ xsr = XMLInputFactory.newFactory().createXMLStreamReader(bis);
+ return new PluginDescriptor(new PluginDescriptorStaxReader().read(xsr, true));
+ } else {
+ // Call buildConfiguration() for backward compatibility with subclasses that override it
+ PlexusConfiguration cfg = buildConfiguration(bis);
+ return build(source, cfg);
+ }
+ } catch (XMLStreamException | IOException e) {
+ throw new PlexusConfigurationException(e.getMessage(), e);
+ }
}
public PluginDescriptor build(StreamSupplier inputSupplier) throws PlexusConfigurationException {
diff --git a/compat/maven-plugin-api/src/site/apt/index.apt b/compat/maven-plugin-api/src/site/apt/index.apt
index eaccae6eb857..aebf9ea28aa5 100644
--- a/compat/maven-plugin-api/src/site/apt/index.apt
+++ b/compat/maven-plugin-api/src/site/apt/index.apt
@@ -33,7 +33,7 @@ Maven 3 Plugin API
[]
- A plugin is described in a {{{./plugin.html}<<>> plugin descriptor}},
+ A plugin is described in a {{{../../api/maven-api-plugin/plugin.html}<<>> plugin descriptor}},
generally generated from plugin sources using {{{/plugin-tools/maven-plugin-plugin/}maven-plugin-plugin}}.
* See Also
diff --git a/compat/maven-repository-metadata/pom.xml b/compat/maven-repository-metadata/pom.xml
index 081e5c0b0f1c..bb12e5216bcc 100644
--- a/compat/maven-repository-metadata/pom.xml
+++ b/compat/maven-repository-metadata/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-repository-metadata
diff --git a/compat/maven-repository-metadata/src/site/apt/index.apt b/compat/maven-repository-metadata/src/site/apt/index.apt
index 56fc874de446..fb6f37f97c65 100644
--- a/compat/maven-repository-metadata/src/site/apt/index.apt
+++ b/compat/maven-repository-metadata/src/site/apt/index.apt
@@ -53,7 +53,7 @@ Maven Repository Metadata Model
* {{{./apidocs/index.html}Java sources}} with Reader and Writers for the Xpp3 XML parser, to read and write <<>> files,
- * a {{{./repository-metadata.html}Descriptor Reference}}.
+ * a {{{../../api/maven-api-metadata/repository-metadata.html}Descriptor Reference}}.
For more information see this page: {{{https://maven.apache.org/repositories/metadata.html}Maven Metadata}}.
diff --git a/compat/maven-resolver-provider/pom.xml b/compat/maven-resolver-provider/pom.xml
index 8ff251a07a3c..79574d50d750 100644
--- a/compat/maven-resolver-provider/pom.xml
+++ b/compat/maven-resolver-provider/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-resolver-provider
diff --git a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorUtils.java b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorUtils.java
index 821db5de1400..7771fb7646f8 100644
--- a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorUtils.java
+++ b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorUtils.java
@@ -86,8 +86,8 @@ public static String toRepositoryChecksumPolicy(final String artifactRepositoryP
case RepositoryPolicy.CHECKSUM_POLICY_FAIL -> RepositoryPolicy.CHECKSUM_POLICY_FAIL;
case RepositoryPolicy.CHECKSUM_POLICY_IGNORE -> RepositoryPolicy.CHECKSUM_POLICY_IGNORE;
case RepositoryPolicy.CHECKSUM_POLICY_WARN -> RepositoryPolicy.CHECKSUM_POLICY_WARN;
- default -> throw new IllegalArgumentException(
- "unknown repository checksum policy: " + artifactRepositoryPolicy);
+ default ->
+ throw new IllegalArgumentException("unknown repository checksum policy: " + artifactRepositoryPolicy);
};
}
}
diff --git a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
index 5c5eb0cd0c0a..e96c0deaa510 100644
--- a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
+++ b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultVersionRangeResolver.java
@@ -28,9 +28,11 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import org.apache.maven.api.Constants;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.repository.metadata.Versioning;
import org.apache.maven.metadata.v4.MetadataStaxReader;
@@ -53,6 +55,7 @@
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.spi.synccontext.SyncContextFactory;
+import org.eclipse.aether.util.ConfigUtils;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionConstraint;
@@ -107,11 +110,35 @@ public VersionRangeResult resolveVersionRange(RepositorySystemSession session, V
result.addVersion(versionConstraint.getVersion());
} else {
VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
+ VersionRange.Bound upperBound = versionConstraint.getRange().getUpperBound();
if (lowerBound != null
&& lowerBound.equals(versionConstraint.getRange().getUpperBound())) {
result.addVersion(lowerBound.getVersion());
} else {
- Map versionIndex = getVersions(session, result, request);
+ Metadata.Nature wantedNature;
+ String natureString = ConfigUtils.getString(
+ session, request.getNature().name(), Constants.MAVEN_VERSION_RANGE_RESOLVER_NATURE_OVERRIDE);
+ if ("auto".equals(natureString)) {
+ org.eclipse.aether.artifact.Artifact lowerArtifact = lowerBound != null
+ ? request.getArtifact()
+ .setVersion(lowerBound.getVersion().toString())
+ : null;
+ org.eclipse.aether.artifact.Artifact upperArtifact = upperBound != null
+ ? request.getArtifact()
+ .setVersion(upperBound.getVersion().toString())
+ : null;
+
+ if (lowerArtifact != null && lowerArtifact.isSnapshot()
+ || upperArtifact != null && upperArtifact.isSnapshot()) {
+ wantedNature = Metadata.Nature.RELEASE_OR_SNAPSHOT;
+ } else {
+ wantedNature = Metadata.Nature.RELEASE;
+ }
+ } else {
+ wantedNature = Metadata.Nature.valueOf(natureString.toUpperCase(Locale.ROOT));
+ }
+
+ Map versionIndex = getVersions(session, result, request, wantedNature);
List versions = new ArrayList<>();
for (Map.Entry v : versionIndex.entrySet()) {
@@ -135,7 +162,10 @@ public VersionRangeResult resolveVersionRange(RepositorySystemSession session, V
}
private Map getVersions(
- RepositorySystemSession session, VersionRangeResult result, VersionRangeRequest request) {
+ RepositorySystemSession session,
+ VersionRangeResult result,
+ VersionRangeRequest request,
+ Metadata.Nature wantedNature) {
RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
Map versionIndex = new HashMap<>();
@@ -144,7 +174,7 @@ private Map getVersions(
request.getArtifact().getGroupId(),
request.getArtifact().getArtifactId(),
MAVEN_METADATA_XML,
- Metadata.Nature.RELEASE_OR_SNAPSHOT);
+ wantedNature);
List metadataRequests =
new ArrayList<>(request.getRepositories().size());
diff --git a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
index 72275546b66c..0ee51533211f 100644
--- a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
+++ b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
@@ -43,11 +43,12 @@
import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
import org.eclipse.aether.util.artifact.DefaultArtifactTypeRegistry;
import org.eclipse.aether.util.graph.manager.ClassicDependencyManager;
+import org.eclipse.aether.util.graph.manager.TransitiveDependencyManager;
import org.eclipse.aether.util.graph.selector.AndDependencySelector;
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
+import org.eclipse.aether.util.graph.transformer.ConfigurableVersionSelector;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
-import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
@@ -95,7 +96,9 @@ protected DependencyManager getDependencyManager() {
}
public DependencyManager getDependencyManager(boolean transitive) {
- return new ClassicDependencyManager(transitive, getScopeManager());
+ return transitive
+ ? new TransitiveDependencyManager(getScopeManager())
+ : new ClassicDependencyManager(getScopeManager());
}
protected DependencySelector getDependencySelector() {
@@ -109,7 +112,7 @@ protected DependencySelector getDependencySelector() {
protected DependencyGraphTransformer getDependencyGraphTransformer() {
return new ChainedDependencyGraphTransformer(
new ConflictResolver(
- new NearestVersionSelector(), new ManagedScopeSelector(getScopeManager()),
+ new ConfigurableVersionSelector(), new ManagedScopeSelector(getScopeManager()),
new SimpleOptionalitySelector(), new ManagedScopeDeriver(getScopeManager())),
new ManagedDependencyContextRefiner(getScopeManager()));
}
diff --git a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java
index 37a7c3b416bf..ea0004223afb 100644
--- a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java
+++ b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/relocation/UserPropertiesArtifactRelocationSource.java
@@ -202,8 +202,10 @@ private static Artifact parseArtifact(String coords) {
case 3 -> new DefaultArtifact(parts[0], parts[1], "*", "*", parts[2]);
case 4 -> new DefaultArtifact(parts[0], parts[1], "*", parts[2], parts[3]);
case 5 -> new DefaultArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]);
- default -> throw new IllegalArgumentException("Bad artifact coordinates " + coords
- + ", expected format is :[:[:]]:");};
+ default ->
+ throw new IllegalArgumentException("Bad artifact coordinates " + coords
+ + ", expected format is :[:[:]]:");
+ };
return s;
}
}
diff --git a/compat/maven-settings-builder/pom.xml b/compat/maven-settings-builder/pom.xml
index dac986044776..dc7806e20ac5 100644
--- a/compat/maven-settings-builder/pom.xml
+++ b/compat/maven-settings-builder/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-settings-builder
diff --git a/compat/maven-settings/pom.xml b/compat/maven-settings/pom.xml
index 253989512052..3a825e9ddc6d 100644
--- a/compat/maven-settings/pom.xml
+++ b/compat/maven-settings/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-settings
diff --git a/compat/maven-settings/src/site/apt/index.apt b/compat/maven-settings/src/site/apt/index.apt
index 1aed2a9b5d2a..bd84dd8036e1 100644
--- a/compat/maven-settings/src/site/apt/index.apt
+++ b/compat/maven-settings/src/site/apt/index.apt
@@ -26,7 +26,7 @@
Maven Settings Model
This is the model for Maven settings in <<>> package,
- delegating content to {{{../api/maven-api-settings/index.html}Maven 4 API immutable settings}}. All the effective model
+ delegating content to {{{../../api/maven-api-settings/index.html}Maven 4 API immutable settings}}. All the effective model
building logic from multiple settings files is done in {{{../maven-settings-builder/}Maven Settings Builder}}.
The following are generated from this model:
@@ -36,7 +36,7 @@ Maven Settings Model
* A {{{../../api/maven-api-settings/settings.html}Descriptor Reference}}
- * An {{{https://maven.apache.org/xsd/settings-2.0.0-rc-2.xsd}XSD}}
+ * An {{{https://maven.apache.org/xsd/settings-2.0.0.xsd}XSD}}
* See Also User Documentation
diff --git a/compat/maven-toolchain-builder/pom.xml b/compat/maven-toolchain-builder/pom.xml
index d1b4ada0fd15..635a58d7ea10 100644
--- a/compat/maven-toolchain-builder/pom.xml
+++ b/compat/maven-toolchain-builder/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-toolchain-builder
diff --git a/compat/maven-toolchain-model/pom.xml b/compat/maven-toolchain-model/pom.xml
index f905271c7c5e..8f309312c805 100644
--- a/compat/maven-toolchain-model/pom.xml
+++ b/compat/maven-toolchain-model/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-compat-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-toolchain-model
diff --git a/compat/maven-toolchain-model/src/site/apt/index.apt b/compat/maven-toolchain-model/src/site/apt/index.apt
index 81e22f7ce451..b3f74172aff4 100644
--- a/compat/maven-toolchain-model/src/site/apt/index.apt
+++ b/compat/maven-toolchain-model/src/site/apt/index.apt
@@ -26,7 +26,7 @@
Maven Toolchain Model
This is the model for Maven toolchain in <<>> package,
- delegating content to {{{../api/maven-api-toolchain/index.html}Maven 4 API immutable toolchain}}. All the effective model
+ delegating content to {{{../../api/maven-api-toolchain/index.html}Maven 4 API immutable toolchain}}. All the effective model
building logic from multiple toolchains files is done in {{{../maven-toolchain-builder/}Maven Toolchain Builder}}.
The following are generated from this model:
@@ -34,6 +34,6 @@ Maven Toolchain Model
* {{{./apidocs/index.html}Java sources}} with Reader and Writers for the Xpp3 XML parser, <<>> and <<>> transformers, and <<>> package
for Merger and v4 Reader and Writers for the Xpp3 XML parser,
- * A {{{./toolchains.html}Descriptor Reference}}
+ * A {{{../../api/maven-api-toolchain/toolchains.html}Descriptor Reference}}
- * An {{{https://maven.apache.org/xsd/toolchains-1.1.0.xsd}XSD}}
+ * An {{{https://maven.apache.org/xsd/toolchains-1.2.0.xsd}XSD}}
diff --git a/compat/pom.xml b/compat/pom.xml
index e70077ef4ff8..654d233ae1b7 100644
--- a/compat/pom.xml
+++ b/compat/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.maven
maven
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-compat-modules
@@ -45,4 +45,9 @@ under the License.
maven-toolchain-model
maven-toolchain-builder
+
+
+ compat
+
+
diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml
index 15e4ce5837f7..12926bda1ec7 100644
--- a/impl/maven-cli/pom.xml
+++ b/impl/maven-cli/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.maven
maven-impl-modules
- 4.0.0-rc-4-SNAPSHOT
+ 4.0.0-SNAPSHOT
maven-cli
@@ -160,8 +160,13 @@ under the License.
- org.jdom
- jdom2
+ eu.maveniverse.maven.domtrip
+ domtrip-core
+
+
+
+ eu.maveniverse.maven.domtrip
+ domtrip-maven
@@ -235,15 +240,14 @@ under the License.
jline-native
test
+
+ eu.maveniverse.maven.mimir
+ testing
+ test
+
-
-
- true
- src/test/resources-filtered
-
-
org.apache.maven.plugins
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java
index 89dd0e0c0e3f..61a65954a272 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java
@@ -76,6 +76,7 @@
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider;
import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager;
+import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory;
import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.WorkspaceReader;
@@ -270,6 +271,7 @@ protected Session newSession(
return new SimpleSession(mavenSession, getRepositorySystem(), repositories);
}
+ @SuppressWarnings("unchecked")
@Override
public T getService(Class clazz) throws NoSuchElementException {
if (clazz == ArtifactCoordinatesFactory.class) {
@@ -284,7 +286,9 @@ public T getService(Class clazz) throws NoSuchElementExce
return (T) new DefaultArtifactManager(this);
} else if (clazz == RepositoryFactory.class) {
return (T) new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager(
- new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()));
+ new DefaultUpdatePolicyAnalyzer(),
+ new DefaultChecksumPolicyProvider(),
+ new DefaultRepositoryKeyFunctionFactory()));
} else if (clazz == Interpolator.class) {
return (T) new DefaultInterpolator();
// } else if (clazz == ModelResolver.class) {
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
index 4e9724b59a46..4c9b6528ac36 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
@@ -183,6 +183,7 @@ public InvokerRequest parseInvocation(ParserRequest parserRequest) {
context.systemProperties::get));
}
+ // below we use effective properties as both system + user are present
// core extensions
try {
context.extensions = readCoreExtensionsDescriptor(context);
@@ -363,6 +364,7 @@ protected Map populateSystemProperties(LocalContext context) {
EnvironmentUtils.addEnvVars(systemProperties);
SystemProperties.addSystemProperties(systemProperties);
+ systemProperties.putAll(context.systemPropertiesOverrides);
// ----------------------------------------------------------------------
// Properties containing info about the currently running version of Maven
@@ -393,6 +395,31 @@ protected Map populateSystemProperties(LocalContext context) {
String mavenBuildVersion = CLIReportingUtils.createMavenVersionString(buildProperties);
systemProperties.setProperty(Constants.MAVEN_BUILD_VERSION, mavenBuildVersion);
+ Path mavenConf;
+ if (systemProperties.getProperty(Constants.MAVEN_INSTALLATION_CONF) != null) {
+ mavenConf = context.installationDirectory.resolve(
+ systemProperties.getProperty(Constants.MAVEN_INSTALLATION_CONF));
+ } else if (systemProperties.getProperty("maven.conf") != null) {
+ mavenConf = context.installationDirectory.resolve(systemProperties.getProperty("maven.conf"));
+ } else if (systemProperties.getProperty(Constants.MAVEN_HOME) != null) {
+ mavenConf = context.installationDirectory
+ .resolve(systemProperties.getProperty(Constants.MAVEN_HOME))
+ .resolve("conf");
+ } else {
+ mavenConf = context.installationDirectory.resolve("");
+ }
+
+ UnaryOperator callback = or(
+ context.extraInterpolationSource()::get,
+ context.systemPropertiesOverrides::get,
+ systemProperties::getProperty);
+ Path propertiesFile = mavenConf.resolve("maven-system.properties");
+ try {
+ MavenPropertiesLoader.loadProperties(systemProperties, propertiesFile, callback, false);
+ } catch (IOException e) {
+ throw new IllegalStateException("Error loading properties from " + propertiesFile, e);
+ }
+
Map result = toMap(systemProperties);
result.putAll(context.systemPropertiesOverrides);
return result;
@@ -408,8 +435,9 @@ protected Map populateUserProperties(LocalContext context) {
// are most dominant.
// ----------------------------------------------------------------------
- Map userSpecifiedProperties =
- new HashMap<>(context.options.userProperties().orElse(new HashMap<>()));
+ Map userSpecifiedProperties = context.options != null
+ ? new HashMap<>(context.options.userProperties().orElse(new HashMap<>()))
+ : new HashMap<>();
createInterpolator().interpolate(userSpecifiedProperties, paths::get);
// ----------------------------------------------------------------------
@@ -431,13 +459,16 @@ protected Map populateUserProperties(LocalContext context) {
} else {
mavenConf = context.installationDirectory.resolve("");
}
- Path propertiesFile = mavenConf.resolve("maven.properties");
+ Path propertiesFile = mavenConf.resolve("maven-user.properties");
try {
MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, callback, false);
} catch (IOException e) {
throw new IllegalStateException("Error loading properties from " + propertiesFile, e);
}
+ // Warn about deprecated maven.properties files
+ warnAboutDeprecatedPropertiesFiles(context);
+
// CLI specified properties are most dominant
userProperties.putAll(userSpecifiedProperties);
@@ -454,24 +485,26 @@ protected List readCoreExtensionsDescriptor(LocalContext context
Path file;
List loaded;
+ Map eff = new HashMap<>(context.systemProperties);
+ eff.putAll(context.userProperties);
+
// project
- file = context.cwd.resolve(context.userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS));
- loaded = readCoreExtensionsDescriptorFromFile(file);
+ file = context.cwd.resolve(eff.get(Constants.MAVEN_PROJECT_EXTENSIONS));
+ loaded = readCoreExtensionsDescriptorFromFile(file, false);
if (!loaded.isEmpty()) {
result.add(new CoreExtensions(file, loaded));
}
// user
- file = context.userHomeDirectory.resolve(context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS));
- loaded = readCoreExtensionsDescriptorFromFile(file);
+ file = context.userHomeDirectory.resolve(eff.get(Constants.MAVEN_USER_EXTENSIONS));
+ loaded = readCoreExtensionsDescriptorFromFile(file, true);
if (!loaded.isEmpty()) {
result.add(new CoreExtensions(file, loaded));
}
// installation
- file = context.installationDirectory.resolve(
- context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS));
- loaded = readCoreExtensionsDescriptorFromFile(file);
+ file = context.installationDirectory.resolve(eff.get(Constants.MAVEN_INSTALLATION_EXTENSIONS));
+ loaded = readCoreExtensionsDescriptorFromFile(file, true);
if (!loaded.isEmpty()) {
result.add(new CoreExtensions(file, loaded));
}
@@ -479,7 +512,7 @@ protected List readCoreExtensionsDescriptor(LocalContext context
return result.isEmpty() ? null : List.copyOf(result);
}
- protected List readCoreExtensionsDescriptorFromFile(Path extensionsFile) {
+ protected List readCoreExtensionsDescriptorFromFile(Path extensionsFile, boolean allowMetaVersions) {
try {
if (extensionsFile != null && Files.exists(extensionsFile)) {
try (InputStream is = Files.newInputStream(extensionsFile)) {
@@ -487,7 +520,8 @@ protected List readCoreExtensionsDescriptorFromFile(Path extensio
extensionsFile,
List.copyOf(new CoreExtensionsStaxReader()
.read(is, true, new InputSource(extensionsFile.toString()))
- .getExtensions()));
+ .getExtensions()),
+ allowMetaVersions);
}
}
return List.of();
@@ -497,23 +531,37 @@ protected List readCoreExtensionsDescriptorFromFile(Path extensio
}
protected List validateCoreExtensionsDescriptorFromFile(
- Path extensionFile, List coreExtensions) {
+ Path extensionFile, List coreExtensions, boolean allowMetaVersions) {
Map> gasLocations = new HashMap<>();
+ Map> metaVersionLocations = new HashMap<>();
for (CoreExtension coreExtension : coreExtensions) {
String ga = coreExtension.getGroupId() + ":" + coreExtension.getArtifactId();
InputLocation location = coreExtension.getLocation("");
gasLocations.computeIfAbsent(ga, k -> new ArrayList<>()).add(location);
+ // TODO: metaversions could be extensible enum with these two values out of the box
+ if ("LATEST".equals(coreExtension.getVersion()) || "RELEASE".equals(coreExtension.getVersion())) {
+ metaVersionLocations.computeIfAbsent(ga, k -> new ArrayList<>()).add(location);
+ }
}
- if (gasLocations.values().stream().noneMatch(l -> l.size() > 1)) {
- return coreExtensions;
+ if (gasLocations.values().stream().anyMatch(l -> l.size() > 1)) {
+ throw new IllegalStateException("Extension conflicts in file " + extensionFile + ": "
+ + gasLocations.entrySet().stream()
+ .map(e -> e.getKey() + " defined on lines "
+ + e.getValue().stream()
+ .map(l -> String.valueOf(l.getLineNumber()))
+ .collect(Collectors.joining(", ")))
+ .collect(Collectors.joining("; ")));
}
- throw new IllegalStateException("Extension conflicts in file " + extensionFile + ": "
- + gasLocations.entrySet().stream()
- .map(e -> e.getKey() + " defined on lines "
- + e.getValue().stream()
- .map(l -> String.valueOf(l.getLineNumber()))
- .collect(Collectors.joining(", ")))
- .collect(Collectors.joining("; ")));
+ if (!allowMetaVersions && !metaVersionLocations.isEmpty()) {
+ throw new IllegalStateException("Extension with illegal version in file " + extensionFile + ": "
+ + metaVersionLocations.entrySet().stream()
+ .map(e -> e.getKey() + " defined on lines "
+ + e.getValue().stream()
+ .map(l -> String.valueOf(l.getLineNumber()))
+ .collect(Collectors.joining(", ")))
+ .collect(Collectors.joining("; ")));
+ }
+ return coreExtensions;
}
@Nullable
@@ -530,4 +578,31 @@ protected CIInfo detectCI(LocalContext context) {
}
return detected.get(0);
}
+
+ private void warnAboutDeprecatedPropertiesFiles(LocalContext context) {
+ Map systemProperties = context.systemProperties;
+
+ // Check for deprecated ~/.m2/maven.properties
+ String userConfig = systemProperties.get("maven.user.conf");
+ Path userMavenProperties = userConfig != null ? Path.of(userConfig).resolve("maven.properties") : null;
+ if (userMavenProperties != null && Files.exists(userMavenProperties)) {
+ context.parserRequest
+ .logger()
+ .warn("Loading deprecated properties file: " + userMavenProperties + ". "
+ + "Please rename to 'maven-user.properties'. "
+ + "Support for 'maven.properties' will be removed in Maven 4.1.0.");
+ }
+
+ // Check for deprecated .mvn/maven.properties in project directory
+ String projectConfig = systemProperties.get("maven.project.conf");
+ Path projectMavenProperties =
+ projectConfig != null ? Path.of(projectConfig).resolve("maven.properties") : null;
+ if (projectMavenProperties != null && Files.exists(projectMavenProperties)) {
+ context.parserRequest
+ .logger()
+ .warn("Loading deprecated properties file: " + projectMavenProperties + ". "
+ + "Please rename to 'maven-user.properties'. "
+ + "Support for 'maven.properties' will be removed in Maven 4.1.0.");
+ }
+ }
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CliUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CliUtils.java
index 503ee85908a4..834f017b2e76 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CliUtils.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CliUtils.java
@@ -18,7 +18,6 @@
*/
package org.apache.maven.cling.invoker;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
@@ -60,11 +59,7 @@ public static String stripLeadingAndTrailingQuotes(String str) {
@Nonnull
public static Path getCanonicalPath(Path path) {
requireNonNull(path, "path");
- try {
- return path.toRealPath();
- } catch (IOException e) {
- return getCanonicalPath(path.getParent()).resolve(path.getFileName());
- }
+ return path.toAbsolutePath().normalize();
}
@Nonnull
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
index f78bcb0f5216..c417f24f40f0 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
@@ -279,7 +279,7 @@ public final Options interpolate(UnaryOperator callback) {
for (String arg : commandLine.getArgList()) {
commandLineBuilder.addArg(interpolator.interpolate(arg, callback));
}
- return copy(source, cliManager, commandLineBuilder.build());
+ return copy(source, cliManager, commandLineBuilder.get());
} catch (InterpolatorException e) {
throw new IllegalArgumentException("Could not interpolate CommonsCliOptions", e);
}
@@ -348,116 +348,116 @@ protected void prepareOptions(org.apache.commons.cli.Options options) {
options.addOption(Option.builder(HELP)
.longOpt("help")
.desc("Display help information")
- .build());
+ .get());
options.addOption(Option.builder(USER_PROPERTY)
.numberOfArgs(2)
.valueSeparator('=')
.desc("Define a user property")
- .build());
+ .get());
options.addOption(Option.builder(SHOW_VERSION_AND_EXIT)
.longOpt("version")
.desc("Display version information")
- .build());
+ .get());
options.addOption(Option.builder(QUIET)
.longOpt("quiet")
.desc("Quiet output - only show errors")
- .build());
+ .get());
options.addOption(Option.builder(VERBOSE)
.longOpt("verbose")
.desc("Produce execution verbose output")
- .build());
+ .get());
options.addOption(Option.builder(SHOW_ERRORS)
.longOpt("errors")
.desc("Produce execution error messages")
- .build());
+ .get());
options.addOption(Option.builder(BATCH_MODE)
.longOpt("batch-mode")
.desc("Run in non-interactive mode. Alias for --non-interactive (kept for backwards compatability)")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(NON_INTERACTIVE)
.desc("Run in non-interactive mode. Alias for --batch-mode")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(FORCE_INTERACTIVE)
.desc(
"Run in interactive mode. Overrides, if applicable, the CI environment variable and --non-interactive/--batch-mode options")
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_USER_SETTINGS)
.longOpt("settings")
.desc("Alternate path for the user settings file")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_PROJECT_SETTINGS)
.longOpt("project-settings")
.desc("Alternate path for the project settings file")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_INSTALLATION_SETTINGS)
.longOpt("install-settings")
.desc("Alternate path for the installation settings file")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_USER_TOOLCHAINS)
.longOpt("toolchains")
.desc("Alternate path for the user toolchains file")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_INSTALLATION_TOOLCHAINS)
.longOpt("install-toolchains")
.desc("Alternate path for the installation toolchains file")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(FAIL_ON_SEVERITY)
.longOpt("fail-on-severity")
.desc("Configure which severity of logging should cause the build to fail")
.hasArg()
- .build());
+ .get());
options.addOption(Option.builder(LOG_FILE)
.longOpt("log-file")
.hasArg()
.desc("Log file where all build output will go (disables output color)")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(RAW_STREAMS)
.desc("Do not decorate standard output and error streams")
- .build());
+ .get());
options.addOption(Option.builder(SHOW_VERSION)
.longOpt("show-version")
.desc("Display version information WITHOUT stopping build")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(COLOR)
.hasArg()
.optionalArg(true)
.desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.")
- .build());
+ .get());
options.addOption(Option.builder(OFFLINE)
.longOpt("offline")
.desc("Work offline")
- .build());
+ .get());
// Parameters handled by script
options.addOption(Option.builder()
.longOpt(DEBUG)
.desc("Launch the JVM in debug mode (script option).")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(ENC)
.desc("Launch the Maven Encryption tool (script option).")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(UPGRADE)
.desc("Launch the Maven Upgrade tool (script option).")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(SHELL)
.desc("Launch the Maven Shell tool (script option).")
- .build());
+ .get());
options.addOption(Option.builder()
.longOpt(YJP)
.desc("Launch the JVM with Yourkit profiler (script option).")
- .build());
+ .get());
// Deprecated
options.addOption(Option.builder(ALTERNATE_GLOBAL_SETTINGS)
@@ -469,7 +469,7 @@ protected void prepareOptions(org.apache.commons.cli.Options options) {
.setSince("4.0.0")
.setDescription("Use -is,--install-settings instead.")
.get())
- .build());
+ .get());
options.addOption(Option.builder(ALTERNATE_GLOBAL_TOOLCHAINS)
.longOpt("global-toolchains")
.desc(" Alternate path for the global toolchains file.")
@@ -479,7 +479,7 @@ protected void prepareOptions(org.apache.commons.cli.Options options) {
.setSince("4.0.0")
.setDescription("Use -it,--install-toolchains instead.")
.get())
- .build());
+ .get());
}
public CommandLine parse(String[] args) throws ParseException {
@@ -487,7 +487,7 @@ public CommandLine parse(String[] args) throws ParseException {
String[] cleanArgs = CleanArgument.cleanArgs(args);
DefaultParser parser = DefaultParser.builder()
.setDeprecatedHandler(this::addDeprecatedOption)
- .build();
+ .get();
CommandLine commandLine = parser.parse(options, cleanArgs);
// to trigger deprecation handler, so we can report deprecation BEFORE we actually use options
options.getOptions().forEach(commandLine::hasOption);
@@ -535,7 +535,7 @@ public void displayHelp(String command, Consumer pw) {
}
protected String commandLineSyntax(String command) {
- return command + " [options] [goals]";
+ return command + " [options] [ ...]";
}
}
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
index 9abe2e8ceb3d..5ec158321dbd 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
@@ -48,7 +48,6 @@
import org.apache.maven.api.cli.cisupport.CIInfo;
import org.apache.maven.api.cli.logging.AccumulatingLogger;
import org.apache.maven.api.services.BuilderProblem;
-import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.Lookup;
import org.apache.maven.api.services.MavenException;
import org.apache.maven.api.services.MessageBuilder;
@@ -150,6 +149,7 @@ protected int doInvoke(C context) throws Exception {
validate(context);
pushCoreProperties(context);
pushUserProperties(context);
+ setupGuiceClassLoading(context);
configureLogging(context);
createTerminal(context);
activateLogging(context);
@@ -248,13 +248,23 @@ protected void pushUserProperties(C context) throws Exception {
}
}
+ /**
+ * Sets up Guice class loading mode to CHILD, if not already set.
+ * Default Guice class loading mode uses a terminally deprecated JDK memory-access classes.
+ */
+ protected void setupGuiceClassLoading(C context) {
+ if (System.getProperty("guice_custom_class_loading", "").isBlank()) {
+ System.setProperty("guice_custom_class_loading", "CHILD");
+ }
+ }
+
protected void configureLogging(C context) throws Exception {
// LOG COLOR
- Map userProperties = context.protoSession.getUserProperties();
+ Map effectiveProperties = context.protoSession.getEffectiveProperties();
String styleColor = context.options()
.color()
- .orElse(userProperties.getOrDefault(
- Constants.MAVEN_STYLE_COLOR_PROPERTY, userProperties.getOrDefault("style.color", "auto")))
+ .orElse(effectiveProperties.getOrDefault(
+ Constants.MAVEN_STYLE_COLOR_PROPERTY, effectiveProperties.getOrDefault("style.color", "auto")))
.toLowerCase(Locale.ENGLISH);
if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) {
context.coloredOutput = true;
@@ -274,15 +284,17 @@ protected void configureLogging(C context) throws Exception {
context.loggerFactory = LoggerFactory.getILoggerFactory();
context.slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(context.loggerFactory);
- context.loggerLevel = Slf4jConfiguration.Level.INFO;
if (context.invokerRequest.effectiveVerbose()) {
context.loggerLevel = Slf4jConfiguration.Level.DEBUG;
+ context.slf4jConfiguration.setRootLoggerLevel(context.loggerLevel);
} else if (context.options().quiet().orElse(false)) {
context.loggerLevel = Slf4jConfiguration.Level.ERROR;
+ context.slf4jConfiguration.setRootLoggerLevel(context.loggerLevel);
+ } else {
+ // fall back to default log level specified in conf
+ // see https://issues.apache.org/jira/browse/MNG-2570 and https://github.com/apache/maven/issues/11199
+ context.loggerLevel = Slf4jConfiguration.Level.INFO; // default for display purposes
}
- context.slf4jConfiguration.setRootLoggerLevel(context.loggerLevel);
- // else fall back to default log level specified in conf
- // see https://issues.apache.org/jira/browse/MNG-2570
}
protected BuildEventListener determineBuildEventListener(C context) {
@@ -330,7 +342,7 @@ protected void doCreateTerminal(C context, TerminalBuilder builder) {
context.coloredOutput = context.coloredOutput != null ? context.coloredOutput : false;
context.closeables.add(out::flush);
} else {
- builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut);
+ builder.systemOutput(TerminalBuilder.SystemOutput.SysOut);
}
if (context.coloredOutput != null) {
builder.color(context.coloredOutput);
@@ -342,12 +354,10 @@ protected void doCreateTerminal(C context, TerminalBuilder builder) {
*/
protected final void doConfigureWithTerminal(C context, Terminal terminal) {
context.terminal = terminal;
- // tricky thing: align what JLine3 detected and Maven thinks:
+ // Align Maven's color setting with JLine's terminal detection:
// if embedded, we default to context.coloredOutput=false unless overridden (see above)
- // if not embedded, JLine3 may detect redirection and will create dumb terminal.
+ // if not embedded, JLine detects redirection via SysOut and will create dumb terminal.
// To align Maven with outcomes, we set here color enabled based on these premises.
- // Note: Maven3 suffers from similar thing: if you do `mvn3 foo > log.txt`, the output will
- // not be not colored (good), but Maven will print out "Message scheme: color".
MessageUtils.setColorEnabled(
context.coloredOutput != null ? context.coloredOutput : !Terminal.TYPE_DUMB.equals(terminal.getType()));
@@ -362,7 +372,15 @@ protected final void doConfigureWithTerminal(C context, Terminal terminal) {
/**
* Override this method to add some special handling for "raw streams" enabled option.
*/
- protected void doConfigureWithTerminalWithRawStreamsEnabled(C context) {}
+ protected void doConfigureWithTerminalWithRawStreamsEnabled(C context) {
+ context.invokerRequest.stdIn().ifPresent(System::setIn);
+ context.invokerRequest
+ .stdOut()
+ .ifPresent(out -> System.setOut(out instanceof PrintStream pw ? pw : new PrintStream(out, true)));
+ context.invokerRequest
+ .stdErr()
+ .ifPresent(err -> System.setErr(err instanceof PrintStream pw ? pw : new PrintStream(err, true)));
+ }
/**
* Override this method to add some special handling for "raw streams" disabled option.
@@ -418,9 +436,10 @@ protected void activateLogging(C context) throws Exception {
switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) {
case "warn", "warning" -> LogLevelRecorder.Level.WARN;
case "error" -> LogLevelRecorder.Level.ERROR;
- default -> throw new IllegalArgumentException(
- logLevelThreshold
- + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.");
+ default ->
+ throw new IllegalArgumentException(
+ logLevelThreshold
+ + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.");
};
recorder.setMaxLevelAllowed(level);
context.logger.info("Enabled to break the build on log level " + logLevelThreshold + ".");
@@ -592,7 +611,7 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
}
} else {
String userSettingsFileStr =
- context.protoSession.getUserProperties().get(Constants.MAVEN_USER_SETTINGS);
+ context.protoSession.getEffectiveProperties().get(Constants.MAVEN_USER_SETTINGS);
if (userSettingsFileStr != null) {
userSettingsFile =
context.userDirectory.resolve(userSettingsFileStr).normalize();
@@ -610,7 +629,7 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
}
} else {
String projectSettingsFileStr =
- context.protoSession.getUserProperties().get(Constants.MAVEN_PROJECT_SETTINGS);
+ context.protoSession.getEffectiveProperties().get(Constants.MAVEN_PROJECT_SETTINGS);
if (projectSettingsFileStr != null) {
projectSettingsFile = context.cwd.resolve(projectSettingsFileStr);
}
@@ -627,7 +646,7 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
}
} else {
String installationSettingsFileStr =
- context.protoSession.getUserProperties().get(Constants.MAVEN_INSTALLATION_SETTINGS);
+ context.protoSession.getEffectiveProperties().get(Constants.MAVEN_INSTALLATION_SETTINGS);
if (installationSettingsFileStr != null) {
installationSettingsFile = context.installationDirectory
.resolve(installationSettingsFileStr)
@@ -639,8 +658,7 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
context.projectSettingsPath = projectSettingsFile;
context.userSettingsPath = userSettingsFile;
- UnaryOperator interpolationSource = Interpolator.chain(
- context.protoSession.getUserProperties()::get, context.protoSession.getSystemProperties()::get);
+ UnaryOperator interpolationSource = context.protoSession.getEffectiveProperties()::get;
SettingsBuilderRequest settingsRequest = SettingsBuilderRequest.builder()
.session(context.protoSession)
.installationSettingsSource(
@@ -726,14 +744,15 @@ protected boolean mayDisableInteractiveMode(C context, boolean proposedInteracti
protected Path localRepositoryPath(C context) {
// user override
- String userDefinedLocalRepo = context.protoSession.getUserProperties().get(Constants.MAVEN_REPO_LOCAL);
+ String userDefinedLocalRepo =
+ context.protoSession.getEffectiveProperties().get(Constants.MAVEN_REPO_LOCAL);
if (userDefinedLocalRepo == null) {
- userDefinedLocalRepo = context.protoSession.getUserProperties().get(Constants.MAVEN_REPO_LOCAL);
+ userDefinedLocalRepo = context.protoSession.getEffectiveProperties().get(Constants.MAVEN_REPO_LOCAL);
if (userDefinedLocalRepo != null) {
context.logger.warn("The property '" + Constants.MAVEN_REPO_LOCAL
+ "' has been set using a JVM system property which is deprecated. "
+ "The property can be passed as a Maven argument or in the Maven project configuration file,"
- + "usually located at ${session.rootDirectory}/.mvn/maven.properties.");
+ + "usually located at ${session.rootDirectory}/.mvn/maven-user.properties.");
}
}
if (userDefinedLocalRepo != null) {
@@ -746,7 +765,7 @@ protected Path localRepositoryPath(C context) {
}
// defaults
return context.userDirectory
- .resolve(context.protoSession.getUserProperties().get(Constants.MAVEN_USER_CONF))
+ .resolve(context.protoSession.getEffectiveProperties().get(Constants.MAVEN_USER_CONF))
.resolve("repository")
.normalize();
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java
index 04b8ba893387..f1290b02e574 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java
@@ -120,13 +120,7 @@ protected DefaultPlexusContainer container(
container.setLoggerManager(createLoggerManager());
ProtoSession protoSession = context.protoSession;
- UnaryOperator extensionSource = expression -> {
- String value = protoSession.getUserProperties().get(expression);
- if (value == null) {
- value = protoSession.getSystemProperties().get(expression);
- }
- return value;
- };
+ UnaryOperator extensionSource = protoSession.getEffectiveProperties()::get;
List failures = new ArrayList<>();
for (LoadedCoreExtension extension : loadedExtensions) {
container.discoverComponents(
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java
index 6edfde98eee4..590529d5f193 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import org.apache.maven.api.cli.CoreExtensions;
@@ -60,7 +61,7 @@ protected List selectCoreExtensions(C context, List args) {
}
protected MavenOptions parseMavenAtFileOptions(Path atFile) {
- try (Stream lines = Files.lines(atFile, Charset.defaultCharset())) {
+ try (Stream lines = Files.lines(atFile, StandardCharsets.UTF_8)) {
List args =
lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#")).toList();
return parseArgs("atFile", args);
@@ -77,7 +77,7 @@ protected MavenOptions parseMavenAtFileOptions(Path atFile) {
}
protected MavenOptions parseMavenConfigOptions(Path configFile) {
- try (Stream lines = Files.lines(configFile, Charset.defaultCharset())) {
+ try (Stream lines = Files.lines(configFile, StandardCharsets.UTF_8)) {
List args =
lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#")).toList();
MavenOptions options = parseArgs("maven.config", args);
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java
index 34d2a6932356..dfe857e2443a 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java
@@ -86,6 +86,7 @@ protected MavenContext copyIfDifferent(MavenContext mavenContext, InvokerRequest
shadow.containerCapsule = mavenContext.containerCapsule;
shadow.lookup = mavenContext.lookup;
shadow.eventSpyDispatcher = mavenContext.eventSpyDispatcher;
+ shadow.simplexTransferListener = mavenContext.simplexTransferListener;
shadow.maven = mavenContext.maven;
return shadow;
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
index 495d6373dee0..e24caa2495fc 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
@@ -96,11 +96,11 @@ protected void prepareOptions(org.apache.commons.cli.Options options) {
options.addOption(Option.builder(FORCE)
.longOpt("force")
.desc("Should overwrite without asking any configuration?")
- .build());
+ .get());
options.addOption(Option.builder(YES)
.longOpt("yes")
.desc("Should imply user answered \"yes\" to all incoming questions?")
- .build());
+ .get());
}
}
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java
index 01c22ed1eb75..6dbc69d654b7 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java
@@ -89,8 +89,7 @@ protected int execute(LookupContext context) throws Exception {
DefaultParser parser = new DefaultParser();
parser.setRegexCommand("[:]{0,1}[a-zA-Z!]{1,}\\S*"); // change default regex to support shell commands
- String banner =
- """
+ String banner = """
░▒▓██████████████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓███████▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░\s
░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░\s
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/CommonsCliUpgradeOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/CommonsCliUpgradeOptions.java
index 0e5621c2889a..29025cce9a33 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/CommonsCliUpgradeOptions.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/CommonsCliUpgradeOptions.java
@@ -44,24 +44,6 @@ protected CommonsCliUpgradeOptions(String source, CLIManager cliManager, Command
super(source, cliManager, commandLine);
}
- @Override
- @Nonnull
- public Optional force() {
- if (commandLine.hasOption(CLIManager.FORCE)) {
- return Optional.of(Boolean.TRUE);
- }
- return Optional.empty();
- }
-
- @Override
- @Nonnull
- public Optional yes() {
- if (commandLine.hasOption(CLIManager.YES)) {
- return Optional.of(Boolean.TRUE);
- }
- return Optional.empty();
- }
-
@Override
@Nonnull
public Optional> goals() {
@@ -143,10 +125,8 @@ public void displayHelp(ParserRequest request, Consumer printStream) {
printStream.accept(" --plugins Upgrade plugins known to fail with Maven 4");
printStream.accept(
" -a, --all Apply all upgrades (equivalent to --model-version 4.1.0 --infer --model --plugins)");
- printStream.accept(" -f, --force Overwrite files without asking for confirmation");
- printStream.accept(" -y, --yes Answer \"yes\" to all prompts automatically");
printStream.accept("");
- printStream.accept("Default behavior: --model and --plugins are applied if no other options are specified");
+ printStream.accept("Default behavior: --model --plugins --infer are applied if no other options are specified");
printStream.accept("");
}
@@ -157,8 +137,6 @@ protected CommonsCliUpgradeOptions copy(
}
protected static class CLIManager extends CommonsCliOptions.CLIManager {
- public static final String FORCE = "f";
- public static final String YES = "y";
public static final String MODEL_VERSION = "m";
public static final String DIRECTORY = "d";
public static final String INFER = "i";
@@ -169,42 +147,34 @@ protected static class CLIManager extends CommonsCliOptions.CLIManager {
@Override
protected void prepareOptions(org.apache.commons.cli.Options options) {
super.prepareOptions(options);
- options.addOption(Option.builder(FORCE)
- .longOpt("force")
- .desc("Should overwrite without asking any configuration?")
- .build());
- options.addOption(Option.builder(YES)
- .longOpt("yes")
- .desc("Should imply user answered \"yes\" to all incoming questions?")
- .build());
options.addOption(Option.builder(MODEL_VERSION)
.longOpt("model-version")
.hasArg()
.argName("version")
.desc("Target POM model version (4.0.0 or 4.1.0)")
- .build());
+ .get());
options.addOption(Option.builder(DIRECTORY)
.longOpt("directory")
.hasArg()
.argName("path")
.desc("Directory to use as starting point for POM discovery")
- .build());
+ .get());
options.addOption(Option.builder(INFER)
.longOpt("infer")
.desc("Use inference when upgrading (remove redundant information)")
- .build());
+ .get());
options.addOption(Option.builder(MODEL)
.longOpt("model")
.desc("Fix Maven 4 compatibility issues in POM files")
- .build());
+ .get());
options.addOption(Option.builder(PLUGINS)
.longOpt("plugins")
.desc("Upgrade plugins known to fail with Maven 4 to their minimum compatible versions")
- .build());
+ .get());
options.addOption(Option.builder(ALL)
.longOpt("all")
.desc("Apply all upgrades (equivalent to --model-version 4.1.0 --infer --model --plugins)")
- .build());
+ .get());
}
}
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/ConsoleIcon.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/ConsoleIcon.java
new file mode 100644
index 000000000000..1066c04f81a5
--- /dev/null
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/ConsoleIcon.java
@@ -0,0 +1,108 @@
+/*
+ * 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.cling.invoker.mvnup;
+
+import java.nio.charset.Charset;
+
+import org.jline.terminal.Terminal;
+
+/**
+ * Console icons for Maven upgrade tool output.
+ * Each icon has a Unicode character and an ASCII fallback.
+ * The appropriate representation is chosen based on the terminal's charset capabilities.
+ */
+public enum ConsoleIcon {
+ /**
+ * Success/completion icon.
+ */
+ SUCCESS('✓', "[OK]"),
+
+ /**
+ * Error/failure icon.
+ */
+ ERROR('✗', "[ERROR]"),
+
+ /**
+ * Warning icon.
+ */
+ WARNING('âš ', "[WARNING]"),
+
+ /**
+ * Detail/bullet point icon.
+ */
+ DETAIL('•', "-"),
+
+ /**
+ * Action/arrow icon.
+ */
+ ACTION('→', ">");
+
+ private final char unicodeChar;
+ private final String asciiFallback;
+
+ ConsoleIcon(char unicodeChar, String asciiFallback) {
+ this.unicodeChar = unicodeChar;
+ this.asciiFallback = asciiFallback;
+ }
+
+ /**
+ * Returns the appropriate icon representation for the given terminal.
+ * Tests if the terminal's charset can encode the Unicode character,
+ * falling back to ASCII if not.
+ *
+ * @param terminal the terminal to get the icon for
+ * @return the Unicode character if supported, otherwise the ASCII fallback
+ */
+ public String getIcon(Terminal terminal) {
+ Charset charset = getTerminalCharset(terminal);
+ return charset.newEncoder().canEncode(unicodeChar) ? String.valueOf(unicodeChar) : asciiFallback;
+ }
+
+ /**
+ * Gets the charset used by the terminal for output.
+ * Falls back to the system default charset if terminal charset is not available.
+ *
+ * @param terminal the terminal to get the charset from
+ * @return the terminal's output charset or the system default charset
+ */
+ private static Charset getTerminalCharset(Terminal terminal) {
+ if (terminal != null && terminal.encoding() != null) {
+ return terminal.encoding();
+ }
+ return Charset.defaultCharset();
+ }
+
+ /**
+ * Returns the Unicode character for this icon.
+ *
+ * @return the Unicode character
+ */
+ public char getUnicodeChar() {
+ return unicodeChar;
+ }
+
+ /**
+ * Returns the ASCII fallback text for this icon.
+ *
+ * @return the ASCII fallback text
+ */
+ public String getAsciiFallback() {
+ return asciiFallback;
+ }
+}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/UpgradeContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/UpgradeContext.java
index bef4e344fd2c..27e231077fc4 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/UpgradeContext.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/UpgradeContext.java
@@ -30,7 +30,7 @@
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Indentation;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Indentation;
@SuppressWarnings("VisibilityModifier")
public class UpgradeContext extends LookupContext {
@@ -118,35 +118,35 @@ public void println() {
* Logs a successful operation with a checkmark icon.
*/
public void success(String message) {
- logger.info(getCurrentIndent() + "✓ " + message);
+ logger.info(getCurrentIndent() + ConsoleIcon.SUCCESS.getIcon(terminal) + " " + message);
}
/**
* Logs an error with an X icon.
*/
public void failure(String message) {
- logger.error(getCurrentIndent() + "✗ " + message);
+ logger.error(getCurrentIndent() + ConsoleIcon.ERROR.getIcon(terminal) + " " + message);
}
/**
* Logs a warning with a warning icon.
*/
public void warning(String message) {
- logger.warn(getCurrentIndent() + "âš " + message);
+ logger.warn(getCurrentIndent() + ConsoleIcon.WARNING.getIcon(terminal) + " " + message);
}
/**
* Logs detailed information with a bullet point.
*/
public void detail(String message) {
- logger.info(getCurrentIndent() + "• " + message);
+ logger.info(getCurrentIndent() + ConsoleIcon.DETAIL.getIcon(terminal) + " " + message);
}
/**
* Logs a performed action with an arrow icon.
*/
public void action(String message) {
- logger.info(getCurrentIndent() + "→ " + message);
+ logger.info(getCurrentIndent() + ConsoleIcon.ACTION.getIcon(terminal) + " " + message);
}
/**
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
index dfd14967cc1c..0dc73c968c18 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeGoal.java
@@ -18,26 +18,26 @@
*/
package org.apache.maven.cling.invoker.mvnup.goals;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.DomTripException;
+import eu.maveniverse.domtrip.Element;
+import eu.maveniverse.domtrip.Parser;
+import eu.maveniverse.domtrip.maven.MavenPomElements;
import org.apache.maven.api.cli.mvnup.UpgradeOptions;
import org.apache.maven.api.di.Inject;
import org.apache.maven.cling.invoker.mvnup.Goal;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
-import org.jdom2.Document;
-import org.jdom2.JDOMException;
-import org.jdom2.output.Format;
-import org.jdom2.output.XMLOutputter;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Files.MVN_DIRECTORY;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_1_0;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Files.MVN_DIRECTORY;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.ModelVersions.MODEL_VERSION_4_1_0;
/**
* Base class for upgrade goals containing shared functionality.
@@ -160,7 +160,7 @@ public int execute(UpgradeContext context) throws Exception {
} else if (options.all().orElse(false)) {
targetModel = MODEL_VERSION_4_1_0;
} else {
- targetModel = UpgradeConstants.ModelVersions.MODEL_VERSION_4_0_0;
+ targetModel = MavenPomElements.ModelVersions.MODEL_VERSION_4_0_0;
}
if (!ModelVersionUtils.isValidModelVersion(targetModel)) {
@@ -176,7 +176,7 @@ public int execute(UpgradeContext context) throws Exception {
Map pomMap;
try {
pomMap = PomDiscovery.discoverPoms(startingDirectory);
- } catch (IOException | JDOMException e) {
+ } catch (IOException | DomTripException e) {
context.failure("Failed to discover POM files: " + e.getMessage());
return 1;
}
@@ -208,12 +208,14 @@ protected int doUpgrade(UpgradeContext context, String targetModel, Map pomMap) {
context.info("");
@@ -237,27 +239,16 @@ protected void saveModifications(UpgradeContext context, Map pom
Path pomPath = entry.getKey();
Document document = entry.getValue();
try {
- String content = Files.readString(entry.getKey(), StandardCharsets.UTF_8);
- int startIndex = content.indexOf("<" + document.getRootElement().getName());
- String head = startIndex >= 0 ? content.substring(0, startIndex) : "";
- String lastTag = document.getRootElement().getName() + ">";
- int endIndex = content.lastIndexOf(lastTag);
- String tail = endIndex >= 0 ? content.substring(endIndex + lastTag.length()) : "";
- Format format = Format.getRawFormat();
- format.setLineSeparator(System.lineSeparator());
- XMLOutputter out = new XMLOutputter(format);
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- try (OutputStream outputStream = output) {
- outputStream.write(head.getBytes(StandardCharsets.UTF_8));
- out.output(document.getRootElement(), outputStream);
- outputStream.write(tail.getBytes(StandardCharsets.UTF_8));
- }
- String newBody = output.toString(StandardCharsets.UTF_8);
- Files.writeString(pomPath, newBody, StandardCharsets.UTF_8);
+ // Use domtrip for perfect formatting preservation
+ String xmlContent = DomUtils.toXml(document);
+ Files.writeString(pomPath, xmlContent);
+ context.detail("Saved: " + pomPath);
} catch (Exception e) {
context.failure("Failed to save " + pomPath + ": " + e.getMessage());
}
}
+
+ context.success("All modifications saved successfully");
}
/**
@@ -288,4 +279,101 @@ protected void createMvnDirectoryIfNeeded(UpgradeContext context) {
context.failure("Failed to create .mvn directory: " + e.getMessage());
}
}
+
+ /**
+ * Fixes incompatible extensions in .mvn/extensions.xml for Maven 4 compatibility.
+ *
+ *
+ * os-maven-plugin : Replaced with Maveniverse Nisse extension
+ * (compatible with both Maven 3 and 4). Also adds {@code -Dnisse.compat.osDetector}
+ * to {@code .mvn/maven.config} for drop-in compatibility.
+ * Develocity/Gradle Enterprise extension : Removed because it depends
+ * on {@code org.slf4j.impl.SimpleLogger} which is not available in Maven 4.
+ *
+ */
+ protected void fixIncompatibleExtensions(UpgradeContext context) {
+ Path startingDirectory = context.options().directory().map(Paths::get).orElse(context.invokerRequest.cwd());
+ Path extensionsXml = startingDirectory.resolve(MVN_DIRECTORY).resolve("extensions.xml");
+
+ if (!Files.exists(extensionsXml)) {
+ return;
+ }
+
+ context.info("");
+ context.info("Checking .mvn/extensions.xml for Maven 4 incompatible extensions...");
+ context.indent();
+
+ try {
+ String content = Files.readString(extensionsXml);
+ Document doc = new Parser().parse(content);
+ Element root = doc.root();
+ boolean modified = false;
+ boolean needsNisseCompat = false;
+
+ List extensions = root.childElements("extension").toList();
+ List toRemove = new ArrayList<>();
+
+ for (Element ext : extensions) {
+ String groupId = ext.childTextTrimmed("groupId");
+ String artifactId = ext.childTextTrimmed("artifactId");
+
+ if ("kr.motd.maven".equals(groupId) && "os-maven-plugin".equals(artifactId)) {
+ DomUtils.updateOrCreateChildElement(ext, "groupId", "eu.maveniverse.maven.nisse");
+ DomUtils.updateOrCreateChildElement(ext, "artifactId", "extension");
+ DomUtils.updateOrCreateChildElement(ext, "version", "0.4.4");
+ context.detail(
+ "Replaced kr.motd.maven:os-maven-plugin with eu.maveniverse.maven.nisse:extension:0.4.4");
+ modified = true;
+ needsNisseCompat = true;
+ } else if ("com.gradle".equals(groupId)
+ && ("develocity-maven-extension".equals(artifactId)
+ || "gradle-enterprise-maven-extension".equals(artifactId))) {
+ toRemove.add(ext);
+ context.detail("Removed incompatible extension: " + groupId + ":" + artifactId);
+ modified = true;
+ }
+ }
+
+ for (Element ext : toRemove) {
+ DomUtils.removeElement(ext);
+ }
+
+ if (modified) {
+ if (shouldSaveModifications()) {
+ String modifiedXml = DomUtils.toXml(doc);
+ Files.writeString(extensionsXml, modifiedXml);
+ context.success("Updated .mvn/extensions.xml");
+
+ if (needsNisseCompat) {
+ addNisseCompatFlag(startingDirectory, context);
+ }
+ } else {
+ context.action("Would update .mvn/extensions.xml");
+ if (needsNisseCompat) {
+ context.action("Would add -Dnisse.compat.osDetector to .mvn/maven.config");
+ }
+ }
+ } else {
+ context.success("No incompatible extensions found");
+ }
+ } catch (Exception e) {
+ context.failure("Failed to process .mvn/extensions.xml: " + e.getMessage());
+ } finally {
+ context.unindent();
+ }
+ }
+
+ private void addNisseCompatFlag(Path startingDirectory, UpgradeContext context) {
+ Path mavenConfig = startingDirectory.resolve(MVN_DIRECTORY).resolve("maven.config");
+ try {
+ String configContent = Files.exists(mavenConfig) ? Files.readString(mavenConfig) : "";
+ if (!configContent.contains("-Dnisse.compat.osDetector")) {
+ String separator = configContent.isEmpty() || configContent.endsWith("\n") ? "" : "\n";
+ Files.writeString(mavenConfig, configContent + separator + "-Dnisse.compat.osDetector\n");
+ context.success("Added -Dnisse.compat.osDetector to .mvn/maven.config");
+ }
+ } catch (IOException e) {
+ context.failure("Failed to update .mvn/maven.config: " + e.getMessage());
+ }
+ }
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeStrategy.java
index dde29c29d149..3828e3f03ae6 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeStrategy.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/AbstractUpgradeStrategy.java
@@ -18,20 +18,60 @@
*/
package org.apache.maven.cling.invoker.mvnup.goals;
+import java.io.File;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.Element;
+import eu.maveniverse.domtrip.maven.Coordinates;
+import eu.maveniverse.domtrip.maven.MavenPomElements;
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.Session;
import org.apache.maven.api.cli.mvnup.UpgradeOptions;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Provides;
+import org.apache.maven.api.model.Repository;
+import org.apache.maven.api.model.RepositoryPolicy;
+import org.apache.maven.api.services.ModelBuilder;
+import org.apache.maven.api.services.ModelBuilderRequest;
+import org.apache.maven.api.services.ModelBuilderResult;
+import org.apache.maven.api.services.RepositoryFactory;
+import org.apache.maven.api.services.Sources;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
-import org.jdom2.Document;
+import org.apache.maven.impl.standalone.ApiRunner;
+import org.codehaus.plexus.components.secdispatcher.Dispatcher;
+import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
+import org.eclipse.aether.spi.io.PathProcessor;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.jdk.JdkTransporterFactory;
+
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PARENT;
/**
* Abstract base class for upgrade strategies that provides common functionality
* and reduces code duplication across strategy implementations.
+ *
+ * Strategies work with domtrip Documents for perfect formatting preservation.
+ * Subclasses can create domtrip Editors from Documents as needed:
+ *
+ * Editor editor = new Editor(document);
+ * // ... perform domtrip operations ...
+ * // Document is automatically updated
+ *
*/
public abstract class AbstractUpgradeStrategy implements UpgradeStrategy {
+ private Session session;
+
/**
* Template method that handles common logging and error handling.
* Subclasses implement the actual upgrade logic in doApply().
@@ -92,4 +132,193 @@ protected void logSummary(UpgradeContext context, UpgradeResult result) {
}
context.unindent();
}
+
+ /**
+ * Extracts an Artifact from a POM document with parent resolution.
+ * If groupId or version are missing, attempts to resolve from parent.
+ *
+ * This method handles Maven's inheritance mechanism where groupId and version
+ * can be inherited from the parent POM.
+ *
+ * @param context the upgrade context for logging
+ * @param pomDocument the POM document
+ * @return the Artifact or null if it cannot be determined
+ */
+ public static Coordinates extractArtifactCoordinatesWithParentResolution(
+ UpgradeContext context, Document pomDocument) {
+ Element root = pomDocument.root();
+
+ // Extract direct values
+ String groupId = root.childTextTrimmed(MavenPomElements.Elements.GROUP_ID);
+ String artifactId = root.childTextTrimmed(MavenPomElements.Elements.ARTIFACT_ID);
+ String version = root.childTextTrimmed(MavenPomElements.Elements.VERSION);
+
+ // If groupId or version is missing, try to get from parent
+ if (groupId == null || version == null) {
+ Element parentElement = root.childElement(PARENT).orElse(null);
+ if (parentElement != null) {
+ if (groupId == null) {
+ groupId = parentElement.childTextTrimmed(MavenPomElements.Elements.GROUP_ID);
+ }
+ if (version == null) {
+ version = parentElement.childTextTrimmed(MavenPomElements.Elements.VERSION);
+ }
+ }
+ }
+
+ // ArtifactId is required and cannot be inherited
+ if (artifactId == null || artifactId.isEmpty()) {
+ context.debug("Cannot determine artifactId for POM");
+ return null;
+ }
+
+ // GroupId and version can be inherited, but if still null, we can't create a valid Artifact
+ if (groupId == null || groupId.isEmpty() || version == null || version.isEmpty()) {
+ context.debug("Cannot determine complete GAV for artifactId: " + artifactId);
+ return null;
+ }
+
+ return Coordinates.of(groupId, artifactId, version);
+ }
+
+ /**
+ * Computes all artifacts from all POMs in a multi-module project.
+ * This includes resolving parent inheritance.
+ *
+ * @param context the upgrade context for logging
+ * @param pomMap map of all POM files in the project
+ * @return set of all Artifacts in the project
+ */
+ public static Set computeAllArtifactCoordinates(UpgradeContext context, Map pomMap) {
+ Map coordinatesByGAV = new HashMap<>();
+
+ context.info("Computing artifacts for inference from " + pomMap.size() + " POM(s)...");
+
+ // Extract artifact from all POMs in the project
+ for (Map.Entry entry : pomMap.entrySet()) {
+ Path pomPath = entry.getKey();
+ Document pomDocument = entry.getValue();
+
+ Coordinates coordinate =
+ AbstractUpgradeStrategy.extractArtifactCoordinatesWithParentResolution(context, pomDocument);
+ if (coordinate != null) {
+ coordinatesByGAV.putIfAbsent(coordinate.toGAV(), coordinate);
+ context.debug("Found artifact: " + coordinate.toGAV() + " from " + pomPath);
+ }
+ }
+
+ context.info("Computed " + coordinatesByGAV.size() + " unique artifact(s) for inference");
+ return new HashSet<>(coordinatesByGAV.values());
+ }
+
+ protected Session getSession() {
+ if (session == null) {
+ session = createMaven4Session();
+ }
+ return session;
+ }
+
+ private Session createMaven4Session() {
+ Session session = ApiRunner.createSession(injector -> {
+ injector.bindInstance(Dispatcher.class, new LegacyDispatcher());
+ injector.bindImplicit(TransporterFactoryConfig.class);
+ });
+
+ // TODO: we should read settings
+ RemoteRepository central =
+ session.createRemoteRepository(RemoteRepository.CENTRAL_ID, "https://repo.maven.apache.org/maven2");
+ RemoteRepository snapshots = session.getService(RepositoryFactory.class)
+ .createRemote(Repository.newBuilder()
+ .id("apache-snapshots")
+ .url("https://repository.apache.org/content/repositories/snapshots/")
+ .releases(RepositoryPolicy.newBuilder().enabled("false").build())
+ .snapshots(RepositoryPolicy.newBuilder().enabled("true").build())
+ .build());
+
+ return session.withRemoteRepositories(List.of(central, snapshots));
+ }
+
+ protected Path createTempProjectStructure(UpgradeContext context, Map pomMap) throws Exception {
+ Path tempDir = Files.createTempDirectory("mvnup-project-");
+ context.debug("Created temp project directory: " + tempDir);
+
+ Path commonRoot = findCommonRoot(pomMap.keySet());
+ context.debug("Common root: " + commonRoot);
+
+ for (Map.Entry entry : pomMap.entrySet()) {
+ Path originalPath = entry.getKey();
+ Document document = entry.getValue();
+
+ Path relativePath = commonRoot.relativize(originalPath);
+ Path tempPomPath = tempDir.resolve(relativePath);
+
+ Files.createDirectories(tempPomPath.getParent());
+ Files.writeString(tempPomPath, document.toXml());
+ context.debug("Wrote POM to temp location: " + tempPomPath);
+ }
+
+ return tempDir;
+ }
+
+ protected Path findCommonRoot(Set pomPaths) {
+ Path commonRoot = null;
+ for (Path pomPath : pomPaths) {
+ Path parent = pomPath.getParent();
+ if (parent == null) {
+ parent = Path.of(".");
+ }
+ if (commonRoot == null) {
+ commonRoot = parent;
+ } else {
+ while (!parent.startsWith(commonRoot)) {
+ commonRoot = commonRoot.getParent();
+ if (commonRoot == null) {
+ break;
+ }
+ }
+ }
+ }
+ return commonRoot;
+ }
+
+ protected void cleanupTempDirectory(Path tempDir) {
+ try {
+ Files.walk(tempDir)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ } catch (Exception e) {
+ // Best effort cleanup
+ }
+ }
+
+ protected org.apache.maven.api.model.Model buildEffectiveModel(Path pomPath) {
+ Session session = getSession();
+ ModelBuilder modelBuilder = session.getService(ModelBuilder.class);
+
+ ModelBuilderRequest request = ModelBuilderRequest.builder()
+ .session(session)
+ .source(Sources.buildSource(pomPath))
+ .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
+ .recursive(false)
+ .build();
+
+ ModelBuilderResult result = modelBuilder.newSession().build(request);
+ return result.getEffectiveModel();
+ }
+
+ static class TransporterFactoryConfig {
+ @Provides
+ @Named(JdkTransporterFactory.NAME)
+ static TransporterFactory jdkTransporterFactory(
+ ChecksumExtractor checksumExtractor, PathProcessor pathProcessor) {
+ return new JdkTransporterFactory(checksumExtractor, pathProcessor);
+ }
+
+ @Provides
+ @Named(FileTransporterFactory.NAME)
+ static TransporterFactory fileTransporterFactory() {
+ return new FileTransporterFactory();
+ }
+ }
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java
index 426981a09a51..6b9b0c8e39c4 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java
@@ -19,55 +19,52 @@
package org.apache.maven.cling.invoker.mvnup.goals;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import eu.maveniverse.domtrip.Comment;
+import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.Editor;
+import eu.maveniverse.domtrip.Element;
+import eu.maveniverse.domtrip.maven.Coordinates;
+import eu.maveniverse.domtrip.maven.MavenPomElements;
import org.apache.maven.api.cli.mvnup.UpgradeOptions;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Priority;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
-import org.jdom2.Attribute;
-import org.jdom2.Comment;
-import org.jdom2.Content;
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.jdom2.Namespace;
-import org.jdom2.Text;
-
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Files.DEFAULT_PARENT_RELATIVE_PATH;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Plugins.DEFAULT_MAVEN_PLUGIN_GROUP_ID;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Plugins.MAVEN_PLUGIN_PREFIX;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.COMBINE_APPEND;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.COMBINE_CHILDREN;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.COMBINE_MERGE;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.COMBINE_OVERRIDE;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.COMBINE_SELF;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.ARTIFACT_ID;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.BUILD;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.CLASSIFIER;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.DEPENDENCIES;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.DEPENDENCY;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.DEPENDENCY_MANAGEMENT;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.GROUP_ID;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PARENT;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGINS;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN_MANAGEMENT;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN_REPOSITORIES;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN_REPOSITORY;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PROFILE;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PROFILES;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.RELATIVE_PATH;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.REPOSITORIES;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.REPOSITORY;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.TYPE;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.VERSION;
+
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Attributes.COMBINE_APPEND;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Attributes.COMBINE_CHILDREN;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Attributes.COMBINE_MERGE;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Attributes.COMBINE_OVERRIDE;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Attributes.COMBINE_SELF;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.BUILD;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.DEPENDENCIES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.DEPENDENCY;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.DEPENDENCY_MANAGEMENT;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PARENT;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGINS;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_MANAGEMENT;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_REPOSITORIES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_REPOSITORY;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROFILE;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROFILES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROPERTIES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.RELATIVE_PATH;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.REPOSITORIES;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.REPOSITORY;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Files.DEFAULT_PARENT_RELATIVE_PATH;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Plugins.DEFAULT_MAVEN_PLUGIN_GROUP_ID;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Plugins.MAVEN_PLUGIN_PREFIX;
/**
* Strategy for applying Maven 4 compatibility fixes to POM files.
@@ -78,6 +75,12 @@
@Priority(20)
public class CompatibilityFixStrategy extends AbstractUpgradeStrategy {
+ private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\{([^}]+)}");
+
+ private static final Set VALID_COMBINE_SELF_VALUES = Set.of(COMBINE_OVERRIDE, COMBINE_MERGE, "remove");
+
+ private static final Set VALID_COMBINE_CHILDREN_VALUES = Set.of(COMBINE_APPEND, COMBINE_MERGE);
+
@Override
public boolean isApplicable(UpgradeContext context) {
UpgradeOptions options = getOptions(context);
@@ -125,6 +128,9 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap)
Set modifiedPoms = new HashSet<>();
Set errorPoms = new HashSet<>();
+ Set allDefinedProperties = collectAllDefinedProperties(pomMap);
+ allDefinedProperties.addAll(collectEffectiveProperties(context, pomMap));
+
for (Map.Entry entry : pomMap.entrySet()) {
Path pomPath = entry.getKey();
Document pomDocument = entry.getValue();
@@ -136,13 +142,14 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap)
try {
boolean hasIssues = false;
- // Apply all compatibility fixes
hasIssues |= fixUnsupportedCombineChildrenAttributes(pomDocument, context);
hasIssues |= fixUnsupportedCombineSelfAttributes(pomDocument, context);
hasIssues |= fixDuplicateDependencies(pomDocument, context);
hasIssues |= fixDuplicatePlugins(pomDocument, context);
hasIssues |= fixUnsupportedRepositoryExpressions(pomDocument, context);
hasIssues |= fixIncorrectParentRelativePaths(pomDocument, pomPath, pomMap, context);
+ hasIssues |= fixUndefinedPropertyExpressions(pomDocument, allDefinedProperties, context);
+ hasIssues |= fixUndefinedPropertyExpressionsInRepositories(pomDocument, allDefinedProperties, context);
if (hasIssues) {
context.success("Maven 4 compatibility issues fixed");
@@ -163,147 +170,152 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap)
/**
* Fixes unsupported combine.children attribute values.
- * Maven 4 only supports 'append' and 'merge', not 'override'.
+ * Maven 4 only supports 'append' and 'merge' (default is merge).
+ * Invalid values are removed entirely since Maven 3 silently ignored them.
*/
private boolean fixUnsupportedCombineChildrenAttributes(Document pomDocument, UpgradeContext context) {
- boolean fixed = false;
- Element root = pomDocument.getRootElement();
-
- // Find all elements with combine.children="override" and change to "merge"
- List elementsWithCombineChildren = findElementsWithAttribute(root, COMBINE_CHILDREN, COMBINE_OVERRIDE);
- for (Element element : elementsWithCombineChildren) {
- element.getAttribute(COMBINE_CHILDREN).setValue(COMBINE_MERGE);
- context.detail("Fixed: " + COMBINE_CHILDREN + "='" + COMBINE_OVERRIDE + "' → '" + COMBINE_MERGE + "' in "
- + element.getName());
- fixed = true;
+ Element root = pomDocument.root();
+
+ List invalidElements = findElementsWithInvalidAttribute(
+ root, COMBINE_CHILDREN, VALID_COMBINE_CHILDREN_VALUES)
+ .toList();
+
+ for (Element element : invalidElements) {
+ String invalidValue = element.attribute(COMBINE_CHILDREN);
+ element.removeAttribute(COMBINE_CHILDREN);
+ context.detail(
+ "Fixed: removed invalid " + COMBINE_CHILDREN + "='" + invalidValue + "' from " + element.name());
}
- return fixed;
+ return !invalidElements.isEmpty();
}
/**
* Fixes unsupported combine.self attribute values.
- * Maven 4 only supports 'override', 'merge', and 'remove' (default is merge), not 'append'.
+ * Maven 4 only supports 'override', 'merge', and 'remove' (default is merge).
+ * Invalid values are removed entirely since Maven 3 silently ignored them.
*/
private boolean fixUnsupportedCombineSelfAttributes(Document pomDocument, UpgradeContext context) {
- boolean fixed = false;
- Element root = pomDocument.getRootElement();
-
- // Find all elements with combine.self="append" and change to "merge"
- List elementsWithCombineSelf = findElementsWithAttribute(root, COMBINE_SELF, COMBINE_APPEND);
- for (Element element : elementsWithCombineSelf) {
- element.getAttribute(COMBINE_SELF).setValue(COMBINE_MERGE);
- context.detail("Fixed: " + COMBINE_SELF + "='" + COMBINE_APPEND + "' → '" + COMBINE_MERGE + "' in "
- + element.getName());
- fixed = true;
+ Element root = pomDocument.root();
+
+ List invalidElements = findElementsWithInvalidAttribute(root, COMBINE_SELF, VALID_COMBINE_SELF_VALUES)
+ .toList();
+
+ for (Element element : invalidElements) {
+ String invalidValue = element.attribute(COMBINE_SELF);
+ element.removeAttribute(COMBINE_SELF);
+ context.detail("Fixed: removed invalid " + COMBINE_SELF + "='" + invalidValue + "' from " + element.name());
}
- return fixed;
+ return !invalidElements.isEmpty();
}
/**
* Fixes duplicate dependencies in dependencies and dependencyManagement sections.
*/
private boolean fixDuplicateDependencies(Document pomDocument, UpgradeContext context) {
- Element root = pomDocument.getRootElement();
- Namespace namespace = root.getNamespace();
- boolean fixed = false;
-
- // Fix main dependencies
- Element dependenciesElement = root.getChild(DEPENDENCIES, namespace);
- if (dependenciesElement != null) {
- fixed |= fixDuplicateDependenciesInSection(dependenciesElement, namespace, context, DEPENDENCIES);
- }
-
- // Fix dependencyManagement
- Element dependencyManagementElement = root.getChild(DEPENDENCY_MANAGEMENT, namespace);
- if (dependencyManagementElement != null) {
- Element managedDependenciesElement = dependencyManagementElement.getChild(DEPENDENCIES, namespace);
- if (managedDependenciesElement != null) {
- fixed |= fixDuplicateDependenciesInSection(
- managedDependenciesElement, namespace, context, DEPENDENCY_MANAGEMENT);
- }
- }
+ Element root = pomDocument.root();
+
+ // Collect all dependency containers to process
+ Stream dependencyContainers = Stream.concat(
+ // Root level dependencies
+ Stream.of(
+ new DependencyContainer(
+ root.childElement(DEPENDENCIES).orElse(null), DEPENDENCIES),
+ new DependencyContainer(
+ root.childElement(DEPENDENCY_MANAGEMENT)
+ .flatMap(dm -> dm.childElement(DEPENDENCIES))
+ .orElse(null),
+ DEPENDENCY_MANAGEMENT))
+ .filter(container -> container.element != null),
+ // Profile dependencies
+ root.childElement(PROFILES).stream()
+ .flatMap(profiles -> profiles.childElements(PROFILE))
+ .flatMap(profile -> Stream.of(
+ new DependencyContainer(
+ profile.childElement(DEPENDENCIES)
+ .orElse(null),
+ "profile dependencies"),
+ new DependencyContainer(
+ profile.childElement(DEPENDENCY_MANAGEMENT)
+ .flatMap(dm -> dm.childElement(DEPENDENCIES))
+ .orElse(null),
+ "profile dependencyManagement"))
+ .filter(container -> container.element != null)));
+
+ return dependencyContainers
+ .map(container -> fixDuplicateDependenciesInSection(container.element, context, container.sectionName))
+ .reduce(false, Boolean::logicalOr);
+ }
- // Fix profile dependencies
- Element profilesElement = root.getChild(PROFILES, namespace);
- if (profilesElement != null) {
- List profileElements = profilesElement.getChildren(PROFILE, namespace);
- for (Element profileElement : profileElements) {
- Element profileDependencies = profileElement.getChild(DEPENDENCIES, namespace);
- if (profileDependencies != null) {
- fixed |= fixDuplicateDependenciesInSection(
- profileDependencies, namespace, context, "profile dependencies");
- }
+ private static class DependencyContainer {
+ final Element element;
+ final String sectionName;
- Element profileDepMgmt = profileElement.getChild(DEPENDENCY_MANAGEMENT, namespace);
- if (profileDepMgmt != null) {
- Element profileManagedDeps = profileDepMgmt.getChild(DEPENDENCIES, namespace);
- if (profileManagedDeps != null) {
- fixed |= fixDuplicateDependenciesInSection(
- profileManagedDeps, namespace, context, "profile dependencyManagement");
- }
- }
- }
+ DependencyContainer(Element element, String sectionName) {
+ this.element = element;
+ this.sectionName = sectionName;
}
-
- return fixed;
}
/**
* Fixes duplicate plugins in plugins and pluginManagement sections.
*/
private boolean fixDuplicatePlugins(Document pomDocument, UpgradeContext context) {
- Element root = pomDocument.getRootElement();
- Namespace namespace = root.getNamespace();
- boolean fixed = false;
+ Element root = pomDocument.root();
+
+ // Collect all build elements to process
+ Stream buildContainers = Stream.concat(
+ // Root level build
+ Stream.of(new BuildContainer(root.childElement(BUILD).orElse(null), BUILD))
+ .filter(container -> container.element != null),
+ // Profile builds
+ root.childElement(PROFILES).stream()
+ .flatMap(profiles -> profiles.childElements(PROFILE))
+ .map(profile ->
+ new BuildContainer(profile.childElement(BUILD).orElse(null), "profile build"))
+ .filter(container -> container.element != null));
+
+ return buildContainers
+ .map(container -> fixPluginsInBuildElement(container.element, context, container.sectionName))
+ .reduce(false, Boolean::logicalOr);
+ }
- // Fix build plugins
- Element buildElement = root.getChild(BUILD, namespace);
- if (buildElement != null) {
- fixed |= fixPluginsInBuildElement(buildElement, namespace, context, BUILD);
- }
+ private static class BuildContainer {
+ final Element element;
+ final String sectionName;
- // Fix profile plugins
- Element profilesElement = root.getChild(PROFILES, namespace);
- if (profilesElement != null) {
- for (Element profileElement : profilesElement.getChildren(PROFILE, namespace)) {
- Element profileBuildElement = profileElement.getChild(BUILD, namespace);
- if (profileBuildElement != null) {
- fixed |= fixPluginsInBuildElement(profileBuildElement, namespace, context, "profile build");
- }
- }
+ BuildContainer(Element element, String sectionName) {
+ this.element = element;
+ this.sectionName = sectionName;
}
-
- return fixed;
}
/**
* Fixes unsupported repository URL expressions.
*/
private boolean fixUnsupportedRepositoryExpressions(Document pomDocument, UpgradeContext context) {
- Element root = pomDocument.getRootElement();
- Namespace namespace = root.getNamespace();
- boolean fixed = false;
-
- // Fix repositories
- fixed |= fixRepositoryExpressions(root.getChild(REPOSITORIES, namespace), namespace, context);
-
- // Fix pluginRepositories
- fixed |= fixRepositoryExpressions(root.getChild(PLUGIN_REPOSITORIES, namespace), namespace, context);
-
- // Fix repositories and pluginRepositories in profiles
- Element profilesElement = root.getChild(PROFILES, namespace);
- if (profilesElement != null) {
- List profileElements = profilesElement.getChildren(PROFILE, namespace);
- for (Element profileElement : profileElements) {
- fixed |= fixRepositoryExpressions(profileElement.getChild(REPOSITORIES, namespace), namespace, context);
- fixed |= fixRepositoryExpressions(
- profileElement.getChild(PLUGIN_REPOSITORIES, namespace), namespace, context);
- }
- }
-
- return fixed;
+ Element root = pomDocument.root();
+
+ // Collect all repository containers to process
+ Stream repositoryContainers = Stream.concat(
+ // Root level repositories
+ Stream.of(
+ root.childElement(REPOSITORIES).orElse(null),
+ root.childElement(PLUGIN_REPOSITORIES).orElse(null))
+ .filter(Objects::nonNull),
+ // Profile repositories
+ root.childElement(PROFILES).stream()
+ .flatMap(profiles -> profiles.childElements(PROFILE))
+ .flatMap(profile -> Stream.of(
+ profile.childElement(REPOSITORIES).orElse(null),
+ profile.childElement(PLUGIN_REPOSITORIES)
+ .orElse(null))
+ .filter(Objects::nonNull)));
+
+ return repositoryContainers
+ .map(container -> fixRepositoryExpressions(container, pomDocument, context))
+ .reduce(false, Boolean::logicalOr);
}
/**
@@ -311,22 +323,21 @@ private boolean fixUnsupportedRepositoryExpressions(Document pomDocument, Upgrad
*/
private boolean fixIncorrectParentRelativePaths(
Document pomDocument, Path pomPath, Map pomMap, UpgradeContext context) {
- Element root = pomDocument.getRootElement();
- Namespace namespace = root.getNamespace();
+ Element root = pomDocument.root();
- Element parentElement = root.getChild(PARENT, namespace);
+ Element parentElement = root.childElement(PARENT).orElse(null);
if (parentElement == null) {
return false; // No parent to fix
}
- Element relativePathElement = parentElement.getChild(RELATIVE_PATH, namespace);
+ Element relativePathElement = parentElement.childElement(RELATIVE_PATH).orElse(null);
String currentRelativePath =
- relativePathElement != null ? relativePathElement.getTextTrim() : DEFAULT_PARENT_RELATIVE_PATH;
+ relativePathElement != null ? relativePathElement.textContent().trim() : DEFAULT_PARENT_RELATIVE_PATH;
// Try to find the correct parent POM
- String parentGroupId = getChildText(parentElement, GROUP_ID, namespace);
- String parentArtifactId = getChildText(parentElement, ARTIFACT_ID, namespace);
- String parentVersion = getChildText(parentElement, VERSION, namespace);
+ String parentGroupId = parentElement.childText(MavenPomElements.Elements.GROUP_ID);
+ String parentArtifactId = parentElement.childText(MavenPomElements.Elements.ARTIFACT_ID);
+ String parentVersion = parentElement.childText(MavenPomElements.Elements.VERSION);
Path correctParentPath = findParentPomInMap(context, parentGroupId, parentArtifactId, parentVersion, pomMap);
if (correctParentPath != null) {
@@ -335,20 +346,8 @@ private boolean fixIncorrectParentRelativePaths(
String correctRelativePathStr = correctRelativePath.toString().replace('\\', '/');
if (!correctRelativePathStr.equals(currentRelativePath)) {
- // Update relativePath element
- if (relativePathElement == null) {
- relativePathElement = new Element(RELATIVE_PATH, namespace);
- Element insertAfter = parentElement.getChild(VERSION, namespace);
- if (insertAfter == null) {
- insertAfter = parentElement.getChild(ARTIFACT_ID, namespace);
- }
- if (insertAfter != null) {
- parentElement.addContent(parentElement.indexOf(insertAfter) + 1, relativePathElement);
- } else {
- parentElement.addContent(relativePathElement);
- }
- }
- relativePathElement.setText(correctRelativePathStr);
+ // Update or create relativePath element using DomUtils convenience method
+ DomUtils.updateOrCreateChildElement(parentElement, RELATIVE_PATH, correctRelativePathStr);
context.detail("Fixed: " + "relativePath corrected from '" + currentRelativePath + "' to '"
+ correctRelativePathStr + "'");
return true;
@@ -361,82 +360,329 @@ private boolean fixIncorrectParentRelativePaths(
return false;
}
+ private Set collectAllDefinedProperties(Map pomMap) {
+ Set properties = new HashSet<>();
+ for (Map.Entry entry : pomMap.entrySet()) {
+ collectPropertiesFromDom(entry.getValue(), properties);
+ }
+ return properties;
+ }
+
+ private void collectPropertiesFromDom(Document document, Set properties) {
+ Element root = document.root();
+
+ root.childElement(PROPERTIES)
+ .ifPresent(propsElement -> propsElement.childElements().forEach(child -> properties.add(child.name())));
+
+ root.childElement(PROFILES).ifPresent(profiles -> profiles.childElements(PROFILE)
+ .forEach(profile -> profile.childElement(PROPERTIES)
+ .ifPresent(propsElement ->
+ propsElement.childElements().forEach(child -> properties.add(child.name())))));
+ }
+
+ private Set collectEffectiveProperties(UpgradeContext context, Map pomMap) {
+ Set properties = new HashSet<>();
+ for (Path pomPath : pomMap.keySet()) {
+ try {
+ org.apache.maven.api.model.Model effectiveModel = buildEffectiveModel(pomPath);
+ properties.addAll(effectiveModel.getProperties().keySet());
+ } catch (Exception e) {
+ context.debug("Failed to build effective model for " + pomPath + ": " + e.getMessage());
+ }
+ }
+ return properties;
+ }
+
/**
- * Recursively finds all elements with a specific attribute value.
+ * Fixes dependencies with undefined property expressions by commenting them out.
*/
- private List findElementsWithAttribute(Element element, String attributeName, String attributeValue) {
- List result = new ArrayList<>();
+ private boolean fixUndefinedPropertyExpressions(
+ Document pomDocument, Set allDefinedProperties, UpgradeContext context) {
+ Element root = pomDocument.root();
+
+ Stream dependencyContainers = Stream.concat(
+ Stream.of(
+ new DependencyContainer(
+ root.childElement(DEPENDENCIES).orElse(null), DEPENDENCIES),
+ new DependencyContainer(
+ root.childElement(DEPENDENCY_MANAGEMENT)
+ .flatMap(dm -> dm.childElement(DEPENDENCIES))
+ .orElse(null),
+ DEPENDENCY_MANAGEMENT))
+ .filter(container -> container.element != null),
+ root.childElement(PROFILES).stream()
+ .flatMap(profiles -> profiles.childElements(PROFILE))
+ .flatMap(profile -> Stream.of(
+ new DependencyContainer(
+ profile.childElement(DEPENDENCIES)
+ .orElse(null),
+ "profile dependencies"),
+ new DependencyContainer(
+ profile.childElement(DEPENDENCY_MANAGEMENT)
+ .flatMap(dm -> dm.childElement(DEPENDENCIES))
+ .orElse(null),
+ "profile dependencyManagement"))
+ .filter(container -> container.element != null)));
+
+ return dependencyContainers
+ .map(container -> fixUndefinedPropertyExpressionsInSection(
+ container.element, allDefinedProperties, pomDocument, context, container.sectionName))
+ .reduce(false, Boolean::logicalOr);
+ }
- // Check current element
- Attribute attr = element.getAttribute(attributeName);
- if (attr != null && attributeValue.equals(attr.getValue())) {
- result.add(element);
- }
+ /**
+ * Fixes repositories with undefined property expressions by commenting them out.
+ */
+ private boolean fixUndefinedPropertyExpressionsInRepositories(
+ Document pomDocument, Set allDefinedProperties, UpgradeContext context) {
+ Element root = pomDocument.root();
+
+ Stream repositoryContainers = Stream.concat(
+ Stream.of(
+ new RepositoryContainer(
+ root.childElement(REPOSITORIES).orElse(null), REPOSITORY, REPOSITORIES),
+ new RepositoryContainer(
+ root.childElement(PLUGIN_REPOSITORIES).orElse(null),
+ PLUGIN_REPOSITORY,
+ PLUGIN_REPOSITORIES))
+ .filter(c -> c.element != null),
+ root.childElement(PROFILES).stream()
+ .flatMap(profiles -> profiles.childElements(PROFILE))
+ .flatMap(profile -> Stream.of(
+ new RepositoryContainer(
+ profile.childElement(REPOSITORIES)
+ .orElse(null),
+ REPOSITORY,
+ "profile repositories"),
+ new RepositoryContainer(
+ profile.childElement(PLUGIN_REPOSITORIES)
+ .orElse(null),
+ PLUGIN_REPOSITORY,
+ "profile pluginRepositories"))
+ .filter(c -> c.element != null)));
+
+ return repositoryContainers
+ .map(c -> fixUndefinedPropertyExpressionsInRepositorySection(
+ c.element, c.elementType, allDefinedProperties, pomDocument, context, c.sectionName))
+ .reduce(false, Boolean::logicalOr);
+ }
- // Recursively check children
- for (Element child : element.getChildren()) {
- result.addAll(findElementsWithAttribute(child, attributeName, attributeValue));
+ private record RepositoryContainer(Element element, String elementType, String sectionName) {}
+
+ private boolean fixUndefinedPropertyExpressionsInRepositorySection(
+ Element repositoriesElement,
+ String elementType,
+ Set allDefinedProperties,
+ Document pomDocument,
+ UpgradeContext context,
+ String sectionName) {
+ boolean fixed = false;
+ List repositories =
+ repositoriesElement.childElements(elementType).toList();
+ Editor editor = new Editor(pomDocument);
+
+ for (Element repository : repositories) {
+ Set undefinedProps = findUndefinedPropertiesInRepository(repository, allDefinedProperties);
+ if (!undefinedProps.isEmpty()) {
+ String propLabel = undefinedProps.size() > 1 ? "properties" : "property";
+ String propsStr = "'" + String.join("', '", undefinedProps) + "'";
+
+ Comment comment = editor.commentOutElement(repository);
+ String elementXml = comment.content().trim();
+ comment.content(
+ " mvnup: commented out - undefined " + propLabel + " " + propsStr + "\n" + elementXml + " ");
+
+ context.detail("Fixed: Commented out " + elementType + " with undefined " + propLabel + " " + propsStr
+ + " in " + sectionName);
+ fixed = true;
+ }
}
- return result;
+ return fixed;
+ }
+
+ private Set findUndefinedPropertiesInRepository(Element repository, Set allDefinedProperties) {
+ Set undefinedProperties = new HashSet<>();
+
+ String id = repository.childText("id");
+ String url = repository.childText("url");
+
+ collectUndefinedExpressions(id, allDefinedProperties, undefinedProperties);
+ collectUndefinedExpressions(url, allDefinedProperties, undefinedProperties);
+
+ return undefinedProperties;
}
/**
- * Helper methods extracted from BaseUpgradeGoal for compatibility fixes.
+ * Fixes undefined property expressions in a specific dependencies section.
*/
- private boolean fixDuplicateDependenciesInSection(
- Element dependenciesElement, Namespace namespace, UpgradeContext context, String sectionName) {
+ private boolean fixUndefinedPropertyExpressionsInSection(
+ Element dependenciesElement,
+ Set allDefinedProperties,
+ Document pomDocument,
+ UpgradeContext context,
+ String sectionName) {
boolean fixed = false;
- List dependencies = dependenciesElement.getChildren(DEPENDENCY, namespace);
- Map seenDependencies = new HashMap<>();
- List toRemove = new ArrayList<>();
+ List dependencies =
+ dependenciesElement.childElements(DEPENDENCY).toList();
+ Editor editor = new Editor(pomDocument);
for (Element dependency : dependencies) {
- String groupId = getChildText(dependency, GROUP_ID, namespace);
- String artifactId = getChildText(dependency, ARTIFACT_ID, namespace);
- String type = getChildText(dependency, TYPE, namespace);
- String classifier = getChildText(dependency, CLASSIFIER, namespace);
-
- // Create a key for uniqueness check
- String key = groupId + ":" + artifactId + ":" + (type != null ? type : "jar") + ":"
- + (classifier != null ? classifier : "");
-
- if (seenDependencies.containsKey(key)) {
- // Found duplicate - remove it
- toRemove.add(dependency);
- context.detail("Fixed: " + "Removed duplicate dependency: " + key + " in " + sectionName);
+ Set undefinedProps = findUndefinedProperties(dependency, allDefinedProperties);
+ if (!undefinedProps.isEmpty()) {
+ String propLabel = undefinedProps.size() > 1 ? "properties" : "property";
+ String propsStr = "'" + String.join("', '", undefinedProps) + "'";
+
+ Comment comment = editor.commentOutElement(dependency);
+ String elementXml = comment.content().trim();
+ comment.content(
+ " mvnup: commented out - undefined " + propLabel + " " + propsStr + "\n" + elementXml + " ");
+
+ context.detail("Fixed: Commented out dependency with undefined " + propLabel + " " + propsStr + " in "
+ + sectionName);
fixed = true;
- } else {
- seenDependencies.put(key, dependency);
}
}
- // Remove duplicates while preserving formatting
- for (Element duplicate : toRemove) {
- removeElementWithFormatting(duplicate);
+ return fixed;
+ }
+
+ /**
+ * Finds undefined property expressions in a dependency's coordinate fields.
+ */
+ private Set findUndefinedProperties(Element dependency, Set allDefinedProperties) {
+ Set undefinedProperties = new HashSet<>();
+
+ String groupId = dependency.childText(MavenPomElements.Elements.GROUP_ID);
+ String artifactId = dependency.childText(MavenPomElements.Elements.ARTIFACT_ID);
+ String version = dependency.childText(MavenPomElements.Elements.VERSION);
+
+ collectUndefinedExpressions(groupId, allDefinedProperties, undefinedProperties);
+ collectUndefinedExpressions(artifactId, allDefinedProperties, undefinedProperties);
+ collectUndefinedExpressions(version, allDefinedProperties, undefinedProperties);
+
+ return undefinedProperties;
+ }
+
+ private void collectUndefinedExpressions(String value, Set allDefinedProperties, Set result) {
+ if (value == null) {
+ return;
+ }
+ Matcher matcher = EXPRESSION_PATTERN.matcher(value);
+ while (matcher.find()) {
+ String propertyName = matcher.group(1);
+ if (!isWellKnownProperty(propertyName) && !allDefinedProperties.contains(propertyName)) {
+ result.add(propertyName);
+ }
}
+ }
- return fixed;
+ private static boolean isWellKnownProperty(String propertyName) {
+ if (propertyName.startsWith("project.")
+ || propertyName.startsWith("pom.")
+ || propertyName.startsWith("env.")
+ || propertyName.startsWith("settings.")
+ || propertyName.startsWith("maven.")) {
+ return true;
+ }
+ if (propertyName.startsWith("java.")
+ || propertyName.startsWith("os.")
+ || propertyName.startsWith("user.")
+ || propertyName.startsWith("file.")
+ || propertyName.startsWith("line.")
+ || propertyName.startsWith("path.")
+ || propertyName.startsWith("sun.")) {
+ return true;
+ }
+ return "basedir".equals(propertyName)
+ || "revision".equals(propertyName)
+ || "sha1".equals(propertyName)
+ || "changelist".equals(propertyName);
}
- private boolean fixPluginsInBuildElement(
- Element buildElement, Namespace namespace, UpgradeContext context, String sectionName) {
+ /**
+ * Recursively finds all elements with a specific attribute value.
+ */
+ private Stream findElementsWithAttribute(Element element, String attributeName, String attributeValue) {
+ return Stream.concat(
+ // Check current element
+ Stream.of(element).filter(e -> {
+ String attr = e.attribute(attributeName);
+ return attr != null && attributeValue.equals(attr);
+ }),
+ // Recursively check children
+ element.childElements()
+ .flatMap(child -> findElementsWithAttribute(child, attributeName, attributeValue)));
+ }
+
+ /**
+ * Recursively finds all elements with an attribute whose value is not in the set of valid values.
+ */
+ private Stream findElementsWithInvalidAttribute(
+ Element element, String attributeName, Set validValues) {
+ return Stream.concat(
+ Stream.of(element).filter(e -> {
+ String attr = e.attribute(attributeName);
+ return attr != null && !validValues.contains(attr);
+ }),
+ element.childElements()
+ .flatMap(child -> findElementsWithInvalidAttribute(child, attributeName, validValues)));
+ }
+
+ /**
+ * Helper methods extracted from BaseUpgradeGoal for compatibility fixes.
+ */
+ private boolean fixDuplicateDependenciesInSection(
+ Element dependenciesElement, UpgradeContext context, String sectionName) {
+ List dependencies =
+ dependenciesElement.childElements(DEPENDENCY).toList();
+ Map seenDependencies = new HashMap<>();
+
+ List duplicates = dependencies.stream()
+ .filter(dependency -> {
+ String key = createDependencyKey(dependency);
+ if (seenDependencies.containsKey(key)) {
+ context.detail("Fixed: Removed duplicate dependency: " + key + " in " + sectionName);
+ return true; // This is a duplicate
+ } else {
+ seenDependencies.put(key, dependency);
+ return false; // This is the first occurrence
+ }
+ })
+ .toList();
+
+ // Remove duplicates while preserving formatting
+ duplicates.forEach(DomUtils::removeElement);
+
+ return !duplicates.isEmpty();
+ }
+
+ private String createDependencyKey(Element dependency) {
+ String groupId = dependency.childText(MavenPomElements.Elements.GROUP_ID);
+ String artifactId = dependency.childText(MavenPomElements.Elements.ARTIFACT_ID);
+ String type = dependency.childText(MavenPomElements.Elements.TYPE);
+ String classifier = dependency.childText(MavenPomElements.Elements.CLASSIFIER);
+
+ return groupId + ":" + artifactId + ":" + (type != null ? type : "jar") + ":"
+ + (classifier != null ? classifier : "");
+ }
+
+ private boolean fixPluginsInBuildElement(Element buildElement, UpgradeContext context, String sectionName) {
boolean fixed = false;
- Element pluginsElement = buildElement.getChild(PLUGINS, namespace);
+ Element pluginsElement = buildElement.childElement(PLUGINS).orElse(null);
if (pluginsElement != null) {
- fixed |= fixDuplicatePluginsInSection(pluginsElement, namespace, context, sectionName + "/" + PLUGINS);
+ fixed |= fixDuplicatePluginsInSection(pluginsElement, context, sectionName + "/" + PLUGINS);
}
- Element pluginManagementElement = buildElement.getChild(PLUGIN_MANAGEMENT, namespace);
+ Element pluginManagementElement =
+ buildElement.childElement(PLUGIN_MANAGEMENT).orElse(null);
if (pluginManagementElement != null) {
- Element managedPluginsElement = pluginManagementElement.getChild(PLUGINS, namespace);
+ Element managedPluginsElement =
+ pluginManagementElement.childElement(PLUGINS).orElse(null);
if (managedPluginsElement != null) {
fixed |= fixDuplicatePluginsInSection(
- managedPluginsElement,
- namespace,
- context,
- sectionName + "/" + PLUGIN_MANAGEMENT + "/" + PLUGINS);
+ managedPluginsElement, context, sectionName + "/" + PLUGIN_MANAGEMENT + "/" + PLUGINS);
}
}
@@ -446,76 +692,65 @@ private boolean fixPluginsInBuildElement(
/**
* Fixes duplicate plugins within a specific plugins section.
*/
- private boolean fixDuplicatePluginsInSection(
- Element pluginsElement, Namespace namespace, UpgradeContext context, String sectionName) {
- boolean fixed = false;
- List plugins = pluginsElement.getChildren(PLUGIN, namespace);
+ private boolean fixDuplicatePluginsInSection(Element pluginsElement, UpgradeContext context, String sectionName) {
+ List plugins = pluginsElement.childElements(PLUGIN).toList();
Map seenPlugins = new HashMap<>();
- List toRemove = new ArrayList<>();
- for (Element plugin : plugins) {
- String groupId = getChildText(plugin, GROUP_ID, namespace);
- String artifactId = getChildText(plugin, ARTIFACT_ID, namespace);
+ List duplicates = plugins.stream()
+ .filter(plugin -> {
+ String key = createPluginKey(plugin);
+ if (key != null) {
+ if (seenPlugins.containsKey(key)) {
+ context.detail("Fixed: Removed duplicate plugin: " + key + " in " + sectionName);
+ return true; // This is a duplicate
+ } else {
+ seenPlugins.put(key, plugin);
+ }
+ }
+ return false; // This is the first occurrence or invalid plugin
+ })
+ .toList();
- // Default groupId for Maven plugins
- if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
- groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
- }
+ // Remove duplicates while preserving formatting
+ duplicates.forEach(DomUtils::removeElement);
- if (groupId != null && artifactId != null) {
- // Create a key for uniqueness check (groupId:artifactId)
- String key = groupId + ":" + artifactId;
+ return !duplicates.isEmpty();
+ }
- if (seenPlugins.containsKey(key)) {
- // Found duplicate - remove it
- toRemove.add(plugin);
- context.detail("Fixed: " + "Removed duplicate plugin: " + key + " in " + sectionName);
- fixed = true;
- } else {
- seenPlugins.put(key, plugin);
- }
- }
- }
+ private String createPluginKey(Element plugin) {
+ String groupId = plugin.childText(MavenPomElements.Elements.GROUP_ID);
+ String artifactId = plugin.childText(MavenPomElements.Elements.ARTIFACT_ID);
- // Remove duplicates while preserving formatting
- for (Element duplicate : toRemove) {
- removeElementWithFormatting(duplicate);
+ // Default groupId for Maven plugins
+ if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
+ groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
}
- return fixed;
+ return (groupId != null && artifactId != null) ? groupId + ":" + artifactId : null;
}
- private boolean fixRepositoryExpressions(Element repositoriesElement, Namespace namespace, UpgradeContext context) {
+ private boolean fixRepositoryExpressions(
+ Element repositoriesElement, Document pomDocument, UpgradeContext context) {
if (repositoriesElement == null) {
return false;
}
boolean fixed = false;
- String elementType = repositoriesElement.getName().equals(REPOSITORIES) ? REPOSITORY : PLUGIN_REPOSITORY;
- List repositories = repositoriesElement.getChildren(elementType, namespace);
+ String elementType = repositoriesElement.name().equals(REPOSITORIES) ? REPOSITORY : PLUGIN_REPOSITORY;
+ List repositories =
+ repositoriesElement.childElements(elementType).toList();
for (Element repository : repositories) {
- Element urlElement = repository.getChild("url", namespace);
+ Element urlElement = repository.childElement("url").orElse(null);
if (urlElement != null) {
- String url = urlElement.getTextTrim();
- if (url.contains("${")
- && !url.contains("${project.basedir}")
- && !url.contains("${project.rootDirectory}")) {
- String repositoryId = getChildText(repository, "id", namespace);
- context.warning("Found unsupported expression in " + elementType + " URL (id: " + repositoryId
- + "): " + url);
- context.warning(
- "Maven 4 only supports ${project.basedir} and ${project.rootDirectory} expressions in repository URLs");
-
- // Comment out the problematic repository
- Comment comment =
- new Comment(" Repository disabled due to unsupported expression in URL: " + url + " ");
- Element parent = repository.getParentElement();
- parent.addContent(parent.indexOf(repository), comment);
- removeElementWithFormatting(repository);
-
- context.detail("Fixed: " + "Commented out " + elementType + " with unsupported URL expression (id: "
- + repositoryId + ")");
+ String url = urlElement.textContent().trim();
+ String fixedUrl =
+ url.replace("${basedir}", "${project.basedir}").replace("${pom.basedir}", "${project.basedir}");
+ if (!fixedUrl.equals(url)) {
+ urlElement.textContent(fixedUrl);
+ String repositoryId = repository.childText("id");
+ context.detail("Fixed: replaced deprecated expression in " + elementType + " URL (id: "
+ + repositoryId + "): " + url + " → " + fixedUrl);
fixed = true;
}
}
@@ -528,7 +763,8 @@ private Path findParentPomInMap(
UpgradeContext context, String groupId, String artifactId, String version, Map pomMap) {
return pomMap.entrySet().stream()
.filter(entry -> {
- GAV gav = GAVUtils.extractGAVWithParentResolution(context, entry.getValue());
+ Coordinates gav = AbstractUpgradeStrategy.extractArtifactCoordinatesWithParentResolution(
+ context, entry.getValue());
return gav != null
&& Objects.equals(gav.groupId(), groupId)
&& Objects.equals(gav.artifactId(), artifactId)
@@ -538,34 +774,4 @@ private Path findParentPomInMap(
.findFirst()
.orElse(null);
}
-
- private String getChildText(Element parent, String elementName, Namespace namespace) {
- Element element = parent.getChild(elementName, namespace);
- return element != null ? element.getTextTrim() : null;
- }
-
- /**
- * Removes an element while preserving formatting by also removing preceding whitespace.
- */
- private void removeElementWithFormatting(Element element) {
- Element parent = element.getParentElement();
- if (parent != null) {
- int index = parent.indexOf(element);
-
- // Remove the element
- parent.removeContent(element);
-
- // Try to remove preceding whitespace/newline
- if (index > 0) {
- Content prevContent = parent.getContent(index - 1);
- if (prevContent instanceof Text textContent) {
- String text = textContent.getText();
- // If it's just whitespace and newlines, remove it
- if (text.trim().isEmpty() && text.contains("\n")) {
- parent.removeContent(prevContent);
- }
- }
- }
- }
- }
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DomUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DomUtils.java
new file mode 100644
index 000000000000..5c27eb2774cf
--- /dev/null
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/DomUtils.java
@@ -0,0 +1,211 @@
+/*
+ * 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.cling.invoker.mvnup.goals;
+
+import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.Editor;
+import eu.maveniverse.domtrip.Element;
+import eu.maveniverse.domtrip.maven.PomEditor;
+
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.ARTIFACT_ID;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.DEPENDENCY;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.GROUP_ID;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.VERSION;
+
+/**
+ * Utility class for XML operations on Maven POM files.
+ *
+ * This class provides convenience methods that delegate to:
+ *
+ * {@link eu.maveniverse.domtrip.maven.PomEditor} - DomTrip's PomEditor
+ * {@link eu.maveniverse.domtrip.Element} - DomTrip's Element API
+ *
+ *
+ * These methods are kept for convenience and backward compatibility.
+ * For more advanced operations, consider using ExtendedPomEditor or DomTrip directly.
+ *
+ *
Using DomTrip Directly
+ * Many operations can be performed directly using DomTrip's Element API:
+ *
{@code
+ * // Find child element
+ * Element child = parent.childElement("version").orElse(null);
+ *
+ * // Check if child exists
+ * boolean hasVersion = parent.childElement("version").isPresent();
+ *
+ * // Get child text content
+ * String version = parent.childElement("version")
+ * .map(Element::textContent)
+ * .orElse(null);
+ *
+ * // Get trimmed text content
+ * String trimmedVersion = parent.childElement("version")
+ * .map(Element::textContentTrimmed)
+ * .orElse(null);
+ *
+ * // Set text content (fluent API)
+ * element.textContent("4.0.0");
+ * }
+ *
+ * When to Use DomUtils
+ * Use DomUtils methods when you need:
+ *
+ * Maven-specific element ordering (insertNewElement, insertContentElement)
+ * High-level helpers (addGAVElements, createDependency, createPlugin)
+ * Null-safe operations (updateElementContent, removeElement)
+ * Update-or-create patterns (updateOrCreateChildElement)
+ *
+ *
+ * @see eu.maveniverse.domtrip.Element
+ * @see eu.maveniverse.domtrip.Editor
+ * @see eu.maveniverse.domtrip.maven.PomEditor
+ */
+public class DomUtils {
+
+ private DomUtils() {
+ // Utility class
+ }
+
+ /**
+ * Inserts a new child element to the given parent element with proper Maven POM ordering.
+ *
+ * @param name the name of the new element
+ * @param parent the parent element
+ * @return the new element
+ *
+ */
+ public static Element insertNewElement(String name, Element parent) {
+ PomEditor editor = new PomEditor(parent.document());
+ return editor.insertMavenElement(parent, name);
+ }
+
+ /**
+ * Inserts a new content element with the given name and text content.
+ *
+ * @param parent the parent element
+ * @param name the name of the new element
+ * @param content the text content
+ * @return the new element
+ *
+ */
+ public static Element insertContentElement(Element parent, String name, String content) {
+ PomEditor editor = new PomEditor(parent.document());
+ return editor.insertMavenElement(parent, name, content);
+ }
+
+ /**
+ * Finds a child element by name under the specified parent.
+ *
+ * @param parent the parent element
+ * @param name the child element name to find
+ * @return the child element if found, null otherwise
+ *
+ */
+ public static Element findChildElement(Element parent, String name) {
+ return parent.childElement(name).orElse(null);
+ }
+
+ /**
+ * Serializes a domtrip Document to XML string with preserved formatting.
+ *
+ * @param document the domtrip Document
+ * @return the XML string with preserved formatting
+ *
+ */
+ public static String toXml(Document document) {
+ Editor editor = new Editor(document);
+ return editor.toXml();
+ }
+
+ /**
+ * Removes an element from its parent.
+ *
+ * @param element the element to remove
+ *
+ */
+ public static void removeElement(Element element) {
+ Editor editor = new Editor(element.document());
+ editor.removeElement(element);
+ }
+
+ /**
+ * Convenience method to add GAV (groupId, artifactId, version) elements to a parent.
+ *
+ * @param parent the parent element (e.g., dependency or plugin)
+ * @param groupId the groupId value
+ * @param artifactId the artifactId value
+ * @param version the version value (can be null to skip)
+ *
+ */
+ public static void addGAVElements(Element parent, String groupId, String artifactId, String version) {
+ insertContentElement(parent, GROUP_ID, groupId);
+ insertContentElement(parent, ARTIFACT_ID, artifactId);
+ if (version != null && !version.isEmpty()) {
+ insertContentElement(parent, VERSION, version);
+ }
+ }
+
+ /**
+ * Convenience method to create a dependency element with GAV.
+ *
+ * @param dependenciesElement the dependencies parent element
+ * @param groupId the groupId value
+ * @param artifactId the artifactId value
+ * @param version the version value (can be null)
+ * @return the created dependency element
+ *
+ */
+ public static Element createDependency(
+ Element dependenciesElement, String groupId, String artifactId, String version) {
+ Element dependency = insertNewElement(DEPENDENCY, dependenciesElement);
+ addGAVElements(dependency, groupId, artifactId, version);
+ return dependency;
+ }
+
+ /**
+ * Convenience method to create a plugin element with GAV.
+ *
+ * @param pluginsElement the plugins parent element
+ * @param groupId the groupId value
+ * @param artifactId the artifactId value
+ * @param version the version value (can be null)
+ * @return the created plugin element
+ *
+ */
+ public static Element createPlugin(Element pluginsElement, String groupId, String artifactId, String version) {
+ Element plugin = insertNewElement(PLUGIN, pluginsElement);
+ addGAVElements(plugin, groupId, artifactId, version);
+ return plugin;
+ }
+
+ /**
+ * Updates or creates a child element with the given content.
+ *
+ * @param parent the parent element
+ * @param childName the child element name
+ * @param content the content to set
+ * @return the updated or created element
+ *
+ */
+ public static Element updateOrCreateChildElement(Element parent, String childName, String content) {
+ PomEditor editor = new PomEditor(parent.document());
+ return editor.updateOrCreateChildElement(parent, childName, content);
+ }
+}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/GAVUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/GAVUtils.java
deleted file mode 100644
index 53427b50cc2d..000000000000
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/GAVUtils.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.cling.invoker.mvnup.goals;
-
-import java.nio.file.Path;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.jdom2.Namespace;
-
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.ARTIFACT_ID;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.GROUP_ID;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PARENT;
-import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.VERSION;
-
-/**
- * Utility class for handling GroupId, ArtifactId, Version (GAV) operations
- * in Maven POM files during the upgrade process.
- */
-public final class GAVUtils {
-
- private GAVUtils() {
- // Utility class
- }
-
- /**
- * Computes all GAVs from all POMs in the multi-module project for inference.
- * This includes resolving parent inheritance and relative path parents.
- *
- * @param context the upgrade context
- * @param pomMap map of all POM files in the project
- * @return set of all GAVs in the project
- */
- public static Set computeAllGAVs(UpgradeContext context, Map pomMap) {
- Set