diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java index 043473499..6df5e19a4 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java @@ -67,6 +67,7 @@ import org.apache.maven.tools.plugin.extractor.annotations.converter.JavaClassConverterContext; import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocBlockTagsToXhtmlConverter; import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocInlineTagsToXhtmlConverter; +import org.apache.maven.tools.plugin.extractor.annotations.datamodel.AfterAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent; @@ -729,6 +730,14 @@ private List toMojoDescriptors( } } + // @After annotations — collect from class hierarchy + List afterAnnotations = + getAfterAnnotationsFromHierarchy(mojoAnnotatedClass, mojoAnnotatedClasses); + for (AfterAnnotationContent after : afterAnnotations) { + mojoDescriptor.addAfterLink( + new ExtendedMojoDescriptor.AfterLink(after.phase(), after.type(), after.scope())); + } + mojoDescriptor.setExecutionStrategy(mojo.executionStrategy()); // ??? // mojoDescriptor.alwaysExecute(mojo.a) @@ -819,6 +828,32 @@ protected MojoAnnotatedClass findClassWithExecuteAnnotationInParentHierarchy( return findClassWithExecuteAnnotationInParentHierarchy(parent, mojoAnnotatedClasses); } + /** + * Collects all {@code @After} annotations from the class hierarchy, starting from the + * most-derived class. Unlike {@code @Execute}, {@code @After} is repeatable, so + * multiple entries may exist on a single class and across the hierarchy. + */ + protected List getAfterAnnotationsFromHierarchy( + MojoAnnotatedClass mojoAnnotatedClass, Map mojoAnnotatedClasses) { + List result = new ArrayList<>(); + collectAfterAnnotations(mojoAnnotatedClass, mojoAnnotatedClasses, result); + return result; + } + + private void collectAfterAnnotations( + MojoAnnotatedClass mojoAnnotatedClass, + Map mojoAnnotatedClasses, + List result) { + result.addAll(mojoAnnotatedClass.getAfterAnnotations()); + String parentClassName = mojoAnnotatedClass.getParentClassName(); + if (parentClassName != null && !parentClassName.isEmpty()) { + MojoAnnotatedClass parent = mojoAnnotatedClasses.get(parentClassName); + if (parent != null) { + collectAfterAnnotations(parent, mojoAnnotatedClasses, result); + } + } + } + protected Map getParametersParentHierarchy( MojoAnnotatedClass mojoAnnotatedClass, Map mojoAnnotatedClasses) { List parameterAnnotationContents = new ArrayList<>(); diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/AfterAnnotationContent.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/AfterAnnotationContent.java new file mode 100644 index 000000000..cd5d81b92 --- /dev/null +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/AfterAnnotationContent.java @@ -0,0 +1,80 @@ +/* + * 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.tools.plugin.extractor.annotations.datamodel; + +/** + * Holds scanned data from a {@code @After} annotation + * ({@code org.apache.maven.api.plugin.annotations.After}). + *

+ * The {@code @After} annotation declares that the annotated mojo must run + * after another phase has completed. It carries three attributes: + *

    + *
  • {@code phase} – the phase this mojo must run after
  • + *
  • {@code type} – the scope type ({@code PROJECT}, {@code DEPENDENCIES}, {@code CHILDREN})
  • + *
  • {@code scope} – the dependency scope
  • + *
+ *

+ * Setter method names match the annotation attribute names so that + * {@link org.apache.maven.tools.plugin.extractor.annotations.scanner.DefaultMojoAnnotationsScanner#populateAnnotationContent} + * can fill them via reflection. + * + * @since 4.0.0 + */ +public class AfterAnnotationContent { + private String phase; + + private String type; + + private String scope; + + public String phase() { + return this.phase; + } + + public void phase(String phase) { + this.phase = phase; + } + + public String type() { + return this.type; + } + + public void type(String type) { + this.type = type; + } + + public String scope() { + return this.scope; + } + + public void scope(String scope) { + this.scope = scope; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("AfterAnnotationContent"); + sb.append("{phase='").append(phase).append('\''); + sb.append(", type='").append(type).append('\''); + sb.append(", scope='").append(scope).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/DefaultMojoAnnotationsScanner.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/DefaultMojoAnnotationsScanner.java index fad64132e..e9d8dca34 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/DefaultMojoAnnotationsScanner.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/DefaultMojoAnnotationsScanner.java @@ -26,6 +26,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -42,6 +43,7 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.tools.plugin.extractor.ExtractionException; +import org.apache.maven.tools.plugin.extractor.annotations.datamodel.AfterAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent; @@ -73,6 +75,8 @@ public class DefaultMojoAnnotationsScanner implements MojoAnnotationsScanner { public static final String MOJO_V4 = MVN4_API + "Mojo"; public static final String EXECUTE_V4 = MVN4_API + "Execute"; public static final String PARAMETER_V4 = MVN4_API + "Parameter"; + public static final String AFTER_V4 = MVN4_API + "After"; + public static final String AFTERS_V4 = MVN4_API + "Afters"; public static final String MOJO_V3 = Mojo.class.getName(); public static final String EXECUTE_V3 = Execute.class.getName(); @@ -308,6 +312,37 @@ protected void analyzeVisitors(MojoClassVisitor mojoClassVisitor) throws Extract mojoAnnotatedClass.setExecute(executeAnnotationContent); } + // @After annotation(s) — v4 API only + List afterAnnotations = new ArrayList<>(); + + // single @After + mojoAnnotationVisitor = mojoClassVisitor.getAnnotationVisitor(AFTER_V4); + if (mojoAnnotationVisitor != null) { + AfterAnnotationContent afterAnnotationContent = new AfterAnnotationContent(); + populateAnnotationContent(afterAnnotationContent, mojoAnnotationVisitor); + afterAnnotations.add(afterAnnotationContent); + } + + // @Afters repeatable container wraps multiple @After in its "value" array attribute + mojoAnnotationVisitor = mojoClassVisitor.getAnnotationVisitor(AFTERS_V4); + if (mojoAnnotationVisitor != null) { + // The @Afters annotation has a "value" array attribute; visitArray("value") + // creates a sub-visitor, and each nested @After is visited via visitAnnotation + // on that sub-visitor. + MojoAnnotationVisitor arrayVisitor = mojoAnnotationVisitor.getArrayVisitor("value"); + if (arrayVisitor != null) { + for (MojoAnnotationVisitor nestedVisitor : arrayVisitor.getNestedAnnotationVisitors()) { + AfterAnnotationContent afterAnnotationContent = new AfterAnnotationContent(); + populateAnnotationContent(afterAnnotationContent, nestedVisitor); + afterAnnotations.add(afterAnnotationContent); + } + } + } + + if (!afterAnnotations.isEmpty()) { + mojoAnnotatedClass.setAfterAnnotations(afterAnnotations); + } + // @Parameter annotations List mojoParameterVisitors = mojoClassVisitor.findParameterVisitors(new HashSet<>(Arrays.asList(PARAMETER_V3, PARAMETER_V4))); diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotatedClass.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotatedClass.java index f408d8dcf..703abefc8 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotatedClass.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotatedClass.java @@ -18,10 +18,13 @@ */ package org.apache.maven.tools.plugin.extractor.annotations.scanner; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.maven.artifact.Artifact; +import org.apache.maven.tools.plugin.extractor.annotations.datamodel.AfterAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent; import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent; @@ -42,6 +45,8 @@ public class MojoAnnotatedClass { private ExecuteAnnotationContent execute; + private List afterAnnotations; + /** * key is field name */ @@ -99,6 +104,18 @@ public MojoAnnotatedClass setExecute(ExecuteAnnotationContent execute) { return this; } + public List getAfterAnnotations() { + if (this.afterAnnotations == null) { + this.afterAnnotations = new ArrayList<>(); + } + return afterAnnotations; + } + + public MojoAnnotatedClass setAfterAnnotations(List afterAnnotations) { + this.afterAnnotations = afterAnnotations; + return this; + } + public Map getParameters() { if (this.parameters == null) { this.parameters = new HashMap<>(); @@ -141,7 +158,11 @@ public void setArtifact(Artifact artifact) { } public boolean hasAnnotations() { - return !(getComponents().isEmpty() && getParameters().isEmpty() && execute == null && mojo == null); + return !(getComponents().isEmpty() + && getParameters().isEmpty() + && execute == null + && mojo == null + && getAfterAnnotations().isEmpty()); } public boolean isV4Api() { @@ -161,6 +182,7 @@ public String toString() { sb.append(", parentClassName='").append(parentClassName).append('\''); sb.append(", mojo=").append(mojo); sb.append(", execute=").append(execute); + sb.append(", afterAnnotations=").append(afterAnnotations); sb.append(", parameters=").append(parameters); sb.append(", components=").append(components); sb.append(", v4api=").append(v4Api); diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotationsScanner.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotationsScanner.java index 9495f0b93..aced90595 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotationsScanner.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/MojoAnnotationsScanner.java @@ -44,7 +44,9 @@ public interface MojoAnnotationsScanner { Execute.class.getName(), Deprecated.class.getName(), V4_API_ANNOTATIONS_PACKAGE + ".Mojo", - V4_API_ANNOTATIONS_PACKAGE + ".Execute"); + V4_API_ANNOTATIONS_PACKAGE + ".Execute", + V4_API_ANNOTATIONS_PACKAGE + ".After", + V4_API_ANNOTATIONS_PACKAGE + ".Afters"); List FIELD_LEVEL_ANNOTATIONS = Arrays.asList( Parameter.class.getName(), diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/visitors/MojoAnnotationVisitor.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/visitors/MojoAnnotationVisitor.java index 9eb420da9..2e535a07f 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/visitors/MojoAnnotationVisitor.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/scanner/visitors/MojoAnnotationVisitor.java @@ -18,11 +18,15 @@ */ package org.apache.maven.tools.plugin.extractor.annotations.scanner.visitors; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; /** * Visitor for annotations. @@ -35,6 +39,10 @@ public class MojoAnnotationVisitor extends AnnotationVisitor { private Map annotationValues = new HashMap<>(); + private List nestedAnnotationVisitors; + + private Map arrayVisitors; + MojoAnnotationVisitor(String annotationClassName) { super(Opcodes.ASM9); this.annotationClassName = annotationClassName; @@ -56,11 +64,44 @@ public void visitEnum(String name, String desc, String value) { @Override public AnnotationVisitor visitAnnotation(String name, String desc) { - return new MojoAnnotationVisitor(this.annotationClassName); + String className = desc != null ? Type.getType(desc).getClassName() : this.annotationClassName; + MojoAnnotationVisitor nested = new MojoAnnotationVisitor(className); + if (nestedAnnotationVisitors == null) { + nestedAnnotationVisitors = new ArrayList<>(); + } + nestedAnnotationVisitors.add(nested); + return nested; } @Override - public AnnotationVisitor visitArray(String s) { - return new MojoAnnotationVisitor(this.annotationClassName); + public AnnotationVisitor visitArray(String name) { + MojoAnnotationVisitor arrayVisitor = new MojoAnnotationVisitor(this.annotationClassName); + if (this.arrayVisitors == null) { + this.arrayVisitors = new HashMap<>(); + } + this.arrayVisitors.put(name, arrayVisitor); + return arrayVisitor; + } + + /** + * Returns the sub-visitor created by {@link #visitArray(String)} for the given array attribute. + * The sub-visitor's {@link #getNestedAnnotationVisitors()} contains the annotation elements. + * + * @param name the array attribute name + * @return the array sub-visitor, or {@code null} if not visited + */ + public MojoAnnotationVisitor getArrayVisitor(String name) { + return arrayVisitors != null ? arrayVisitors.get(name) : null; + } + + /** + * Returns the nested annotation visitors collected by {@link #visitAnnotation(String, String)} + * and by array sub-visitors. Used to extract elements from repeatable annotation containers + * such as {@code @Afters}. + * + * @return list of nested annotation visitors, never {@code null} + */ + public List getNestedAnnotationVisitors() { + return nestedAnnotationVisitors != null ? nestedAnnotationVisitors : Collections.emptyList(); } } diff --git a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/ExtendedMojoDescriptor.java b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/ExtendedMojoDescriptor.java index 7dad0a003..342f0fa1f 100644 --- a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/ExtendedMojoDescriptor.java +++ b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/ExtendedMojoDescriptor.java @@ -18,6 +18,9 @@ */ package org.apache.maven.tools.plugin; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; import org.apache.maven.plugin.descriptor.MojoDescriptor; @@ -31,6 +34,7 @@ public class ExtendedMojoDescriptor extends MojoDescriptor { private final boolean containsXhtmlTextValues; private boolean v4Api; + private List afterLinks; public ExtendedMojoDescriptor() { this(false); @@ -63,6 +67,66 @@ public void setV4Api(boolean v4Api) { this.v4Api = v4Api; } + /** + * Returns the after-links declared via {@code @After} annotations. + * + * @return unmodifiable list of after-links, never {@code null} + * @since 4.0.0 + */ + public List getAfterLinks() { + return afterLinks != null ? Collections.unmodifiableList(afterLinks) : Collections.emptyList(); + } + + /** + * Adds an after-link. + * + * @param afterLink the after-link to add + * @since 4.0.0 + */ + public void addAfterLink(AfterLink afterLink) { + if (this.afterLinks == null) { + this.afterLinks = new ArrayList<>(); + } + this.afterLinks.add(afterLink); + } + + /** + * Holds after-link data from the {@code @After} annotation. + *

+ * Mirrors the {@code org.apache.maven.api.plugin.descriptor.AfterLink} model class + * from the Maven 4 API without introducing a compile-time dependency on it. + * + * @since 4.0.0 + */ + public static class AfterLink { + private final String phase; + private final String type; + private final String scope; + + public AfterLink(String phase, String type, String scope) { + this.phase = phase; + this.type = type; + this.scope = scope; + } + + public String getPhase() { + return phase; + } + + public String getType() { + return type; + } + + public String getScope() { + return scope; + } + + @Override + public String toString() { + return "AfterLink{phase='" + phase + "', type='" + type + "', scope='" + scope + "'}"; + } + } + @Override public int hashCode() { return Objects.hash(containsXhtmlTextValues, v4Api); diff --git a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/PluginDescriptorFilesGenerator.java b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/PluginDescriptorFilesGenerator.java index 07ee99566..e1f6ffdc8 100644 --- a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/PluginDescriptorFilesGenerator.java +++ b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/PluginDescriptorFilesGenerator.java @@ -332,6 +332,32 @@ protected void processMojoDescriptor( GeneratorUtils.element(w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle()); } + // ---------------------------------------------------------------------- + // After links (v4 API only) + // ---------------------------------------------------------------------- + + if (isV4 && mojoDescriptor instanceof ExtendedMojoDescriptor) { + List afterLinks = + ((ExtendedMojoDescriptor) mojoDescriptor).getAfterLinks(); + if (!afterLinks.isEmpty()) { + w.startElement("afterLinks"); + for (ExtendedMojoDescriptor.AfterLink afterLink : afterLinks) { + w.startElement("afterLink"); + if (afterLink.getPhase() != null) { + GeneratorUtils.element(w, "phase", afterLink.getPhase()); + } + if (afterLink.getType() != null) { + GeneratorUtils.element(w, "type", afterLink.getType()); + } + if (afterLink.getScope() != null) { + GeneratorUtils.element(w, "scope", afterLink.getScope()); + } + w.endElement(); // afterLink + } + w.endElement(); // afterLinks + } + } + // ---------------------------------------------------------------------- // // ----------------------------------------------------------------------