This is an automated email from the ASF dual-hosted git repository.
elharo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-archetype.git
The following commit(s) were added to refs/heads/master by this push:
new 137461e [ARCHETYPE-406][ARCHETYPE-558][ARCHETYPE-562] Support full
Velocity default value expressions for required properties; properly handle
ordering among same (#64)
137461e is described below
commit 137461e8dd71a26f53d25aa9c58e29e04127d6a7
Author: Matt Benson <[email protected]>
AuthorDate: Wed Jul 7 13:10:05 2021 -0500
[ARCHETYPE-406][ARCHETYPE-558][ARCHETYPE-562] Support full Velocity default
value expressions for required properties; properly handle ordering among same
(#64)
* add interactive configuration test
* [ARCHETYPE-406]
* [ARCHETYPE-558]
* [ARCHETYPE-562]
- Support full Velocity default value expressions for required properties;
properly handle ordering among same
* rename method and parameters for clarity
* javadoc
---
.../DefaultArchetypeGenerationConfigurator.java | 274 +++++++++++----------
...efaultArchetypeGenerationConfigurator2Test.java | 85 ++++++-
.../generation/RequiredPropertyComparatorTest.java | 14 ++
3 files changed, 242 insertions(+), 131 deletions(-)
diff --git
a/maven-archetype-plugin/src/main/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator.java
b/maven-archetype-plugin/src/main/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator.java
index 3720ef0..69a7ae1 100644
---
a/maven-archetype-plugin/src/main/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator.java
+++
b/maven-archetype-plugin/src/main/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator.java
@@ -37,6 +37,11 @@ import
org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.apache.velocity.runtime.parser.node.ASTReference;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+import org.apache.velocity.runtime.visitor.BaseVisitor;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.components.interactivity.PrompterException;
@@ -44,12 +49,17 @@ import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.StringUtils;
import java.io.IOException;
+import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
+import java.util.Set;
// TODO: this seems to have more responsibilities than just a configurator
@Component( role = ArchetypeGenerationConfigurator.class, hint = "default" )
@@ -159,6 +169,9 @@ public class DefaultArchetypeGenerationConfigurator
throw new ArchetypeGenerationConfigurationFailure( "The defined
artifact is not an archetype" );
}
+ List<String> propertiesRequired =
archetypeConfiguration.getRequiredProperties();
+ Collections.sort( propertiesRequired, new RequiredPropertyComparator(
archetypeConfiguration ) );
+
Context context = new VelocityContext();
if ( interactiveMode.booleanValue() )
{
@@ -168,70 +181,45 @@ public class DefaultArchetypeGenerationConfigurator
context.put( Constants.VERSION, ad.getVersion() );
while ( !confirmed )
{
- List<String> propertiesRequired =
archetypeConfiguration.getRequiredProperties();
- getLogger().debug( "Required properties before content sort: "
+ propertiesRequired );
- Collections.sort( propertiesRequired, new
RequiredPropertyComparator( archetypeConfiguration ) );
- getLogger().debug( "Required properties after content sort: "
+ propertiesRequired );
-
- if ( !archetypeConfiguration.isConfigured() )
+ if ( archetypeConfiguration.isConfigured() )
{
for ( String requiredProperty : propertiesRequired )
{
- if ( !archetypeConfiguration.isConfigured(
requiredProperty ) )
- {
- if ( "package".equals( requiredProperty ) )
- {
- // if the asked property is 'package', then
- // use its default and if not defined,
- // use the 'groupId' property value.
- String packageDefault =
archetypeConfiguration.getDefaultValue( requiredProperty );
- packageDefault = ( null == packageDefault ||
"".equals( packageDefault ) )
- ? archetypeConfiguration.getProperty(
"groupId" )
- : archetypeConfiguration.getDefaultValue(
requiredProperty );
-
- String value =
- getTransitiveDefaultValue( packageDefault,
archetypeConfiguration, requiredProperty,
- context );
-
- value =
archetypeGenerationQueryer.getPropertyValue( requiredProperty, value, null );
-
- archetypeConfiguration.setProperty(
requiredProperty, value );
-
- context.put( Constants.PACKAGE, value );
- }
- else
- {
- String value =
archetypeConfiguration.getDefaultValue( requiredProperty );
-
- value = getTransitiveDefaultValue( value,
archetypeConfiguration, requiredProperty,
- context );
-
- value =
archetypeGenerationQueryer.getPropertyValue( requiredProperty, value,
-
archetypeConfiguration.getPropertyValidationRegex( requiredProperty ) );
-
- archetypeConfiguration.setProperty(
requiredProperty, value );
-
- context.put( requiredProperty, value );
- }
- }
- else
- {
- getLogger().info(
- "Using property: " + requiredProperty + " = "
+ archetypeConfiguration.getProperty(
- requiredProperty ) );
- archetypeConfiguration.setProperty(
requiredProperty, archetypeConfiguration.getProperty(
+ getLogger().info(
+ "Using property: " + requiredProperty + " = " +
archetypeConfiguration.getProperty(
requiredProperty ) );
- }
}
}
else
{
-
for ( String requiredProperty : propertiesRequired )
{
- getLogger().info(
- "Using property: " + requiredProperty + " = " +
archetypeConfiguration.getProperty(
- requiredProperty ) );
+ String value;
+
+ if ( archetypeConfiguration.isConfigured(
requiredProperty ) )
+ {
+ getLogger().info(
+ "Using property: " + requiredProperty + " = "
+ archetypeConfiguration.getProperty(
+ requiredProperty ) );
+
+ value = archetypeConfiguration.getProperty(
requiredProperty );
+ }
+ else
+ {
+ String defaultValue =
archetypeConfiguration.getDefaultValue( requiredProperty );
+
+ if ( Constants.PACKAGE.equals( requiredProperty )
&& StringUtils.isEmpty( defaultValue ) )
+ {
+ defaultValue =
archetypeConfiguration.getProperty( Constants.GROUP_ID );
+ }
+ value =
archetypeGenerationQueryer.getPropertyValue( requiredProperty,
+ expandEmbeddedTemplateExpressions(
defaultValue, requiredProperty, context ),
+
archetypeConfiguration.getPropertyValidationRegex( requiredProperty ) );
+ }
+
+ archetypeConfiguration.setProperty( requiredProperty,
value );
+
+ context.put( requiredProperty, value );
}
}
@@ -257,15 +245,22 @@ public class DefaultArchetypeGenerationConfigurator
{
if ( !archetypeConfiguration.isConfigured() )
{
- for ( String requiredProperty :
archetypeConfiguration.getRequiredProperties() )
+ for ( String requiredProperty : propertiesRequired )
{
- if ( !archetypeConfiguration.isConfigured(
requiredProperty ) && (
- archetypeConfiguration.getDefaultValue(
requiredProperty ) != null ) )
+ if ( archetypeConfiguration.isConfigured( requiredProperty
) )
{
- String value = archetypeConfiguration.getDefaultValue(
requiredProperty );
- value = getTransitiveDefaultValue( value,
archetypeConfiguration, requiredProperty, context );
- archetypeConfiguration.setProperty( requiredProperty,
value );
- context.put( requiredProperty, value );
+ context.put( requiredProperty,
archetypeConfiguration.getProperty( requiredProperty ) );
+ }
+ else
+ {
+ String defaultValue =
archetypeConfiguration.getDefaultValue( requiredProperty );
+
+ if ( defaultValue != null )
+ {
+ String value = expandEmbeddedTemplateExpressions(
defaultValue, requiredProperty, context );
+ archetypeConfiguration.setProperty(
requiredProperty, value );
+ context.put( requiredProperty, value );
+ }
}
}
@@ -313,46 +308,24 @@ public class DefaultArchetypeGenerationConfigurator
request.setProperties( properties );
}
- private String getTransitiveDefaultValue( String defaultValue,
ArchetypeConfiguration archetypeConfiguration,
- String requiredProperty, Context
context )
+ private static String expandEmbeddedTemplateExpressions( String
originalText, String textDescription, Context context )
{
- String result = defaultValue;
- if ( null == result )
+ if ( StringUtils.contains( originalText, "${" ) )
{
- return null;
- }
- for ( String property : archetypeConfiguration.getRequiredProperties()
)
- {
- if ( result.indexOf( "${" + property + "}" ) >= 0 )
+ try ( StringWriter target = new StringWriter() )
{
-
- result = StringUtils.replace( result, "${" + property + "}",
-
archetypeConfiguration.getProperty( property ) );
+ Velocity.evaluate( context, target, textDescription,
originalText );
+ return target.toString();
+ }
+ catch ( IOException ex )
+ {
+ // closing StringWriter shouldn't actually generate any
exception
+ throw new RuntimeException( "Exception closing StringWriter",
ex );
}
}
- if ( result.contains( "${" ) )
- {
- result = evaluateProperty( context, requiredProperty, defaultValue
);
- }
- return result;
- }
-
-
- private String evaluateProperty( Context context, String property, String
value )
- {
-
- try ( StringWriter stringWriter = new StringWriter() )
- {
- Velocity.evaluate( context, stringWriter, property, value );
- return stringWriter.toString();
- }
- catch ( Exception ex )
- {
- return value;
- }
+ return originalText;
}
-
private void restoreCommandLineProperties( ArchetypeConfiguration
archetypeConfiguration,
Properties executionProperties )
{
@@ -368,71 +341,118 @@ public class DefaultArchetypeGenerationConfigurator
}
}
+ void setArchetypeGenerationQueryer( ArchetypeGenerationQueryer
archetypeGenerationQueryer )
+ {
+ this.archetypeGenerationQueryer = archetypeGenerationQueryer;
+ }
+
public static class RequiredPropertyComparator
implements Comparator<String>
{
private final ArchetypeConfiguration archetypeConfiguration;
+ private Map<String, Set<String>> propertyReferenceMap;
+
public RequiredPropertyComparator( ArchetypeConfiguration
archetypeConfiguration )
{
this.archetypeConfiguration = archetypeConfiguration;
+ propertyReferenceMap = computePropertyReferences();
}
@Override
public int compare( String left, String right )
{
- String leftDefault = archetypeConfiguration.getDefaultValue( left
);
-
- if ( ( leftDefault != null ) && leftDefault.indexOf( "${" + right
+ "}" ) >= 0 )
- { //left contains right
+ if ( references( right, left ) )
+ {
return 1;
}
- String rightDefault = archetypeConfiguration.getDefaultValue(
right );
-
- if ( ( rightDefault != null ) && rightDefault.indexOf( "${" + left
+ "}" ) >= 0 )
- { //right contains left
+ if ( references( left, right ) )
+ {
return -1;
}
- return comparePropertyName( left, right );
+ return Integer.compare( propertyReferenceMap.get( left ).size(),
propertyReferenceMap.get( right ).size() );
}
- private int comparePropertyName( String left, String right )
+ private Map<String, Set<String>> computePropertyReferences()
{
- if ( "groupId".equals( left ) )
- {
- return -1;
- }
- if ( "groupId".equals( right ) )
- {
- return 1;
- }
- if ( "artifactId".equals( left ) )
- {
- return -1;
- }
- if ( "artifactId".equals( right ) )
+ Map<String, Set<String>> result = new HashMap<>();
+
+ List<String> requiredProperties =
archetypeConfiguration.getRequiredProperties();
+
+ for ( String propertyName : requiredProperties )
{
- return 1;
+ final Set<String> referencedPropertyNames = new
LinkedHashSet<>();
+
+ String defaultValue = archetypeConfiguration.getDefaultValue(
propertyName );
+ if ( StringUtils.contains( defaultValue, "${" ) )
+ {
+ try
+ {
+ final boolean dumpNamespace = false;
+ SimpleNode node = RuntimeSingleton.parse(
+ new StringReader( defaultValue ),
propertyName + ".default", dumpNamespace );
+ node.jjtAccept( new BaseVisitor()
+ {
+ @SuppressWarnings( "unchecked" )
+ @Override
+ public Object visit( ASTReference node, Object
data )
+ {
+ ( ( Set<String> ) data ).add(
node.getFirstToken().next.image );
+ return super.visit( node, data );
+ }
+ }, referencedPropertyNames );
+ }
+ catch ( ParseException e )
+ {
+ throw new IllegalStateException( "Unparsable default
value for property " + propertyName, e );
+ }
+ }
+
+ result.put( propertyName, referencedPropertyNames );
}
- if ( "version".equals( left ) )
+
+ return result;
+ }
+
+ /**
+ * Learn whether one property references another. Semantically,
"references
+ * {@code targetProperty}, {@code sourceProperty} (does)."
+ *
+ * @param targetProperty {@link String} denoting property for which
the state of
+ * being-referenced-by-the-property-denoted-by {@code
sourceProperty} is desired
+ * @param sourceProperty {@link String} denoting property for which
the state of
+ * references-the-property-denoted-by {@code targetProperty} is
desired
+ * @return {@code boolean}
+ */
+ private boolean references( String targetProperty, String
sourceProperty )
+ {
+ if ( targetProperty.equals( sourceProperty ) )
{
- return -1;
+ return false;
}
- if ( "version".equals( right ) )
+ synchronized ( this )
{
- return 1;
+ if ( ! propertyReferenceMap.containsKey( sourceProperty ) )
+ // something has changed
+ {
+ this.propertyReferenceMap = computePropertyReferences();
+ }
}
- if ( "package".equals( left ) )
+ Set<String> referencedProperties = propertyReferenceMap.get(
sourceProperty );
+ if ( referencedProperties.contains( targetProperty ) )
{
- return -1;
+ return true;
}
- if ( "package".equals( right ) )
+ for ( String referencedProperty : referencedProperties )
{
- return 1;
+ if ( references( targetProperty, referencedProperty ) )
+ {
+ return true;
+ }
}
- return left.compareTo( right );
+ return false;
}
}
diff --git
a/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator2Test.java
b/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator2Test.java
index d3ab16e..319b697 100644
---
a/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator2Test.java
+++
b/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/DefaultArchetypeGenerationConfigurator2Test.java
@@ -23,15 +23,17 @@ import java.util.List;
*/
import java.util.Properties;
-
+import java.util.regex.Pattern;
import org.apache.maven.archetype.ArchetypeGenerationRequest;
import org.apache.maven.archetype.common.ArchetypeArtifactManager;
import org.apache.maven.archetype.metadata.ArchetypeDescriptor;
import org.apache.maven.archetype.metadata.RequiredProperty;
+import org.apache.maven.archetype.ui.ArchetypeConfiguration;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.project.ProjectBuildingRequest;
import org.codehaus.plexus.PlexusTestCase;
import org.easymock.EasyMock;
+import org.easymock.IAnswer;
/**
* Tests the ability to use variables in default fields in batch mode.
@@ -40,6 +42,8 @@ public class DefaultArchetypeGenerationConfigurator2Test
extends PlexusTestCase
{
private DefaultArchetypeGenerationConfigurator configurator;
+ private ArchetypeGenerationQueryer queryer;
+ private ArchetypeDescriptor descriptor;
@Override
public void setUp()
@@ -51,7 +55,7 @@ public class DefaultArchetypeGenerationConfigurator2Test
ProjectBuildingRequest buildingRequest = null;
- ArchetypeDescriptor descriptor = new ArchetypeDescriptor();
+ descriptor = new ArchetypeDescriptor();
RequiredProperty groupId = new RequiredProperty();
groupId.setKey( "groupId" );
groupId.setDefaultValue( "com.example.${groupName}" );
@@ -89,8 +93,10 @@ public class DefaultArchetypeGenerationConfigurator2Test
EasyMock.replay( manager );
configurator.setArchetypeArtifactManager( manager );
+ queryer = EasyMock.mock( ArchetypeGenerationQueryer.class );
+ configurator.setArchetypeGenerationQueryer( queryer );
}
-
+
public void testJIRA_509_FileSetArchetypeDefaultsWithVariables() throws
Exception
{
ArchetypeGenerationRequest request = new ArchetypeGenerationRequest();
@@ -108,5 +114,76 @@ public class DefaultArchetypeGenerationConfigurator2Test
assertEquals( "1.0-SNAPSHOT", request.getVersion() );
assertEquals( "com.example.myGroupName", request.getPackage() );
}
-
+
+ public void testInteractive() throws Exception
+ {
+ ArchetypeGenerationRequest request = new ArchetypeGenerationRequest();
+ request.setArchetypeGroupId( "archetypeGroupId" );
+ request.setArchetypeArtifactId( "archetypeArtifactId" );
+ request.setArchetypeVersion( "archetypeVersion" );
+ Properties properties = new Properties();
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.eq("groupName"),
EasyMock.anyString(),
+ EasyMock.<Pattern> isNull() ) ).andReturn(
"myGroupName" );
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.eq("serviceName"),
EasyMock.anyString(),
+ EasyMock.<Pattern> isNull() ) ).andReturn(
"myServiceName" );
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.anyString(),
EasyMock.anyString(),
+ EasyMock.<Pattern> anyObject())).andAnswer( new
IAnswer<String>() {
+
+ @Override
+ public String answer() throws Throwable {
+ return (String)
EasyMock.getCurrentArguments()[1];
+ }}
+ ).anyTimes();
+
+ EasyMock.expect( queryer.confirmConfiguration(
EasyMock.<ArchetypeConfiguration> anyObject() ) )
+ .andReturn( Boolean.TRUE );
+
+ EasyMock.replay( queryer );
+ configurator.configureArchetype( request, Boolean.TRUE, properties );
+
+ assertEquals( "com.example.myGroupName", request.getGroupId() );
+ assertEquals( "myServiceName", request.getArtifactId() );
+ assertEquals( "1.0-SNAPSHOT", request.getVersion() );
+ assertEquals( "com.example.myGroupName", request.getPackage() );
+ }
+
+ public void testArchetype406ComplexCustomPropertyValue() throws Exception
+ {
+ RequiredProperty custom = new RequiredProperty();
+ custom.setKey( "serviceUpper" );
+ custom.setDefaultValue( "${serviceName.toUpperCase()}" );
+ descriptor.addRequiredProperty( custom );
+
+ ArchetypeGenerationRequest request = new ArchetypeGenerationRequest();
+ request.setArchetypeGroupId( "archetypeGroupId" );
+ request.setArchetypeArtifactId( "archetypeArtifactId" );
+ request.setArchetypeVersion( "archetypeVersion" );
+ Properties properties = new Properties();
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.eq("groupName"),
EasyMock.anyString(),
+ EasyMock.<Pattern> isNull() ) ).andReturn(
"myGroupName" );
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.eq("serviceName"),
EasyMock.anyString(),
+ EasyMock.<Pattern> isNull() ) ).andReturn(
"myServiceName" );
+
+ EasyMock.expect( queryer.getPropertyValue( EasyMock.anyString(),
EasyMock.anyString(),
+ EasyMock.<Pattern> anyObject())).andAnswer( new
IAnswer<String>() {
+
+ @Override
+ public String answer() throws Throwable {
+ return (String)
EasyMock.getCurrentArguments()[1];
+ }}
+ ).anyTimes();
+
+ EasyMock.expect( queryer.confirmConfiguration(
EasyMock.<ArchetypeConfiguration> anyObject() ) )
+ .andReturn( Boolean.TRUE );
+
+ EasyMock.replay( queryer );
+ configurator.configureArchetype( request, Boolean.TRUE, properties );
+
+ assertEquals( "MYSERVICENAME", request.getProperties().get(
"serviceUpper" ) );
+ }
}
\ No newline at end of file
diff --git
a/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/RequiredPropertyComparatorTest.java
b/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/RequiredPropertyComparatorTest.java
index 9254ecb..63e3900 100644
---
a/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/RequiredPropertyComparatorTest.java
+++
b/maven-archetype-plugin/src/test/java/org/apache/maven/archetype/ui/generation/RequiredPropertyComparatorTest.java
@@ -67,4 +67,18 @@ public class RequiredPropertyComparatorTest
Collections.sort( requiredProperties, requiredPropertyComparator );
assertEquals( Arrays.asList( "prop2", "prop1" ), requiredProperties );
}
+
+ public void testTransitivePropertyReferences()
+ {
+ archetypeConfiguration.addRequiredProperty( "foo" );
+ archetypeConfiguration.setDefaultProperty( "foo", "${bar}" );
+ archetypeConfiguration.addRequiredProperty( "bar" );
+ archetypeConfiguration.setDefaultProperty( "bar", "${baz}" );
+ archetypeConfiguration.addRequiredProperty( "baz" );
+
+ List<String> requiredProperties =
archetypeConfiguration.getRequiredProperties();
+ assertEquals( Arrays.asList( "foo", "bar", "baz" ), requiredProperties
);
+ Collections.sort( requiredProperties, requiredPropertyComparator );
+ assertEquals( Arrays.asList( "baz", "bar", "foo" ), requiredProperties
);
+ }
}