gnodet commented on code in PR #13249:
URL: https://github.com/apache/maven/pull/13249#discussion_r4093973449


##########
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/filter/CoordinatePredicate.java:
##########
@@ -0,0 +1,241 @@
+/*
+ * 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.lifecycle.internal.filter;
+
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.api.plugin.descriptor.PluginDescriptor;
+
+/**
+ * A {@link FilterPredicate} that matches {@link MojoExecution}s by plugin 
coordinate or prefix.
+ *
+ * <h2>Syntax: {@code ([G[:A]]|P)[:v][:g[@e]]}</h2>
+ *
+ * <p>The {@code @} separator for execution ID is compatible with Maven's 
existing
+ * {@code plugin:version:goal@executionId} notation used in
+ * {@code DefaultLifecycleExecutionPlanCalculator} for goal tasks.
+ *
+ * <p>Matching uses {@link MojoExecution#getDescriptor()} for goal-level 
fields and
+ * {@link MojoExecution#getPlugin()} for plugin-level coordinates (groupId, 
artifactId, version,
+ * goal prefix).
+ *
+ * <p>Forms:
+ * <ul>
+ *   <li>{@code *} — matches every mojo execution</li>
+ *   <li>{@code :A} — any groupId, specific artifactId (e.g. {@code 
:maven-enforcer-plugin})</li>
+ *   <li>{@code G:A} — exact groupId:artifactId (e.g. {@code 
org.apache.maven.plugins:maven-enforcer-plugin})</li>
+ *   <li>{@code P} — plugin prefix (e.g. {@code enforcer}), resolved against
+ *       {@link MojoExecution#getMojoDescriptor()} goal prefix</li>
+ *   <li>{@code P:v:g} — prefix + version + goal</li>
+ *   <li>{@code P:v:g@e} — prefix + version + goal + executionId</li>
+ * </ul>
+ *
+ * <p>When any field is {@code null} or not specified, it is treated as a 
wildcard (matches any value).
+ *
+ * @since 4.1.0
+ */
+public class CoordinatePredicate implements FilterPredicate {
+
+    /** Wildcard token — matches any value. */
+    private static final String ANY = null;
+
+    private final boolean matchAll;
+    private final String groupId; // null = any, non-null = exact match
+    private final String artifactId; // null = any, non-null = exact match
+    private final String prefix; // null = not used, non-null = match by goal 
prefix
+    private final String version; // null = any
+    private final String goal; // null = any
+    private final String executionId; // null = any
+
+    /** Wildcard predicate — matches everything. */
+    public static final CoordinatePredicate MATCH_ALL = new 
CoordinatePredicate();
+
+    private CoordinatePredicate() {
+        this.matchAll = true;
+        this.groupId = ANY;
+        this.artifactId = ANY;
+        this.prefix = ANY;
+        this.version = ANY;
+        this.goal = ANY;
+        this.executionId = ANY;
+    }
+
+    private CoordinatePredicate(
+            String groupId, String artifactId, String prefix, String version, 
String goal, String executionId) {
+        this.matchAll = false;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.prefix = prefix;
+        this.version = version;
+        this.goal = goal;
+        this.executionId = executionId;
+    }
+
+    /**
+     * Parses a coordinate predicate from a string token.
+     *
+     * <p>Supported forms:
+     * <ul>
+     *   <li>{@code *} — match all</li>
+     *   <li>{@code :A} — by artifactId only</li>
+     *   <li>{@code G:A} — by groupId:artifactId (token contains {@code :} 
after first char)</li>
+     *   <li>{@code P} — by prefix</li>
+     *   <li>{@code P:v:g} — by prefix + version + goal</li>
+     *   <li>{@code P:v:g@e} — by prefix + version + goal + executionId</li>
+     * </ul>
+     *
+     * @param token the filter expression token (not {@code null}, not blank)
+     * @return the parsed predicate
+     */
+    public static CoordinatePredicate parse(String token) {
+        if ("*".equals(token)) {
+            return MATCH_ALL;
+        }
+
+        if (token.startsWith(":")) {
+            // :A form — any groupId, specific artifactId, optional :v:g[@e]
+            // e.g. ":maven-enforcer-plugin" or 
":maven-enforcer-plugin:3.0.0:enforce@enforce-id"
+            String rest = token.substring(1); // remove leading ':'
+            String[] parts = rest.split(":", 3);
+            String artifactId = emptyToNull(parts[0]);
+            String version = parts.length > 1 ? emptyToNull(parts[1]) : null;
+            String goalAndExec = parts.length > 2 ? parts[2] : null;
+            String[] ge = splitGoalExecution(goalAndExec);
+            return new CoordinatePredicate(ANY, artifactId, ANY, version, 
ge[0], ge[1]);
+        }
+
+        // Try to detect G:A form: the token contains ':' AND the part before 
the first ':' looks like a
+        // groupId (contains a '.' suggesting it's a Java package name like 
org.apache.maven).
+        // This distinguishes "org.apache.maven.plugins:maven-enforcer-plugin" 
from "enforcer:3.1.0:enforce".
+        int firstColon = token.indexOf(':');
+        if (firstColon > 0 && token.substring(0, firstColon).contains(".")) {

Review Comment:
   Fixed in 63ce016f02f6d96b684d9b778f2a0f1afa821db8: documented the 
dot-in-groupId constraint in the class Javadoc, parse() Javadoc, and inline 
comments. Added a test (dotlessGroupIdIsMisroutedToPrefixMode) that exposes the 
misrouting and demonstrates the :A workaround.



##########
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/filter/CoordinatePredicate.java:
##########
@@ -0,0 +1,241 @@
+/*
+ * 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.lifecycle.internal.filter;
+
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.api.plugin.descriptor.PluginDescriptor;
+
+/**
+ * A {@link FilterPredicate} that matches {@link MojoExecution}s by plugin 
coordinate or prefix.
+ *
+ * <h2>Syntax: {@code ([G[:A]]|P)[:v][:g[@e]]}</h2>
+ *
+ * <p>The {@code @} separator for execution ID is compatible with Maven's 
existing
+ * {@code plugin:version:goal@executionId} notation used in
+ * {@code DefaultLifecycleExecutionPlanCalculator} for goal tasks.
+ *
+ * <p>Matching uses {@link MojoExecution#getDescriptor()} for goal-level 
fields and
+ * {@link MojoExecution#getPlugin()} for plugin-level coordinates (groupId, 
artifactId, version,
+ * goal prefix).
+ *
+ * <p>Forms:
+ * <ul>
+ *   <li>{@code *} — matches every mojo execution</li>
+ *   <li>{@code :A} — any groupId, specific artifactId (e.g. {@code 
:maven-enforcer-plugin})</li>
+ *   <li>{@code G:A} — exact groupId:artifactId (e.g. {@code 
org.apache.maven.plugins:maven-enforcer-plugin})</li>
+ *   <li>{@code P} — plugin prefix (e.g. {@code enforcer}), resolved against
+ *       {@link MojoExecution#getMojoDescriptor()} goal prefix</li>
+ *   <li>{@code P:v:g} — prefix + version + goal</li>
+ *   <li>{@code P:v:g@e} — prefix + version + goal + executionId</li>
+ * </ul>
+ *
+ * <p>When any field is {@code null} or not specified, it is treated as a 
wildcard (matches any value).
+ *
+ * @since 4.1.0
+ */
+public class CoordinatePredicate implements FilterPredicate {
+
+    /** Wildcard token — matches any value. */
+    private static final String ANY = null;
+
+    private final boolean matchAll;
+    private final String groupId; // null = any, non-null = exact match
+    private final String artifactId; // null = any, non-null = exact match
+    private final String prefix; // null = not used, non-null = match by goal 
prefix
+    private final String version; // null = any
+    private final String goal; // null = any
+    private final String executionId; // null = any
+
+    /** Wildcard predicate — matches everything. */
+    public static final CoordinatePredicate MATCH_ALL = new 
CoordinatePredicate();
+
+    private CoordinatePredicate() {
+        this.matchAll = true;
+        this.groupId = ANY;
+        this.artifactId = ANY;
+        this.prefix = ANY;
+        this.version = ANY;
+        this.goal = ANY;
+        this.executionId = ANY;
+    }
+
+    private CoordinatePredicate(
+            String groupId, String artifactId, String prefix, String version, 
String goal, String executionId) {
+        this.matchAll = false;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.prefix = prefix;
+        this.version = version;
+        this.goal = goal;
+        this.executionId = executionId;
+    }
+
+    /**
+     * Parses a coordinate predicate from a string token.
+     *
+     * <p>Supported forms:
+     * <ul>
+     *   <li>{@code *} — match all</li>
+     *   <li>{@code :A} — by artifactId only</li>
+     *   <li>{@code G:A} — by groupId:artifactId (token contains {@code :} 
after first char)</li>
+     *   <li>{@code P} — by prefix</li>
+     *   <li>{@code P:v:g} — by prefix + version + goal</li>
+     *   <li>{@code P:v:g@e} — by prefix + version + goal + executionId</li>
+     * </ul>
+     *
+     * @param token the filter expression token (not {@code null}, not blank)
+     * @return the parsed predicate
+     */
+    public static CoordinatePredicate parse(String token) {
+        if ("*".equals(token)) {
+            return MATCH_ALL;
+        }
+
+        if (token.startsWith(":")) {
+            // :A form — any groupId, specific artifactId, optional :v:g[@e]
+            // e.g. ":maven-enforcer-plugin" or 
":maven-enforcer-plugin:3.0.0:enforce@enforce-id"
+            String rest = token.substring(1); // remove leading ':'
+            String[] parts = rest.split(":", 3);
+            String artifactId = emptyToNull(parts[0]);
+            String version = parts.length > 1 ? emptyToNull(parts[1]) : null;
+            String goalAndExec = parts.length > 2 ? parts[2] : null;
+            String[] ge = splitGoalExecution(goalAndExec);
+            return new CoordinatePredicate(ANY, artifactId, ANY, version, 
ge[0], ge[1]);
+        }
+
+        // Try to detect G:A form: the token contains ':' AND the part before 
the first ':' looks like a
+        // groupId (contains a '.' suggesting it's a Java package name like 
org.apache.maven).
+        // This distinguishes "org.apache.maven.plugins:maven-enforcer-plugin" 
from "enforcer:3.1.0:enforce".
+        int firstColon = token.indexOf(':');
+        if (firstColon > 0 && token.substring(0, firstColon).contains(".")) {

Review Comment:
   Fixed in 63ce016f02f6d96b684d9b778f2a0f1afa821db8: documented the 
dot-in-groupId constraint in the class Javadoc, parse() Javadoc, and inline 
comments. Added a test (dotlessGroupIdIsMisroutedToPrefixMode) that exposes the 
misrouting and demonstrates the :A workaround.



##########
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/filter/MojoExecutionFilter.java:
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.lifecycle.internal.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.api.Constants;
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.internal.impl.DefaultMojoExecution;
+
+/**
+ * Parses the {@code maven.lifecycle.filter} user property value into a list 
of {@link FilterPredicate}s,
+ * and applies them at mojo execution time in {@code MojoExecutor}.
+ *
+ * <p>The property value is a comma-separated list of predicates, OR-ed 
together:
+ * a mojo execution matching <em>any</em> predicate is skipped (a {@code 
MojoSkipped} event is fired).
+ *
+ * <p>Supported predicate forms:
+ * <ul>
+ *   <li>{@code *} — skip all mojo executions</li>
+ *   <li>{@code :A} — skip by artifactId (e.g. {@code 
:maven-enforcer-plugin})</li>
+ *   <li>{@code G:A} — skip by groupId:artifactId</li>
+ *   <li>{@code P} — skip by plugin prefix (e.g. {@code enforcer})</li>
+ *   <li>{@code P:v:g} — skip by prefix:version:goal</li>
+ *   <li>{@code P:v:g@e} — skip by prefix:version:goal@executionId</li>
+ *   <li>{@code phase(name)} — skip all mojos bound to the named phase</li>
+ * </ul>
+ *
+ * @since 4.1.0
+ */
+public class MojoExecutionFilter {
+
+    /** The name of the user property that activates the filter. */
+    public static final String PROPERTY_NAME = 
Constants.MAVEN_LIFECYCLE_FILTER;
+
+    private MojoExecutionFilter() {
+        // utility class
+    }
+
+    /**
+     * Parses a filter expression string into a list of {@link 
FilterPredicate}s.
+     *
+     * @param expression the comma-separated filter expression (may be {@code 
null} or blank)
+     * @return list of parsed predicates; empty if the expression is absent or 
blank
+     */
+    public static List<FilterPredicate> parse(String expression) {
+        if (expression == null || expression.isBlank()) {
+            return List.of();
+        }
+        List<FilterPredicate> predicates = new ArrayList<>();
+        for (String token : expression.split(",")) {
+            token = token.strip();
+            if (token.isEmpty()) {
+                continue;
+            }
+            predicates.add(parseToken(token));
+        }
+        return List.copyOf(predicates);
+    }
+
+    private static FilterPredicate parseToken(String token) {
+        if (token.startsWith("phase(") && token.endsWith(")")) {
+            String phaseName = token.substring("phase(".length(), 
token.length() - 1);

Review Comment:
   Fixed in 63ce016f02f6d96b684d9b778f2a0f1afa821db8: added a blank check 
before constructing PhasePredicate — throws IllegalArgumentException for 
phase() and phase(  ). Added emptyPhaseParensThrows and blankPhaseParensThrows 
tests.



##########
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/filter/MojoExecutionFilter.java:
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.lifecycle.internal.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.api.Constants;
+import org.apache.maven.api.MojoExecution;
+import org.apache.maven.internal.impl.DefaultMojoExecution;
+
+/**
+ * Parses the {@code maven.lifecycle.filter} user property value into a list 
of {@link FilterPredicate}s,
+ * and applies them at mojo execution time in {@code MojoExecutor}.
+ *
+ * <p>The property value is a comma-separated list of predicates, OR-ed 
together:
+ * a mojo execution matching <em>any</em> predicate is skipped (a {@code 
MojoSkipped} event is fired).
+ *
+ * <p>Supported predicate forms:
+ * <ul>
+ *   <li>{@code *} — skip all mojo executions</li>
+ *   <li>{@code :A} — skip by artifactId (e.g. {@code 
:maven-enforcer-plugin})</li>
+ *   <li>{@code G:A} — skip by groupId:artifactId</li>
+ *   <li>{@code P} — skip by plugin prefix (e.g. {@code enforcer})</li>
+ *   <li>{@code P:v:g} — skip by prefix:version:goal</li>
+ *   <li>{@code P:v:g@e} — skip by prefix:version:goal@executionId</li>
+ *   <li>{@code phase(name)} — skip all mojos bound to the named phase</li>
+ * </ul>
+ *
+ * @since 4.1.0
+ */
+public class MojoExecutionFilter {
+
+    /** The name of the user property that activates the filter. */
+    public static final String PROPERTY_NAME = 
Constants.MAVEN_LIFECYCLE_FILTER;
+
+    private MojoExecutionFilter() {
+        // utility class
+    }
+
+    /**
+     * Parses a filter expression string into a list of {@link 
FilterPredicate}s.
+     *
+     * @param expression the comma-separated filter expression (may be {@code 
null} or blank)
+     * @return list of parsed predicates; empty if the expression is absent or 
blank
+     */
+    public static List<FilterPredicate> parse(String expression) {
+        if (expression == null || expression.isBlank()) {
+            return List.of();
+        }
+        List<FilterPredicate> predicates = new ArrayList<>();
+        for (String token : expression.split(",")) {
+            token = token.strip();
+            if (token.isEmpty()) {
+                continue;
+            }
+            predicates.add(parseToken(token));
+        }
+        return List.copyOf(predicates);
+    }
+
+    private static FilterPredicate parseToken(String token) {
+        if (token.startsWith("phase(") && token.endsWith(")")) {
+            String phaseName = token.substring("phase(".length(), 
token.length() - 1);
+            return new PhasePredicate(phaseName);

Review Comment:
   Fixed in 63ce016f02f6d96b684d9b778f2a0f1afa821db8: added a blank check 
before constructing PhasePredicate — throws IllegalArgumentException for 
phase() and phase(  ). Added emptyPhaseParensThrows and blankPhaseParensThrows 
tests.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to