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 
);
+    }
 }

Reply via email to