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

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


The following commit(s) were added to refs/heads/master by this push:
     new 399f8b4ffc [MNG-8081] interpolate available properties during default 
profile selection (Maven 4.x) (#1446)
399f8b4ffc is described below

commit 399f8b4ffce829837a6ea747eaab070061693653
Author: Matt Benson <gudnabr...@gmail.com>
AuthorDate: Thu May 2 08:13:07 2024 -0500

    [MNG-8081] interpolate available properties during default profile 
selection (Maven 4.x) (#1446)
    
    Co-authored-by: Guillaume Nodet <gno...@gmail.com>
---
 .../internal/impl/model/DefaultModelBuilder.java   | 107 +++++----
 .../internal/impl/model/DefaultModelValidator.java | 263 ++++++++++++++++++--
 .../maven/model/building/DefaultModelBuilder.java  | 111 +++++----
 .../model/validation/DefaultModelValidator.java    | 264 +++++++++++++++++++--
 .../validation/DefaultModelValidatorTest.java      |  56 ++++-
 ...le-activation-file-with-allowed-expressions.xml |  18 ++
 ...tivation-property-with-project-expressions.xml} |  33 +--
 7 files changed, 684 insertions(+), 168 deletions(-)

diff --git 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
index e10a05887f..43bdd649ee 100644
--- 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
+++ 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java
@@ -35,7 +35,9 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.maven.api.VersionRange;
 import org.apache.maven.api.annotations.Nullable;
@@ -50,6 +52,7 @@ import org.apache.maven.api.model.Dependency;
 import org.apache.maven.api.model.DependencyManagement;
 import org.apache.maven.api.model.Exclusion;
 import org.apache.maven.api.model.InputLocation;
+import org.apache.maven.api.model.InputLocationTracker;
 import org.apache.maven.api.model.InputSource;
 import org.apache.maven.api.model.Model;
 import org.apache.maven.api.model.Parent;
@@ -80,8 +83,11 @@ import org.apache.maven.api.services.xml.XmlReaderRequest;
 import org.apache.maven.internal.impl.InternalSession;
 import org.apache.maven.internal.impl.resolver.DefaultModelCache;
 import org.apache.maven.internal.impl.resolver.DefaultModelResolver;
+import org.apache.maven.model.v4.MavenTransformer;
 import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.Interpolator;
 import org.codehaus.plexus.interpolation.MapBasedValueSource;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
 import org.eclipse.aether.impl.RemoteRepositoryManager;
 
@@ -377,54 +383,75 @@ public class DefaultModelBuilder implements ModelBuilder {
 
     private List<Profile> interpolateActivations(
             List<Profile> profiles, DefaultProfileActivationContext context, 
DefaultModelProblemCollector problems) {
-        List<Profile> newProfiles = null;
-        for (int index = 0; index < profiles.size(); index++) {
-            Profile profile = profiles.get(index);
-            Activation activation = profile.getActivation();
-            if (activation != null) {
-                ActivationFile file = activation.getFile();
-                if (file != null) {
-                    String oldExists = file.getExists();
-                    if (isNotEmpty(oldExists)) {
+        if (profiles.stream()
+                .map(org.apache.maven.api.model.Profile::getActivation)
+                .noneMatch(Objects::nonNull)) {
+            return profiles;
+        }
+        final Interpolator xform = new RegexBasedInterpolator();
+        xform.setCacheAnswers(true);
+        Stream.of(context.getUserProperties(), context.getSystemProperties())
+                .map(MapBasedValueSource::new)
+                .forEach(xform::addValueSource);
+
+        class ProfileInterpolator extends MavenTransformer implements 
UnaryOperator<Profile> {
+            ProfileInterpolator() {
+                super(s -> {
+                    if (isNotEmpty(s)) {
                         try {
-                            String newExists = interpolate(oldExists, context);
-                            if (!Objects.equals(oldExists, newExists)) {
-                                if (newProfiles == null) {
-                                    newProfiles = new ArrayList<>(profiles);
-                                }
-                                newProfiles.set(
-                                        index, 
profile.withActivation(activation.withFile(file.withExists(newExists))));
-                            }
+                            return xform.interpolate(s);
                         } catch (InterpolationException e) {
-                            addInterpolationProblem(problems, file, oldExists, 
e, "exists");
-                        }
-                    } else {
-                        String oldMissing = file.getMissing();
-                        if (isNotEmpty(oldMissing)) {
-                            try {
-                                String newMissing = interpolate(oldMissing, 
context);
-                                if (!Objects.equals(oldMissing, newMissing)) {
-                                    if (newProfiles == null) {
-                                        newProfiles = new 
ArrayList<>(profiles);
-                                    }
-                                    newProfiles.set(
-                                            index,
-                                            
profile.withActivation(activation.withFile(file.withMissing(newMissing))));
-                                }
-                            } catch (InterpolationException e) {
-                                addInterpolationProblem(problems, file, 
oldMissing, e, "missing");
-                            }
+                            problems.add(Severity.ERROR, 
ModelProblem.Version.BASE, e.getMessage(), e);
                         }
                     }
+                    return s;
+                });
+            }
+
+            @Override
+            public Profile apply(Profile p) {
+                return Profile.newBuilder(p)
+                        .activation(transformActivation(p.getActivation()))
+                        .build();
+            }
+
+            @Override
+            protected ActivationFile.Builder transformActivationFile_Missing(
+                    Supplier<? extends ActivationFile.Builder> creator,
+                    ActivationFile.Builder builder,
+                    ActivationFile target) {
+                String path = target.getMissing();
+                String xformed = transformPath(path, target, "missing");
+                return xformed != path ? (builder != null ? builder : 
creator.get()).missing(xformed) : builder;
+            }
+
+            @Override
+            protected ActivationFile.Builder transformActivationFile_Exists(
+                    Supplier<? extends ActivationFile.Builder> creator,
+                    ActivationFile.Builder builder,
+                    ActivationFile target) {
+                final String path = target.getExists();
+                final String xformed = transformPath(path, target, "exists");
+                return xformed != path ? (builder != null ? builder : 
creator.get()).exists(xformed) : builder;
+            }
+
+            private String transformPath(String path, ActivationFile target, 
String locationKey) {
+                if (isNotEmpty(path)) {
+                    try {
+                        return 
profileActivationFilePathInterpolator.interpolate(path, context);
+                    } catch (InterpolationException e) {
+                        addInterpolationProblem(problems, target, path, e, 
locationKey);
+                    }
                 }
+                return path;
             }
         }
-        return newProfiles != null ? newProfiles : profiles;
+        return profiles.stream().map(new ProfileInterpolator()).toList();
     }
 
     private static void addInterpolationProblem(
             DefaultModelProblemCollector problems,
-            ActivationFile file,
+            InputLocationTracker target,
             String path,
             InterpolationException e,
             String locationKey) {
@@ -432,14 +459,10 @@ public class DefaultModelBuilder implements ModelBuilder {
                 Severity.ERROR,
                 ModelProblem.Version.BASE,
                 "Failed to interpolate file location " + path + ": " + 
e.getMessage(),
-                file.getLocation(locationKey),
+                target.getLocation(locationKey),
                 e);
     }
 
-    private String interpolate(String path, ProfileActivationContext context) 
throws InterpolationException {
-        return isNotEmpty(path) ? 
profileActivationFilePathInterpolator.interpolate(path, context) : path;
-    }
-
     private static boolean isNotEmpty(String string) {
         return string != null && !string.isEmpty();
     }
diff --git 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
index 120a34e419..ce57f64433 100644
--- 
a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
+++ 
b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelValidator.java
@@ -21,20 +21,31 @@ package org.apache.maven.internal.impl.model;
 import java.io.File;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import org.apache.maven.api.di.Inject;
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.di.Singleton;
 import org.apache.maven.api.model.Activation;
 import org.apache.maven.api.model.ActivationFile;
+import org.apache.maven.api.model.ActivationOS;
+import org.apache.maven.api.model.ActivationProperty;
 import org.apache.maven.api.model.Build;
 import org.apache.maven.api.model.BuildBase;
 import org.apache.maven.api.model.Dependency;
@@ -59,6 +70,7 @@ import org.apache.maven.api.services.ModelProblem.Version;
 import org.apache.maven.api.services.ModelProblemCollector;
 import org.apache.maven.api.services.model.*;
 import org.apache.maven.model.v4.MavenModelVersion;
+import org.apache.maven.model.v4.MavenTransformer;
 
 /**
  */
@@ -80,6 +92,196 @@ public class DefaultModelValidator implements 
ModelValidator {
 
     private static final String EMPTY = "";
 
+    private record ActivationFrame(String location, Optional<? extends 
InputLocationTracker> parent) {}
+
+    private static class ActivationWalker extends MavenTransformer {
+
+        private final Deque<ActivationFrame> stk;
+
+        ActivationWalker(Deque<ActivationFrame> stk, UnaryOperator<String> 
transformer) {
+            super(transformer);
+            this.stk = stk;
+        }
+
+        private ActivationFrame nextFrame(String property) {
+            return new ActivationFrame(property, Optional.empty());
+        }
+
+        private <P> ActivationFrame nextFrame(String property, Function<P, 
InputLocationTracker> child) {
+            @SuppressWarnings("unchecked")
+            final Optional<P> parent = (Optional<P>) stk.peek().parent;
+            return new ActivationFrame(property, parent.map(child));
+        }
+
+        @Override
+        public Activation transformActivation(Activation target) {
+            stk.push(new ActivationFrame("activation", Optional.of(target)));
+            try {
+                return super.transformActivation(target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_ActiveByDefault(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            return builder;
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_File(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("file", Activation::getFile));
+            Optional.ofNullable(target.getFile());
+            try {
+                return super.transformActivation_File(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationFile.Builder transformActivationFile_Exists(
+                Supplier<? extends ActivationFile.Builder> creator,
+                ActivationFile.Builder builder,
+                ActivationFile target) {
+            stk.push(nextFrame("exists"));
+            try {
+                return super.transformActivationFile_Exists(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationFile.Builder transformActivationFile_Missing(
+                Supplier<? extends ActivationFile.Builder> creator,
+                ActivationFile.Builder builder,
+                ActivationFile target) {
+            stk.push(nextFrame("missing"));
+            try {
+                return super.transformActivationFile_Missing(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Jdk(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("jdk"));
+            try {
+                return super.transformActivation_Jdk(creator, builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Os(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("os", Activation::getOs));
+            try {
+                return super.transformActivation_Os(creator, builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Arch(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("arch"));
+            try {
+                return super.transformActivationOS_Arch(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Family(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("family"));
+            try {
+                return super.transformActivationOS_Family(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Name(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("name"));
+            try {
+                return super.transformActivationOS_Name(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Version(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("version"));
+            try {
+                return super.transformActivationOS_Version(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Packaging(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("packaging"));
+            try {
+                return super.transformActivation_Packaging(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Property(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("property", Activation::getProperty));
+            try {
+                return super.transformActivation_Property(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationProperty.Builder transformActivationProperty_Name(
+                Supplier<? extends ActivationProperty.Builder> creator,
+                ActivationProperty.Builder builder,
+                ActivationProperty target) {
+            stk.push(nextFrame("name"));
+            try {
+                return super.transformActivationProperty_Name(creator, 
builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationProperty.Builder transformActivationProperty_Value(
+                Supplier<? extends ActivationProperty.Builder> creator,
+                ActivationProperty.Builder builder,
+                ActivationProperty target) {
+            stk.push(nextFrame("value"));
+            try {
+                return super.transformActivationProperty_Value(creator, 
builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+    }
+
     private final Set<String> validCoordinateIds = new HashSet<>();
 
     private final Set<String> validProfileIds = new HashSet<>();
@@ -282,42 +484,53 @@ public class DefaultModelValidator implements 
ModelValidator {
     }
 
     private void validate30RawProfileActivation(ModelProblemCollector 
problems, Activation activation, String prefix) {
-        if (activation == null || activation.getFile() == null) {
+        if (activation == null) {
             return;
         }
 
-        ActivationFile file = activation.getFile();
-
-        String path;
-        String location;
-
-        if (file.getExists() != null && !file.getExists().isEmpty()) {
-            path = file.getExists();
-            location = "exists";
-        } else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
-            path = file.getMissing();
-            location = "missing";
-        } else {
-            return;
-        }
-
-        if (hasProjectExpression(path)) {
-            Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
-            while (matcher.find()) {
-                String propertyName = matcher.group(0);
-                if (!"${project.basedir}".equals(propertyName)) {
+        final Deque<ActivationFrame> stk = new LinkedList<>();
+
+        final Supplier<String> pathSupplier = () -> {
+            final boolean parallel = false;
+            return StreamSupport.stream(((Iterable<ActivationFrame>) 
stk::descendingIterator).spliterator(), parallel)
+                    .map(ActivationFrame::location)
+                    .collect(Collectors.joining("."));
+        };
+        final Supplier<InputLocation> locationSupplier = () -> {
+            if (stk.size() < 2) {
+                return null;
+            }
+            Iterator<ActivationFrame> f = stk.iterator();
+
+            String location = f.next().location;
+            ActivationFrame parent = f.next();
+
+            return parent.parent.map(p -> 
p.getLocation(location)).orElse(null);
+        };
+        final UnaryOperator<String> transformer = s -> {
+            if (hasProjectExpression(s)) {
+                String path = pathSupplier.get();
+                Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
+                while (matcher.find()) {
+                    String propertyName = matcher.group(0);
+
+                    if (path.startsWith("activation.file.") && 
"${project.basedir}".equals(propertyName)) {
+                        continue;
+                    }
                     addViolation(
                             problems,
                             Severity.WARNING,
                             Version.V30,
-                            prefix + "activation.file." + location,
+                            prefix + path,
                             null,
-                            "Failed to interpolate file location " + path + ": 
" + propertyName
+                            "Failed to interpolate profile activation property 
" + s + ": " + propertyName
                                     + " expressions are not supported during 
profile activation.",
-                            file.getLocation(location));
+                            locationSupplier.get());
                 }
             }
-        }
+            return s;
+        };
+        new ActivationWalker(stk, transformer).transformActivation(activation);
     }
 
     private void validate20RawPlugins(
diff --git 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
index bc84b81c21..69ec67a2f4 100644
--- 
a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
+++ 
b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java
@@ -38,10 +38,13 @@ import java.util.Optional;
 import java.util.Properties;
 import java.util.concurrent.Callable;
 import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.maven.api.VersionRange;
 import org.apache.maven.api.feature.Features;
+import org.apache.maven.api.model.ActivationFile;
 import org.apache.maven.api.model.Exclusion;
 import org.apache.maven.api.model.InputSource;
 import org.apache.maven.api.services.VersionParserException;
@@ -57,6 +60,7 @@ import org.apache.maven.model.Plugin;
 import org.apache.maven.model.PluginManagement;
 import org.apache.maven.model.Profile;
 import org.apache.maven.model.building.ModelProblem.Severity;
+import org.apache.maven.model.building.ModelProblem.Version;
 import org.apache.maven.model.composition.DependencyManagementImporter;
 import org.apache.maven.model.inheritance.InheritanceAssembler;
 import org.apache.maven.model.interpolation.ModelInterpolator;
@@ -82,11 +86,14 @@ import org.apache.maven.model.resolution.ModelResolver;
 import org.apache.maven.model.resolution.UnresolvableModelException;
 import org.apache.maven.model.resolution.WorkspaceModelResolver;
 import org.apache.maven.model.superpom.SuperPomProvider;
+import org.apache.maven.model.v4.MavenTransformer;
 import org.apache.maven.model.validation.DefaultModelValidator;
 import org.apache.maven.model.validation.ModelValidator;
 import org.apache.maven.model.version.ModelVersionParser;
 import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.Interpolator;
 import org.codehaus.plexus.interpolation.MapBasedValueSource;
+import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
 import org.eclipse.sisu.Nullable;
 
@@ -897,69 +904,89 @@ public class DefaultModelBuilder implements ModelBuilder {
             List<org.apache.maven.api.model.Profile> profiles,
             DefaultProfileActivationContext context,
             DefaultModelProblemCollector problems) {
-        List<org.apache.maven.api.model.Profile> newProfiles = null;
-        for (int index = 0; index < profiles.size(); index++) {
-            org.apache.maven.api.model.Profile profile = profiles.get(index);
-            org.apache.maven.api.model.Activation activation = 
profile.getActivation();
-            if (activation != null) {
-                org.apache.maven.api.model.ActivationFile file = 
activation.getFile();
-                if (file != null) {
-                    String oldExists = file.getExists();
-                    if (isNotEmpty(oldExists)) {
+        if (profiles.stream()
+                .map(org.apache.maven.api.model.Profile::getActivation)
+                .noneMatch(Objects::nonNull)) {
+            return profiles;
+        }
+        final Interpolator xform = new RegexBasedInterpolator();
+        xform.setCacheAnswers(true);
+        Stream.of(context.getUserProperties(), context.getSystemProperties())
+                .map(MapBasedValueSource::new)
+                .forEach(xform::addValueSource);
+
+        class ProfileInterpolator extends MavenTransformer
+                implements UnaryOperator<org.apache.maven.api.model.Profile> {
+            ProfileInterpolator() {
+                super(s -> {
+                    if (isNotEmpty(s)) {
                         try {
-                            String newExists = interpolate(oldExists, context);
-                            if (!Objects.equals(oldExists, newExists)) {
-                                if (newProfiles == null) {
-                                    newProfiles = new ArrayList<>(profiles);
-                                }
-                                newProfiles.set(
-                                        index, 
profile.withActivation(activation.withFile(file.withExists(newExists))));
-                            }
+                            return xform.interpolate(s);
                         } catch (InterpolationException e) {
-                            addInterpolationProblem(problems, file, oldExists, 
e, "exists");
-                        }
-                    } else {
-                        String oldMissing = file.getMissing();
-                        if (isNotEmpty(oldMissing)) {
-                            try {
-                                String newMissing = interpolate(oldMissing, 
context);
-                                if (!Objects.equals(oldMissing, newMissing)) {
-                                    if (newProfiles == null) {
-                                        newProfiles = new 
ArrayList<>(profiles);
-                                    }
-                                    newProfiles.set(
-                                            index,
-                                            
profile.withActivation(activation.withFile(file.withMissing(newMissing))));
-                                }
-                            } catch (InterpolationException e) {
-                                addInterpolationProblem(problems, file, 
oldMissing, e, "missing");
-                            }
+                            problems.add(new 
ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
+                                    .setMessage(e.getMessage())
+                                    .setException(e));
                         }
                     }
+                    return s;
+                });
+            }
+
+            @Override
+            public org.apache.maven.api.model.Profile 
apply(org.apache.maven.api.model.Profile p) {
+                return org.apache.maven.api.model.Profile.newBuilder(p)
+                        .activation(transformActivation(p.getActivation()))
+                        .build();
+            }
+
+            @Override
+            protected ActivationFile.Builder transformActivationFile_Missing(
+                    Supplier<? extends ActivationFile.Builder> creator,
+                    ActivationFile.Builder builder,
+                    ActivationFile target) {
+                final String path = target.getMissing();
+                final String xformed = transformPath(path, target, "missing");
+                return xformed != path ? (builder != null ? builder : 
creator.get()).missing(xformed) : builder;
+            }
+
+            @Override
+            protected ActivationFile.Builder transformActivationFile_Exists(
+                    Supplier<? extends ActivationFile.Builder> creator,
+                    ActivationFile.Builder builder,
+                    ActivationFile target) {
+                final String path = target.getExists();
+                final String xformed = transformPath(path, target, "exists");
+                return xformed != path ? (builder != null ? builder : 
creator.get()).exists(xformed) : builder;
+            }
+
+            private String transformPath(String path, ActivationFile target, 
String locationKey) {
+                if (isNotEmpty(path)) {
+                    try {
+                        return 
profileActivationFilePathInterpolator.interpolate(path, context);
+                    } catch (InterpolationException e) {
+                        addInterpolationProblem(problems, target, path, e, 
locationKey);
+                    }
                 }
+                return path;
             }
         }
-        return newProfiles != null ? newProfiles : profiles;
+        return profiles.stream().map(new ProfileInterpolator()).toList();
     }
 
     private static void addInterpolationProblem(
             DefaultModelProblemCollector problems,
-            org.apache.maven.api.model.ActivationFile file,
+            org.apache.maven.api.model.InputLocationTracker target,
             String path,
             InterpolationException e,
             String locationKey) {
         problems.add(new ModelProblemCollectorRequest(Severity.ERROR, 
ModelProblem.Version.BASE)
                 .setMessage("Failed to interpolate file location " + path + ": 
" + e.getMessage())
-                .setLocation(Optional.ofNullable(file.getLocation(locationKey))
+                
.setLocation(Optional.ofNullable(target.getLocation(locationKey))
                         .map(InputLocation::new)
                         .orElse(null))
                 .setException(e));
     }
 
-    private String interpolate(String path, ProfileActivationContext context) 
throws InterpolationException {
-        return isNotEmpty(path) ? 
profileActivationFilePathInterpolator.interpolate(path, context) : path;
-    }
-
     private static boolean isNotEmpty(String string) {
         return string != null && !string.isEmpty();
     }
diff --git 
a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
 
b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
index d2553c455d..246e2f8b13 100644
--- 
a/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
+++ 
b/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java
@@ -25,17 +25,28 @@ import javax.inject.Singleton;
 import java.io.File;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import org.apache.maven.api.model.Activation;
 import org.apache.maven.api.model.ActivationFile;
+import org.apache.maven.api.model.ActivationOS;
+import org.apache.maven.api.model.ActivationProperty;
 import org.apache.maven.api.model.Build;
 import org.apache.maven.api.model.BuildBase;
 import org.apache.maven.api.model.Dependency;
@@ -61,6 +72,7 @@ import org.apache.maven.model.building.ModelProblemCollector;
 import org.apache.maven.model.building.ModelProblemCollectorRequest;
 import org.apache.maven.model.interpolation.ModelVersionProcessor;
 import org.apache.maven.model.v4.MavenModelVersion;
+import org.apache.maven.model.v4.MavenTransformer;
 
 /**
  */
@@ -82,6 +94,196 @@ public class DefaultModelValidator implements 
ModelValidator {
 
     private static final String EMPTY = "";
 
+    private record ActivationFrame(String location, Optional<? extends 
InputLocationTracker> parent) {}
+
+    private static class ActivationWalker extends MavenTransformer {
+
+        private final Deque<ActivationFrame> stk;
+
+        ActivationWalker(Deque<ActivationFrame> stk, UnaryOperator<String> 
transformer) {
+            super(transformer);
+            this.stk = stk;
+        }
+
+        private ActivationFrame nextFrame(String property) {
+            return new ActivationFrame(property, Optional.empty());
+        }
+
+        private <P> ActivationFrame nextFrame(String property, Function<P, 
InputLocationTracker> child) {
+            @SuppressWarnings("unchecked")
+            final Optional<P> parent = (Optional<P>) stk.peek().parent;
+            return new ActivationFrame(property, parent.map(child));
+        }
+
+        @Override
+        public Activation transformActivation(Activation target) {
+            stk.push(new ActivationFrame("activation", Optional.of(target)));
+            try {
+                return super.transformActivation(target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_ActiveByDefault(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            return builder;
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_File(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("file", Activation::getFile));
+            Optional.ofNullable(target.getFile());
+            try {
+                return super.transformActivation_File(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationFile.Builder transformActivationFile_Exists(
+                Supplier<? extends ActivationFile.Builder> creator,
+                ActivationFile.Builder builder,
+                ActivationFile target) {
+            stk.push(nextFrame("exists"));
+            try {
+                return super.transformActivationFile_Exists(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationFile.Builder transformActivationFile_Missing(
+                Supplier<? extends ActivationFile.Builder> creator,
+                ActivationFile.Builder builder,
+                ActivationFile target) {
+            stk.push(nextFrame("missing"));
+            try {
+                return super.transformActivationFile_Missing(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Jdk(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("jdk"));
+            try {
+                return super.transformActivation_Jdk(creator, builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Os(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("os", Activation::getOs));
+            try {
+                return super.transformActivation_Os(creator, builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Arch(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("arch"));
+            try {
+                return super.transformActivationOS_Arch(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Family(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("family"));
+            try {
+                return super.transformActivationOS_Family(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Name(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("name"));
+            try {
+                return super.transformActivationOS_Name(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationOS.Builder transformActivationOS_Version(
+                Supplier<? extends ActivationOS.Builder> creator, 
ActivationOS.Builder builder, ActivationOS target) {
+            stk.push(nextFrame("version"));
+            try {
+                return super.transformActivationOS_Version(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Packaging(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("packaging"));
+            try {
+                return super.transformActivation_Packaging(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected Activation.Builder transformActivation_Property(
+                Supplier<? extends Activation.Builder> creator, 
Activation.Builder builder, Activation target) {
+            stk.push(nextFrame("property", Activation::getProperty));
+            try {
+                return super.transformActivation_Property(creator, builder, 
target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationProperty.Builder transformActivationProperty_Name(
+                Supplier<? extends ActivationProperty.Builder> creator,
+                ActivationProperty.Builder builder,
+                ActivationProperty target) {
+            stk.push(nextFrame("name"));
+            try {
+                return super.transformActivationProperty_Name(creator, 
builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+
+        @Override
+        protected ActivationProperty.Builder transformActivationProperty_Value(
+                Supplier<? extends ActivationProperty.Builder> creator,
+                ActivationProperty.Builder builder,
+                ActivationProperty target) {
+            stk.push(nextFrame("value"));
+            try {
+                return super.transformActivationProperty_Value(creator, 
builder, target);
+            } finally {
+                stk.pop();
+            }
+        }
+    }
+
     private final Set<String> validCoordinateIds = new HashSet<>();
 
     private final Set<String> validProfileIds = new HashSet<>();
@@ -288,42 +490,52 @@ public class DefaultModelValidator implements 
ModelValidator {
     }
 
     private void validate30RawProfileActivation(ModelProblemCollector 
problems, Activation activation, String prefix) {
-        if (activation == null || activation.getFile() == null) {
-            return;
-        }
-
-        ActivationFile file = activation.getFile();
-
-        String path;
-        String location;
-
-        if (file.getExists() != null && !file.getExists().isEmpty()) {
-            path = file.getExists();
-            location = "exists";
-        } else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
-            path = file.getMissing();
-            location = "missing";
-        } else {
+        if (activation == null) {
             return;
         }
-
-        if (hasProjectExpression(path)) {
-            Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
-            while (matcher.find()) {
-                String propertyName = matcher.group(0);
-                if (!"${project.basedir}".equals(propertyName)) {
+        final Deque<ActivationFrame> stk = new LinkedList<>();
+
+        final Supplier<String> pathSupplier = () -> {
+            final boolean parallel = false;
+            return StreamSupport.stream(((Iterable<ActivationFrame>) 
stk::descendingIterator).spliterator(), parallel)
+                    .map(ActivationFrame::location)
+                    .collect(Collectors.joining("."));
+        };
+        final Supplier<InputLocation> locationSupplier = () -> {
+            if (stk.size() < 2) {
+                return null;
+            }
+            Iterator<ActivationFrame> f = stk.iterator();
+
+            String location = f.next().location;
+            ActivationFrame parent = f.next();
+
+            return parent.parent.map(p -> 
p.getLocation(location)).orElse(null);
+        };
+        final UnaryOperator<String> transformer = s -> {
+            if (hasProjectExpression(s)) {
+                String path = pathSupplier.get();
+                Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
+                while (matcher.find()) {
+                    String propertyName = matcher.group(0);
+
+                    if (path.startsWith("activation.file.") && 
"${project.basedir}".equals(propertyName)) {
+                        continue;
+                    }
                     addViolation(
                             problems,
                             Severity.WARNING,
                             Version.V30,
-                            prefix + "activation.file." + location,
+                            prefix + path,
                             null,
-                            "Failed to interpolate file location " + path + ": 
" + propertyName
+                            "Failed to interpolate profile activation property 
" + s + ": " + propertyName
                                     + " expressions are not supported during 
profile activation.",
-                            file.getLocation(location));
+                            locationSupplier.get());
                 }
             }
-        }
+            return s;
+        };
+        new ActivationWalker(stk, transformer).transformActivation(activation);
     }
 
     private void validate20RawPlugins(
diff --git 
a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
 
b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
index 946002f6d2..0fd9041929 100644
--- 
a/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
+++ 
b/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java
@@ -20,6 +20,8 @@ package org.apache.maven.model.validation;
 
 import java.io.InputStream;
 import java.util.List;
+import java.util.Properties;
+import java.util.function.UnaryOperator;
 
 import org.apache.maven.model.Model;
 import org.apache.maven.model.building.DefaultModelBuildingRequest;
@@ -50,32 +52,40 @@ class DefaultModelValidatorTest {
     }
 
     private SimpleProblemCollector validate(String pom) throws Exception {
-        return validateEffective(pom, 
ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
+        return validateEffective(pom, UnaryOperator.identity());
     }
 
     private SimpleProblemCollector validateRaw(String pom) throws Exception {
-        return validateRaw(pom, ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
+        return validateRaw(pom, UnaryOperator.identity());
     }
 
     private SimpleProblemCollector validateEffective(String pom, int level) 
throws Exception {
-        ModelBuildingRequest request = new 
DefaultModelBuildingRequest().setValidationLevel(level);
+        return validateEffective(pom, mbr -> mbr.setValidationLevel(level));
+    }
 
+    private SimpleProblemCollector validateEffective(String pom, 
UnaryOperator<ModelBuildingRequest> requestConfigurer)
+            throws Exception {
         Model model = read(pom);
 
         SimpleProblemCollector problems = new SimpleProblemCollector(model);
 
-        validator.validateEffectiveModel(model, request, problems);
+        validator.validateEffectiveModel(model, requestConfigurer.apply(new 
DefaultModelBuildingRequest()), problems);
 
         return problems;
     }
 
     private SimpleProblemCollector validateRaw(String pom, int level) throws 
Exception {
-        ModelBuildingRequest request = new 
DefaultModelBuildingRequest().setValidationLevel(level);
+        return validateRaw(pom, mbr -> mbr.setValidationLevel(level));
+    }
 
+    private SimpleProblemCollector validateRaw(String pom, 
UnaryOperator<ModelBuildingRequest> requestConfigurer)
+            throws Exception {
         Model model = read(pom);
 
         SimpleProblemCollector problems = new SimpleProblemCollector(model);
 
+        ModelBuildingRequest request = requestConfigurer.apply(new 
DefaultModelBuildingRequest());
+
         validator.validateFileModel(model, request, problems);
 
         validator.validateRawModel(model, request, problems);
@@ -818,24 +828,52 @@ class DefaultModelValidatorTest {
 
     @Test
     void profileActivationWithAllowedExpression() throws Exception {
-        SimpleProblemCollector result = 
validateRaw("raw-model/profile-activation-file-with-allowed-expressions.xml");
+        SimpleProblemCollector result = validateRaw(
+                
"raw-model/profile-activation-file-with-allowed-expressions.xml",
+                mbr -> mbr.setUserProperties(new Properties() {
+                    private static final long serialVersionUID = 1L;
+
+                    {
+                        setProperty("foo", "foo");
+                        setProperty("bar", "foo");
+                    }
+                }));
         assertViolations(result, 0, 0, 0);
     }
 
     @Test
-    void profileActivationWithProjectExpression() throws Exception {
+    void profileActivationFileWithProjectExpression() throws Exception {
         SimpleProblemCollector result = 
validateRaw("raw-model/profile-activation-file-with-project-expressions.xml");
         assertViolations(result, 0, 0, 2);
 
         assertEquals(
                 
"'profiles.profile[exists-project-version].activation.file.exists' "
-                        + "Failed to interpolate file location 
${project.version}/test.txt: "
+                        + "Failed to interpolate profile activation property 
${project.version}/test.txt: "
                         + "${project.version} expressions are not supported 
during profile activation.",
                 result.getWarnings().get(0));
 
         assertEquals(
                 
"'profiles.profile[missing-project-version].activation.file.missing' "
-                        + "Failed to interpolate file location 
${project.version}/test.txt: "
+                        + "Failed to interpolate profile activation property 
${project.version}/test.txt: "
+                        + "${project.version} expressions are not supported 
during profile activation.",
+                result.getWarnings().get(1));
+    }
+
+    @Test
+    void profileActivationPropertyWithProjectExpression() throws Exception {
+        SimpleProblemCollector result =
+                
validateRaw("raw-model/profile-activation-property-with-project-expressions.xml");
+        assertViolations(result, 0, 0, 2);
+
+        assertEquals(
+                
"'profiles.profile[property-name-project-version].activation.property.name' "
+                        + "Failed to interpolate profile activation property 
${project.version}: "
+                        + "${project.version} expressions are not supported 
during profile activation.",
+                result.getWarnings().get(0));
+
+        assertEquals(
+                
"'profiles.profile[property-value-project-version].activation.property.value' "
+                        + "Failed to interpolate profile activation property 
${project.version}: "
                         + "${project.version} expressions are not supported 
during profile activation.",
                 result.getWarnings().get(1));
     }
diff --git 
a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
 
b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
index a4beb6238f..72b6747f18 100644
--- 
a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
+++ 
b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
@@ -60,5 +60,23 @@ under the License.
       </activation>
     </profile>
 
+    <profile>
+      <id>dynamic-property-available</id>
+      <activation>
+        <property>
+          <name>${activationProperty}</name>
+        </property>
+      </activation>
+    </profile>
+
+    <profile>
+      <id>matches-another-property</id>
+      <activation>
+        <property>
+          <name>foo</name>
+          <value>${bar}</value>
+        </property>
+      </activation>
+    </profile>
   </profiles>
 </project>
diff --git 
a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
 
b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml
similarity index 68%
copy from 
maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
copy to 
maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml
index a4beb6238f..8bcf89f66f 100644
--- 
a/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-file-with-allowed-expressions.xml
+++ 
b/maven-model-builder/src/test/resources/poms/validation/raw-model/profile-activation-property-with-project-expressions.xml
@@ -26,37 +26,22 @@ under the License.
   <packaging>pom</packaging>
 
   <profiles>
-    <profile>
-      <id>exists-basedir</id>
-      <activation>
-        <file>
-          <exists>${basedir}/test.txt</exists>
-        </file>
-      </activation>
-    </profile>
-    <profile>
-      <id>missing-basedir</id>
-      <activation>
-        <file>
-          <missing>${basedir}/test.txt</missing>
-        </file>
-      </activation>
-    </profile>
 
     <profile>
-      <id>exists-project-basedir</id>
+      <id>property-name-project-version</id>
       <activation>
-        <file>
-          <exists>${project.basedir}/test.txt</exists>
-        </file>
+        <property>
+          <name>${project.version}</name>
+        </property>
       </activation>
     </profile>
     <profile>
-      <id>missing-project-basedir</id>
+      <id>property-value-project-version</id>
       <activation>
-        <file>
-          <missing>${project.basedir}/test.txt</missing>
-        </file>
+        <property>
+          <name>project.version</name>
+          <value>${project.version}</value>
+        </property>
       </activation>
     </profile>
 

Reply via email to