Support String array for @Parameter's new element "defaultValues" Convert multi-value Strings into a comma-separated String of org.apache.maven.plugin.descriptor.Parameter#defaultValue The underlying Sisu implementaton supports comma separated Strings for collections (https://eclipse.dev/sisu/org.eclipse.sisu.plexus/configuration.html#Collection_Objects). as well as the Maven 4 equivalent at V4MojoConfigurator.class. The old single-value defaultValue is still supported, but for multi-value defaults, use defaultValues instead. This closes #1272
diff --git a/maven-plugin-annotations/src/main/java/org/apache/maven/plugins/annotations/Parameter.java b/maven-plugin-annotations/src/main/java/org/apache/maven/plugins/annotations/Parameter.java index 82ff5e3..ce766ff 100644 --- a/maven-plugin-annotations/src/main/java/org/apache/maven/plugins/annotations/Parameter.java +++ b/maven-plugin-annotations/src/main/java/org/apache/maven/plugins/annotations/Parameter.java
@@ -62,15 +62,30 @@ String property() default ""; /** - * parameter default value, may contain <code>${...}</code> expressions which will be interpreted at + * Parameter default value, may contain <code>${...}</code> expressions which will be interpreted at * inject time: see * <a href="/ref/current/maven-core/apidocs/org/apache/maven/plugin/PluginParameterExpressionEvaluator.html"> * PluginParameterExpressionEvaluator</a>. + * The type conversion from String into the target type is described in <a href="/guides/mini/guide-configuring-plugins.html#Configuring_Parameters">Configuring Parameters</a>. + * Cannot be specified together with {@link #defaultValues()}. * @return the default value + * @see #defaultValues() */ String defaultValue() default ""; /** + * Default values concatenated with commas in the generated plugin descriptor. + * Each String value has the format outlined in {@link #defaultValue()}. + * Multiple values are only supported for collection or array parameters. An empty array specifies no default. + * Cannot be specified together with {@link #defaultValue()}, even if either is explicitly empty. + * Descriptor generation requires maven-plugin-plugin 4.0.0-beta-4 or later. + * @return the default values + * @see #defaultValue() + * @since 4.0.0-beta-4 + */ + String[] defaultValues() default {}; + + /** * is the parameter required? * @return <code>true</code> if the Mojo should fail when the parameter cannot be injected */
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 c8ab3c4..d03a645 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
@@ -706,7 +706,6 @@ mojoDescriptor.setGoal(mojo.name()); mojoDescriptor.setOnlineRequired(mojo.requiresOnline()); - mojoDescriptor.setPhase(mojo.defaultPhase().id()); // Parameter annotations @@ -714,35 +713,7 @@ getParametersParentHierarchy(mojoAnnotatedClass, mojoAnnotatedClasses); for (ParameterAnnotationContent parameterAnnotationContent : new TreeSet<>(parameters.values())) { - org.apache.maven.plugin.descriptor.Parameter parameter = - new org.apache.maven.plugin.descriptor.Parameter(); - String name = StringUtils.isEmpty(parameterAnnotationContent.name()) - ? parameterAnnotationContent.getFieldName() - : parameterAnnotationContent.name(); - parameter.setName(name); - parameter.setAlias(parameterAnnotationContent.alias()); - parameter.setDefaultValue(parameterAnnotationContent.defaultValue()); - parameter.setDeprecated(parameterAnnotationContent.getDeprecated()); - parameter.setDescription(parameterAnnotationContent.getDescription()); - parameter.setEditable(!parameterAnnotationContent.readonly()); - String property = parameterAnnotationContent.property(); - if (StringUtils.contains(property, '$') - || StringUtils.contains(property, '{') - || StringUtils.contains(property, '}')) { - throw new InvalidParameterException( - "Invalid property for parameter '" + parameter.getName() + "', " - + "forbidden characters ${}: " + property, - null); - } - parameter.setExpression((property == null || property.isEmpty()) ? "" : "${" + property + "}"); - StringBuilder type = new StringBuilder(parameterAnnotationContent.getClassName()); - if (!parameterAnnotationContent.getTypeParameters().isEmpty()) { - type.append(parameterAnnotationContent.getTypeParameters().stream() - .collect(Collectors.joining(", ", "<", ">"))); - } - parameter.setType(type.toString()); - parameter.setSince(parameterAnnotationContent.getSince()); - parameter.setRequired(parameterAnnotationContent.required()); + org.apache.maven.plugin.descriptor.Parameter parameter = extractParameter(parameterAnnotationContent); mojoDescriptor.addParameter(parameter); } @@ -774,6 +745,78 @@ return mojoDescriptors; } + private org.apache.maven.plugin.descriptor.Parameter extractParameter( + ParameterAnnotationContent parameterAnnotationContent) throws InvalidParameterException { + org.apache.maven.plugin.descriptor.Parameter parameter = new org.apache.maven.plugin.descriptor.Parameter(); + String name = StringUtils.isEmpty(parameterAnnotationContent.name()) + ? parameterAnnotationContent.getFieldName() + : parameterAnnotationContent.name(); + parameter.setName(name); + parameter.setAlias(parameterAnnotationContent.alias()); + parameter.setDeprecated(parameterAnnotationContent.getDeprecated()); + parameter.setDescription(parameterAnnotationContent.getDescription()); + parameter.setEditable(!parameterAnnotationContent.readonly()); + String property = parameterAnnotationContent.property(); + if (StringUtils.contains(property, '$') + || StringUtils.contains(property, '{') + || StringUtils.contains(property, '}')) { + throw new InvalidParameterException( + "Invalid property for parameter '" + parameter.getName() + "', " + "forbidden characters ${}: " + + property, + null); + } + parameter.setExpression((property == null || property.isEmpty()) ? "" : "${" + property + "}"); + StringBuilder type = new StringBuilder(parameterAnnotationContent.getClassName()); + String rawType = type.toString(); + if (!parameterAnnotationContent.getTypeParameters().isEmpty()) { + type.append(parameterAnnotationContent.getTypeParameters().stream() + .collect(Collectors.joining(", ", "<", ">"))); + } + parameter.setType(type.toString()); + parameter.setSince(parameterAnnotationContent.getSince()); + parameter.setRequired(parameterAnnotationContent.required()); + String defaultValue = parameterAnnotationContent.defaultValue(); + String[] defaultValues = parameterAnnotationContent.defaultValues(); + if (defaultValue != null && defaultValues != null) { + throw new InvalidParameterException( + "Invalid default value for parameter '" + parameter.getName() + + "': defaultValue and defaultValues cannot both be specified", + null); + } + if (defaultValue != null) { + parameter.setDefaultValue(defaultValue); + } else if (defaultValues != null) { + if (defaultValues.length == 1) { + parameter.setDefaultValue(defaultValues[0]); + } else if (defaultValues.length > 1) { + // check if type is either a collection or an array, otherwise we cannot set a default value with + // multiple values + if (!isCollection(rawType)) { + throw new InvalidParameterException( + "Invalid default value for parameter '" + parameter.getName() + "' of type '" + + type.toString() + "', " + + "multiple values are only allowed for collection or array types", + null); + } + parameter.setDefaultValue(String.join(",", defaultValues)); + } + } + return parameter; + } + + /** + * Checks if the given class name represents a collection type (Collection, List, Set, or array). + * This is just a heuristics as the full class hierarchy is not known at this point. + * @param className + * @return true if the class name represents a collection type, false otherwise + */ + static boolean isCollection(String className) { + return className.endsWith("Collection") + || className.endsWith("List") + || className.endsWith("Set") + || className.endsWith("[]"); + } + protected MojoAnnotatedClass findClassWithExecuteAnnotationInParentHierarchy( MojoAnnotatedClass mojoAnnotatedClass, Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) { if (mojoAnnotatedClass.getExecute() != null) {
diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/ParameterAnnotationContent.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/ParameterAnnotationContent.java index 921e30a..71fa247 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/ParameterAnnotationContent.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/datamodel/ParameterAnnotationContent.java
@@ -19,6 +19,7 @@ package org.apache.maven.tools.plugin.extractor.annotations.datamodel; import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -38,6 +39,8 @@ private String defaultValue; + private String[] defaultValues; + private boolean required = false; private boolean readonly = false; @@ -111,6 +114,15 @@ } @Override + public String[] defaultValues() { + return defaultValues; + } + + public void defaultValues(String[] defaultValues) { + this.defaultValues = defaultValues; + } + + @Override public boolean required() { return required; } @@ -162,6 +174,7 @@ sb.append(", alias='").append(alias).append('\''); sb.append(", property='").append(property).append('\''); sb.append(", defaultValue='").append(defaultValue).append('\''); + sb.append(", defaultValues='").append(Arrays.toString(defaultValues)).append('\''); sb.append(", required=").append(required); sb.append(", readonly=").append(readonly); sb.append(", methodSource=").append(annotationOnMethod); @@ -205,7 +218,7 @@ if (!Objects.equals(alias, that.alias)) { return false; } - if (!Objects.equals(defaultValue, that.defaultValue)) { + if (!Objects.equals(defaultValue, that.defaultValue) || !Arrays.equals(defaultValues, that.defaultValues)) { return false; } return Objects.equals(property, that.property); @@ -220,6 +233,7 @@ typeParameters, property, defaultValue, + Arrays.hashCode(defaultValues), required, readonly, annotationOnMethod);
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 2e535a0..70e2bcb 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
@@ -43,6 +43,13 @@ private Map<String, MojoAnnotationVisitor> arrayVisitors; + // only set on array sub-visitors: owner and attribute name to flush the collected elements to + private MojoAnnotationVisitor arrayOwner; + + private String arrayName; + + private List<Object> arrayElements; + MojoAnnotationVisitor(String annotationClassName) { super(Opcodes.ASM9); this.annotationClassName = annotationClassName; @@ -54,7 +61,26 @@ @Override public void visit(String name, Object value) { - annotationValues.put(name, value); + if (arrayOwner != null && name == null) { + if (arrayElements == null) { + arrayElements = new ArrayList<>(); + } + arrayElements.add(value); + } else { + annotationValues.put(name, value); + } + } + + @Override + public void visitEnd() { + if (arrayOwner == null || nestedAnnotationVisitors != null) { + return; + } + if (arrayElements == null) { + arrayOwner.annotationValues.put(arrayName, new String[0]); + } else if (arrayElements.stream().allMatch(String.class::isInstance)) { + arrayOwner.annotationValues.put(arrayName, arrayElements.toArray(new String[0])); + } } @Override @@ -76,6 +102,8 @@ @Override public AnnotationVisitor visitArray(String name) { MojoAnnotationVisitor arrayVisitor = new MojoAnnotationVisitor(this.annotationClassName); + arrayVisitor.arrayOwner = this; + arrayVisitor.arrayName = name; if (this.arrayVisitors == null) { this.arrayVisitors = new HashMap<>(); }
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java index 5cea9d3..a4d5442 100644 --- a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java +++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java
@@ -38,9 +38,12 @@ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.descriptor.InvalidParameterException; import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.tools.plugin.DefaultPluginToolsRequest; import org.apache.maven.tools.plugin.extractor.ExtractionException; @@ -52,6 +55,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -62,8 +66,8 @@ MojoDescriptor extractDescriptorFromMojoClass(Class<? extends AbstractMojo> mojoClass) throws InvalidPluginDescriptorException, ExtractionException, IOException, URISyntaxException { // copy class to an empty tmp directory - Path sourceClass = Paths.get( - mojoClass.getResource(mojoClass.getSimpleName() + ".class").toURI()); + String classFile = mojoClass.getName().substring(mojoClass.getName().lastIndexOf('.') + 1) + ".class"; + Path sourceClass = Paths.get(mojoClass.getResource(classFile).toURI()); Files.copy(sourceClass, targetDir.resolve(sourceClass.getFileName())); JavaAnnotationsMojoDescriptorExtractor mojoDescriptorExtractor = new JavaAnnotationsMojoDescriptorExtractor(); mojoDescriptorExtractor.mojoAnnotationsScanner = new DefaultMojoAnnotationsScanner(); @@ -147,6 +151,88 @@ assertEquals("Says hello.", mojoDescriptor.getDescription()); } + @Test + void scanNonCollectionParametersWithMultiValueDefault() throws Exception { + assertThrows( + InvalidParameterException.class, + () -> extractDescriptorFromMojoClass(MultiDefaultValueOnNonCollectionTypeMojo.class)); + } + + @Test + void scanCollectionParametersWithMultiValueDefault() throws Exception { + MojoDescriptor mojoDescriptor = extractDescriptorFromMojoClass(MultiDefaultValueOnCollectionTypesMojo.class); + assertEquals(4, mojoDescriptor.getParameters().size()); + mojoDescriptor.getParameters().forEach(parameter -> { + assertEquals("value1,value2", parameter.getDefaultValue()); + }); + } + + @Test + void scanScalarAndArrayDefaults() throws Exception { + MojoDescriptor descriptor = extractDescriptorFromMojoClass(DefaultValuesMojo.class); + Map<String, String> defaults = new HashMap<>(); + descriptor.getParameters().forEach(parameter -> defaults.put(parameter.getName(), parameter.getDefaultValue())); + assertEquals(4, defaults.size()); + assertEquals("scalar", defaults.get("scalar")); + assertEquals("one", defaults.get("single")); + assertNull(defaults.get("empty")); + assertNull(defaults.get("absent")); + } + + @Test + void rejectBothDefaultAttributes() { + InvalidParameterException exception = assertThrows( + InvalidParameterException.class, () -> extractDescriptorFromMojoClass(ConflictingDefaultsMojo.class)); + assertTrue(exception.getMessage().contains("defaultValue and defaultValues")); + } + + @Test + void rejectBothDefaultAttributesEvenWhenEmpty() { + assertThrows( + InvalidParameterException.class, + () -> extractDescriptorFromMojoClass(EmptyConflictingDefaultsMojo.class)); + } + + @Mojo(name = "defaults") + public static class DefaultValuesMojo extends AbstractMojo { + @Parameter(defaultValue = "scalar") + private String scalar; + + @Parameter(defaultValues = {"one"}) + private String single; + + @Parameter(defaultValues = {}) + private String empty; + + @Parameter + private String absent; + + @Override + public void execute() {} + } + + @Mojo(name = "conflicting-defaults") + public static class ConflictingDefaultsMojo extends AbstractMojo { + @Parameter( + defaultValue = "one", + defaultValues = {"two"}) + private String value; + + @Override + public void execute() {} + } + + @Mojo(name = "empty-conflicting-defaults") + public static class EmptyConflictingDefaultsMojo extends AbstractMojo { + @Parameter( + defaultValue = "", + defaultValues = {}) + private String value; + + @Override + public void execute() {} + } + /** * Compiles the named sources of {@code src/test/resources/mojo-sources} and extracts the single mojo they * contain, with the sources registered as compile source root so that the Javadoc is read.
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnCollectionTypesMojo.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnCollectionTypesMojo.java new file mode 100644 index 0000000..6a8bf2b --- /dev/null +++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnCollectionTypesMojo.java
@@ -0,0 +1,53 @@ +/* + * 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; + +import java.util.Collection; +import java.util.Set; +import java.util.SortedSet; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Execute; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +@Mojo(name = "foo", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true) +@Execute(goal = "compiler", lifecycle = "my-lifecycle", phase = LifecyclePhase.PACKAGE) +public class MultiDefaultValueOnCollectionTypesMojo extends AbstractFooMojo { + + @Parameter(defaultValues = {"value1", "value2"}) + private String[] foo; + + @Parameter(defaultValues = {"value1", "value2"}) + private Set<String> bar; + + @Parameter(defaultValues = {"value1", "value2"}) + private SortedSet<String> baz; + + @Parameter(defaultValues = {"value1", "value2"}) + private Collection<Integer>[] qux; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + // TODO Auto-generated method stub + + } +}
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnNonCollectionTypeMojo.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnNonCollectionTypeMojo.java new file mode 100644 index 0000000..5d7e5a6 --- /dev/null +++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/MultiDefaultValueOnNonCollectionTypeMojo.java
@@ -0,0 +1,40 @@ +/* + * 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; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Execute; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +@Mojo(name = "foo", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true) +@Execute(goal = "compiler", lifecycle = "my-lifecycle", phase = LifecyclePhase.PACKAGE) +public class MultiDefaultValueOnNonCollectionTypeMojo extends AbstractFooMojo { + + @Parameter(defaultValues = {"value1", "value2"}) + private String bar; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + // TODO Auto-generated method stub + + } +}