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