Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a velocity template based generator #269

Merged
merged 12 commits into from
Dec 29, 2022
4 changes: 4 additions & 0 deletions modello-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
<artifactId>modello-plugin-xsd</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.codehaus.modello</groupId>
<artifactId>modello-plugin-velocity</artifactId>
</dependency>
<dependency>
<groupId>org.sonatype.plexus</groupId>
<artifactId>plexus-build-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.codehaus.modello.maven;

/*
* 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.
*/

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.modello.plugin.velocity.VelocityGenerator;

/**
* Creates files from the model using Velocity templates.
gnodet marked this conversation as resolved.
Show resolved Hide resolved
* <p>
* This mojo can be given a list of templates and a list of parameters.
* Each template from the {@link #templates} property will be run with the following context:
* <ul>
* <li>{@code version}: the version of the model to generate</li>
* <li>{@code model}: the modello model</li>
* <li>{@code Helper}: a {@link org.codehaus.modello.plugin.velocity.Helper} object instance</li>
* <li>any additional parameters specified using the {@link #params} property</li>
* </ul>
* The output file is controlled from within the template using the {@code #MODELLO-VELOCITY#SAVE-OUTPUT-TO}
* <a href="https://velocity.apache.org/engine/2.3/vtl-reference.html#directives">VTL directive</a>.
* This allows a single template to generate multiple files. For example, the following
* directive will redirect further output from the template to a file named
* {@code org/apache/maven/api/model/Plugin.java} if the variable {@code package} is set to
* {@code org.apache.maven.api.model} and the variable {@code className} is set to {@code Plugin}.
* <p>
* {@code #MODELLO-VELOCITY#SAVE-OUTPUT-TO ${package.replace('.','/')}/${className}.java}
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnodet nice documentation, I'm starting to understand
adding an integration test in https://github.com/codehaus-plexus/modello/tree/master/modello-maven-plugin/src/it will help showing classical use cases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnodet looking at the only use case I know of https://github.com/apache/maven/tree/master/maven-model/src/main/mdo
I'm worried on the use case: do you really want to copy paste all the generation code from Modello and tweak it by hand?
I understand that it's easy to prototype some updates to the generated java classes, but I fear it will be unmaintainable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are mostly new generators not supported by modello. The v4 model is immutable which is not supported, the v3 wraps the immutable v4 model, which is not supported, the reader returns the v4 immutable model , which is not supported, the transformer did not exist in maven 3.x, and the merger was hand written... So that's not tweaking the modello code, those are really new generators.
So given those are new generators, we'd have to reimplement them all in modello.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, there's still a problem with the current state in that the templates are duplicated instead of being reused. This need to be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had started working on an IT a few weeks ago, but my laptop died with the unfinished work on it (and I can't even turn that laptop on anymore....). I'll see if I can give in another try in the coming weeks.

@Mojo( name = "velocity", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true )
public class ModelloVelocityMojo
extends AbstractModelloGeneratorMojo
hboutemy marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* The output directory of the generated files.
*/
@Parameter( defaultValue = "${project.build.directory}/generated-sources/modello", required = true )
private File outputDirectory;

/**
* A list of template files to be run against the loaded modello model.
* Those are {@code .vm} files as described in the
* <a href="https://velocity.apache.org/engine/devel/user-guide.html">Velocity Users Guide</a>.
*/
@Parameter
private List<File> templates;

/**
* A list of parameters using the syntax {@code key=value}.
* Those parameters will be made accessible to the templates.
*/
@Parameter
private List<String> params;

protected String getGeneratorType()
{
return "velocity";
}

protected void customizeParameters( Properties parameters )
{
super.customizeParameters( parameters );
Map<String, String> params = this.params != null ? this.params.stream().collect( Collectors.toMap(
s -> s.substring( 0, s.indexOf( '=' ) ), s -> s.substring( s.indexOf( '=' ) + 1 )
) ) : Collections.emptyMap();
parameters.put( "basedir", Objects.requireNonNull( getBasedir(), "basedir is null" ) );
Path basedir = Paths.get( getBasedir() );
gnodet marked this conversation as resolved.
Show resolved Hide resolved
parameters.put( VelocityGenerator.VELOCITY_TEMPLATES, templates.stream()
.map( File::toPath )
.map( basedir::relativize )
.map( Path::toString )
.collect( Collectors.joining( "," ) ) );
parameters.put( VelocityGenerator.VELOCITY_PARAMETERS, params );
}

protected boolean producesCompilableResult()
{
return true;
}

public File getOutputDirectory()
{
return outputDirectory;
}

public void setOutputDirectory( File outputDirectory )
{
this.outputDirectory = outputDirectory;
}
}
4 changes: 3 additions & 1 deletion modello-maven-plugin/src/site/apt/index.apt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

Modello Maven Plugin

This plugin makes use of the {{{http://codehaus-plexus.github.io/modello/}Modello}} project.
This plugin makes use of the {{{https://codehaus-plexus.github.io/modello/}Modello}} project.

* Goals Overview

Expand Down Expand Up @@ -54,6 +54,8 @@ Modello Maven Plugin
* {{{./snakeyaml-extended-reader-mojo.html}modello:snakeyaml-extended-reader}} Generates a YAML reader based on SnakeYaml Streaming APIs from the.
Modello model that records line/column number metadata in the parsed model.

* {{{./velocity-mojo.html}modello:velocity}} Creates files from the model using Velocity templates.

* {{{./converters-mojo.html}modello:converters}} Generates classes that can convert between different versions of the model.

[]
Expand Down
31 changes: 31 additions & 0 deletions modello-plugins/modello-plugin-velocity/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>modello-plugins</artifactId>
<groupId>org.codehaus.modello</groupId>
<version>2.1.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>modello-plugin-velocity</artifactId>
<name>Modello Velocity Plugin</name>
<description>
Modello Velocity Plugin generates files from the Modello model using Velocity templates.
</description>

<dependencies>
<dependency>
<groupId>org.codehaus.modello</groupId>
<artifactId>modello-plugin-xml</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package org.codehaus.modello.plugin.velocity;

/*
* 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.
*/

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.codehaus.modello.ModelloRuntimeException;
import org.codehaus.modello.model.ModelAssociation;
import org.codehaus.modello.model.ModelClass;
import org.codehaus.modello.model.ModelField;
import org.codehaus.modello.model.Version;
import org.codehaus.modello.plugin.AbstractModelloGenerator;
import org.codehaus.modello.plugins.xml.metadata.XmlAssociationMetadata;
import org.codehaus.modello.plugins.xml.metadata.XmlClassMetadata;
import org.codehaus.modello.plugins.xml.metadata.XmlFieldMetadata;
import org.codehaus.plexus.util.StringUtils;

/**
* Helper class to use inside velocity templates.
* <p>
* This will be made available using {@code ${Helper}} inside the template.
* For example, the following line will return the list of ancestors for a given modello class:
* </p>
* <p>
* {@code #set ( $ancestors = $Helper.ancestors( $class ) )}
* </p>
*/
@SuppressWarnings( "unused" )
public class Helper
{
private final Version version;

public Helper( Version version )
{
this.version = version;
}

/**
* Returns the capitalised version of the given string.
*/
public String capitalise( String str )
{
return StringUtils.isEmpty( str ) ? str : Character.toTitleCase( str.charAt( 0 ) ) + str.substring( 1 );
}

/**
* Returns the uncapitalised version of the given string.
*/
public String uncapitalise( String str )
{
return StringUtils.isEmpty( str ) ? str : Character.toLowerCase( str.charAt( 0 ) ) + str.substring( 1 );
}

/**
* Returns the singular name for the given string.
*/
public String singular( String str )
{
return AbstractModelloGenerator.singular( str );
}

/**
* Returns the list of ancestors for the given {@code ModelClass}.
*/
public List<ModelClass> ancestors( ModelClass clazz )
{
List<ModelClass> ancestors = new ArrayList<>();
for ( ModelClass cl = clazz; cl != null; cl = cl.getSuperClass() != null
? cl.getModel().getClass( cl.getSuperClass(), version ) : null )
{
ancestors.add( 0, cl );
}
return ancestors;
}

/**
* Returns the {@code XmlClassMetadata} for the given {@code ModelClass}.
*/
public XmlClassMetadata xmlClassMetadata( ModelClass clazz )
{
return (XmlClassMetadata) clazz.getMetadata( XmlClassMetadata.ID );
}

/**
* Returns the {@code XmlFieldMetadata} for the given {@code ModelField}.
*/
public XmlFieldMetadata xmlFieldMetadata( ModelField field )
{
return (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
}

/**
* Returns the {@code XmlAssociationMetadata} for the given {@code ModelField}.
*/
public XmlAssociationMetadata xmAssociationMetadata( ModelField field )
{
return (XmlAssociationMetadata) ( (ModelAssociation) field )
.getAssociationMetadata( XmlAssociationMetadata.ID );
}

/**
* Checks if the given {@code ModelField} is a flat item.
*/
public boolean isFlatItems( ModelField field )
{
return field instanceof ModelAssociation && xmAssociationMetadata( field ).isFlatItems();
}

/**
* Returns a list of all {@code ModelField} for a given {@code ModelClass}.
* The list will contain all fields defined on the class and on its parents,
* excluding any field flagged as being xml transient.
*/
public List<ModelField> xmlFields( ModelClass modelClass )
{
List<ModelClass> classes = new ArrayList<>();
// get the full inheritance
while ( modelClass != null )
{
classes.add( modelClass );
String superClass = modelClass.getSuperClass();
if ( superClass != null )
{
// superClass can be located outside (not generated by modello)
modelClass = modelClass.getModel().getClass( superClass, version, true );
}
else
{
modelClass = null;
}
}
List<ModelField> fields = new ArrayList<>();
for ( int i = classes.size() - 1; i >= 0; i-- )
{
modelClass = classes.get( i );
Iterator<ModelField> parentIter = fields.iterator();
fields = new ArrayList<>();
for ( ModelField field : modelClass.getFields( version ) )
{
XmlFieldMetadata xmlFieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID );
if ( xmlFieldMetadata.isTransient() )
{
// just ignore xml.transient fields
continue;
}
if ( xmlFieldMetadata.getInsertParentFieldsUpTo() != null )
{
// insert fields from parent up to the specified field
boolean found = false;
while ( !found && parentIter.hasNext() )
{
ModelField parentField = parentIter.next();
fields.add( parentField );
found = parentField.getName().equals( xmlFieldMetadata.getInsertParentFieldsUpTo() );
}
if ( !found )
{
// interParentFieldsUpTo not found
throw new ModelloRuntimeException( "parent field not found: class "
+ modelClass.getName() + " xml.insertParentFieldUpTo='"
+ xmlFieldMetadata.getInsertParentFieldsUpTo() + "'" );
}
}
fields.add( field );
}
// add every remaining fields from parent class
while ( parentIter.hasNext() )
{
fields.add( parentIter.next() );
}
}
return fields;
}
}
Loading