diff --git a/api/maven-api-plugin/pom.xml b/api/maven-api-plugin/pom.xml
index 733c1158e2e7..ff47e3d4f56d 100644
--- a/api/maven-api-plugin/pom.xml
+++ b/api/maven-api-plugin/pom.xml
@@ -89,7 +89,7 @@ under the License.
generate-sources
${project.basedir}/../../src/mdo
- 1.0.0
+ 2.0.0
src/main/mdo/lifecycle.mdo
diff --git a/api/maven-api-plugin/src/main/mdo/lifecycle.mdo b/api/maven-api-plugin/src/main/mdo/lifecycle.mdo
index f469eea4400b..8196e3aa5055 100644
--- a/api/maven-api-plugin/src/main/mdo/lifecycle.mdo
+++ b/api/maven-api-plugin/src/main/mdo/lifecycle.mdo
@@ -30,12 +30,12 @@ under the License.
LifecycleConfiguration
- 1.0.0
+ 1.0.0+
Root element of the {@code lifecycle.xml} file.
lifecycles
- 1.0.0
+ 1.0.0+
Lifecycle
*
@@ -45,19 +45,19 @@ under the License.
Lifecycle
- 1.0.0
+ 1.0.0+
A custom lifecycle mapping definition.
id
true
- 1.0.0
+ 1.0.0+
String
The ID of this lifecycle, for identification in the mojo descriptor.
phases
- 1.0.0
+ 1.0.0+
The phase mappings for this lifecycle.
Phase
@@ -68,19 +68,35 @@ under the License.
Phase
- 1.0.0
+ 1.0.0+
A phase mapping definition.
id
true
- 1.0.0
+ 1.0.0+
String
- The ID of this phase, e.g., <code>generate-sources</code>.
+ The ID of this phase, e.g., {@code generate-sources}.
+
+
+ executionPoint
+ false
+ 2.0.0+
+ String
+
+
+
+
+ priority
+ false
+ 2.0.0+
+ int
+ 0
+ If specified, identifies a within phase prioritization of executions.
executions
- 1.0.0
+ 1.0.0+
The goals to execute within the phase.
Execution
@@ -89,26 +105,51 @@ under the License.
configuration
- 1.0.0
+ 1.0.0+
DOM
Configuration to pass to all goals run in this phase.
+
+
+ 2.0.0+
+
+
+
Execution
- 1.0.0
+ 1.0.0+
A set of goals to execute.
configuration
- 1.0.0
+ 1.0.0+
DOM
Configuration to pass to the goals.
goals
- 1.0.0
+ 1.0.0+
The goals to execute.
String
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
index 6ce06531e57f..def691620841 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
@@ -70,7 +70,8 @@ public Map> calculateLifecycleMappings(
* is interested in, i.e. all phases up to and including the specified phase.
*/
- Map>> mappings = new LinkedHashMap<>();
+ Map>> mappings =
+ new TreeMap<>(new PhaseComparator(lifecycle.getPhases()));
for (String phase : lifecycle.getPhases()) {
Map> phaseBindings = new TreeMap<>();
@@ -94,7 +95,7 @@ public Map> calculateLifecycleMappings(
// if the phase is specified then I don't have to go fetch the plugin yet and pull it down
// to examine the phase it is associated to.
if (execution.getPhase() != null) {
- Map> phaseBindings = mappings.get(execution.getPhase());
+ Map> phaseBindings = getPhaseBindings(mappings, execution.getPhase());
if (phaseBindings != null) {
for (String goal : execution.getGoals()) {
MojoExecution mojoExecution = new MojoExecution(plugin, goal, execution.getId());
@@ -109,7 +110,8 @@ public Map> calculateLifecycleMappings(
MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor(
plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession());
- Map> phaseBindings = mappings.get(mojoDescriptor.getPhase());
+ Map> phaseBindings =
+ getPhaseBindings(mappings, mojoDescriptor.getPhase());
if (phaseBindings != null) {
MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId());
mojoExecution.setLifecyclePhase(mojoDescriptor.getPhase());
@@ -135,6 +137,15 @@ public Map> calculateLifecycleMappings(
return lifecycleMappings;
}
+ private Map> getPhaseBindings(
+ Map>> mappings, String phase) {
+ if (phase != null) {
+ PhaseId id = PhaseId.of(phase);
+ return mappings.get(id.phase());
+ }
+ return null;
+ }
+
private void addMojoExecution(
Map> phaseBindings, MojoExecution mojoExecution, int priority) {
List mojoExecutions = phaseBindings.computeIfAbsent(priority, k -> new ArrayList<>());
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java
new file mode 100644
index 000000000000..db5c4aa75dac
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Compares phases within the context of a specific lifecycle with secondary sorting based on the {@link PhaseId}.
+ */
+public class PhaseComparator implements Comparator {
+ /**
+ * The lifecycle phase ordering.
+ */
+ private final List lifecyclePhases;
+
+ /**
+ * Constructor.
+ *
+ * @param lifecyclePhases the lifecycle phase ordering.
+ */
+ public PhaseComparator(List lifecyclePhases) {
+ this.lifecyclePhases = lifecyclePhases;
+ }
+
+ @Override
+ public int compare(String o1, String o2) {
+ PhaseId p1 = PhaseId.of(o1);
+ PhaseId p2 = PhaseId.of(o2);
+ int i1 = lifecyclePhases.indexOf(p1.phase());
+ int i2 = lifecyclePhases.indexOf(p2.phase());
+ if (i1 == -1 && i2 == -1) {
+ // unknown phases, leave in existing order
+ return 0;
+ }
+ if (i1 == -1) {
+ // second one is known, so it comes first
+ return 1;
+ }
+ if (i2 == -1) {
+ // first one is known, so it comes first
+ return -1;
+ }
+ int rv = Integer.compare(i1, i2);
+ if (rv != 0) {
+ return rv;
+ }
+ // same phase, now compare execution points
+ i1 = p1.executionPoint().ordinal();
+ i2 = p2.executionPoint().ordinal();
+ rv = Integer.compare(i1, i2);
+ if (rv != 0) {
+ return rv;
+ }
+ // same execution point, now compare priorities (highest wins, so invert)
+ return -Integer.compare(p1.priority(), p2.priority());
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java
new file mode 100644
index 000000000000..fd291a4be7c2
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Represents where a dynamic phase should be executed within a static phase.
+ */
+public enum PhaseExecutionPoint {
+ /**
+ * Execution must occur before any executions of the phase proper. Failure of any {@link #BEFORE} dynamic phase
+ * execution will prevent the {@link #AS} phase but will not prevent any {@link #AFTER} dynamic phases.
+ */
+ BEFORE("before:"),
+ /**
+ * Execution is the execution of the phase proper. Failure of any {@link #AS} dynamic phase execution will fail
+ * the phase. Any {@link #AFTER} phases will still be execution.
+ */
+ AS(""),
+ /**
+ * Guaranteed execution dynamic phases on completion of the static phase. All {@link #AFTER} dynamic phases will
+ * be executed provided at least one {@link #BEFORE} or {@link #AS} dynamic phase has started execution.
+ */
+ AFTER("after:");
+
+ private final String prefix;
+
+ PhaseExecutionPoint(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String prefix() {
+ return prefix;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java
new file mode 100644
index 000000000000..dffb131ada4c
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Represents a parsed phase identifier.
+ */
+public class PhaseId {
+ /**
+ * Interned {@link PhaseId} instances.
+ */
+ private static final Map INSTANCES = new WeakHashMap<>();
+
+ /**
+ * The execution point of this {@link PhaseId}.
+ */
+ private final PhaseExecutionPoint executionPoint;
+
+ /**
+ * The static phase that this dynamic phase belongs to.
+ */
+ private final String phase;
+
+ /**
+ * The priority of this dynamic phase within the static phase.
+ */
+ private final int priority;
+
+ /**
+ * Parses the phase identifier.
+ *
+ * @param phase the phase identifier.
+ * @return the {@link PhaseId}.
+ */
+ public static synchronized PhaseId of(String phase) {
+ return INSTANCES.computeIfAbsent(phase, PhaseId::new);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param phase the phase identifier string.
+ */
+ private PhaseId(String phase) {
+ int executionPointEnd = phase.indexOf(':');
+ int phaseStart;
+ if (executionPointEnd == -1) {
+ executionPoint = PhaseExecutionPoint.AS;
+ phaseStart = 0;
+ } else {
+ switch (phase.substring(0, executionPointEnd)) {
+ case "before":
+ executionPoint = PhaseExecutionPoint.BEFORE;
+ phaseStart = executionPointEnd + 1;
+ break;
+ case "after":
+ executionPoint = PhaseExecutionPoint.AFTER;
+ phaseStart = executionPointEnd + 1;
+ break;
+ default:
+ executionPoint = PhaseExecutionPoint.AS;
+ phaseStart = 0;
+ break;
+ }
+ }
+ int phaseEnd = phase.indexOf('[');
+ if (phaseEnd == -1) {
+ priority = 0;
+ this.phase = phase.substring(phaseStart);
+ } else {
+ int priorityEnd = phase.lastIndexOf(']');
+ boolean havePriority;
+ int priority;
+ if (priorityEnd < phaseEnd + 1) {
+ priority = 0;
+ havePriority = false;
+ } else {
+ try {
+ priority = Integer.parseInt(phase.substring(phaseEnd + 1, priorityEnd));
+ havePriority = true;
+ } catch (NumberFormatException e) {
+ // priority must be an integer
+ priority = 0;
+ havePriority = false;
+ }
+ }
+ if (havePriority) {
+ this.phase = phase.substring(phaseStart, phaseEnd);
+ this.priority = priority;
+ } else {
+ this.phase = phase.substring(phaseStart);
+ this.priority = 0;
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PhaseId phaseId = (PhaseId) o;
+
+ if (priority() != phaseId.priority()) {
+ return false;
+ }
+ if (executionPoint() != phaseId.executionPoint()) {
+ return false;
+ }
+ return phase().equals(phaseId.phase());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = executionPoint().hashCode();
+ result = 31 * result + phase().hashCode();
+ result = 31 * result + priority();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return executionPoint().prefix() + phase() + (priority() != 0 ? "[" + priority() + ']' : "");
+ }
+
+ public PhaseExecutionPoint executionPoint() {
+ return executionPoint;
+ }
+
+ public String phase() {
+ return phase;
+ }
+
+ public int priority() {
+ return priority;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
index c037af5c4df8..a5f8f4682546 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
@@ -38,11 +38,12 @@ public void observeExecution(MojoExecution mojoExecution) {
String lifecyclePhase = mojoExecution.getLifecyclePhase();
if (lifecyclePhase != null) {
+ PhaseId phaseId = PhaseId.of(lifecyclePhase);
if (lastLifecyclePhase == null) {
- lastLifecyclePhase = lifecyclePhase;
- } else if (!lifecyclePhase.equals(lastLifecyclePhase)) {
+ lastLifecyclePhase = phaseId.phase();
+ } else if (!phaseId.phase().equals(lastLifecyclePhase)) {
project.addLifecyclePhase(lastLifecyclePhase);
- lastLifecyclePhase = lifecyclePhase;
+ lastLifecyclePhase = phaseId.phase();
}
}
@@ -56,6 +57,6 @@ public boolean isDifferentPhase(MojoExecution nextMojoExecution) {
if (lifecyclePhase == null) {
return lastLifecyclePhase != null;
}
- return !lifecyclePhase.equals(lastLifecyclePhase);
+ return !PhaseId.of(lifecyclePhase).phase().equals(lastLifecyclePhase);
}
}
diff --git a/maven-plugin-api/pom.xml b/maven-plugin-api/pom.xml
index 148f3dc375d0..f3b63da0cb63 100644
--- a/maven-plugin-api/pom.xml
+++ b/maven-plugin-api/pom.xml
@@ -117,7 +117,7 @@ under the License.
../api/maven-api-plugin/src/main/mdo/lifecycle.mdo
- 1.0.0
+ 2.0.0
diff --git a/src/mdo/reader-stax.vm b/src/mdo/reader-stax.vm
index d253537c5b4e..674bd6e7333c 100644
--- a/src/mdo/reader-stax.vm
+++ b/src/mdo/reader-stax.vm
@@ -551,6 +551,9 @@ public class ${className} {
#foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
+ #if ( ! $fieldTagName )
+ #set ( $fieldTagName = $field.name )
+ #end
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
} else if ("$fieldTagName".equals(name)) {
#if ( $locationTracking )
@@ -562,6 +565,8 @@ public class ${className} {
${classLcapName}.${field.name}(interpolatedTrimmed(value, "$fieldTagName"));
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
${classLcapName}.${field.name}(getBooleanValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, ${field.defaultValue}));
+ #elseif ( $field.type == "int" || $field.type == "Integer" )
+ ${classLcapName}.${field.name}(getIntegerValue(interpolatedTrimmed(value, "$fieldTagName"), "$fieldTagName", parser, strict, ${field.defaultValue}));
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
diff --git a/src/mdo/writer-stax.vm b/src/mdo/writer-stax.vm
index dc30f61446ec..e4c544357e39 100644
--- a/src/mdo/writer-stax.vm
+++ b/src/mdo/writer-stax.vm
@@ -226,6 +226,9 @@ public class ${className} {
#foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute && ! $Helper.xmlFieldMetadata( $field ).format )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
+ #if ( ! $fieldTagName )
+ #set ( $fieldTagName = $field.name )
+ #end
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" )
writeAttr("$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer);
@@ -236,6 +239,8 @@ public class ${className} {
#else
writeAttr("$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer);
#end
+ #elseif ( $field.type == "int" || $field.type == "Integer" )
+ writeAttr("$fieldTagName", Integer.toString(${classLcapName}.get${fieldCapName}()), serializer);
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
diff --git a/src/mdo/writer.vm b/src/mdo/writer.vm
index 33a368999301..6c3d28b5a0e5 100644
--- a/src/mdo/writer.vm
+++ b/src/mdo/writer.vm
@@ -152,6 +152,9 @@ public class ${className} {
#foreach ( $field in $allFields )
#if ( $Helper.xmlFieldMetadata( $field ).attribute )
#set ( $fieldTagName = $Helper.xmlFieldMetadata( $field ).tagName )
+ #if ( ! $fieldTagName )
+ #set ( $fieldTagName = $field.name )
+ #end
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" )
writeAttr("$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer);
@@ -162,6 +165,8 @@ public class ${className} {
#else
writeAttr("$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer);
#end
+ #elseif ( $field.type == "int" || $field.type == "Integer" )
+ writeAttr("$fieldTagName", Integer.toString(${classLcapName}.get${fieldCapName}()), serializer);
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end