diff --git a/modello-maven-plugin/pom.xml b/modello-maven-plugin/pom.xml index df13dbc52..5303a7c5f 100644 --- a/modello-maven-plugin/pom.xml +++ b/modello-maven-plugin/pom.xml @@ -118,6 +118,10 @@ modello-plugin-xsd runtime + + org.codehaus.modello + modello-plugin-velocity + org.sonatype.plexus plexus-build-api diff --git a/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/ModelloVelocityMojo.java b/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/ModelloVelocityMojo.java new file mode 100644 index 000000000..b33346ac8 --- /dev/null +++ b/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/ModelloVelocityMojo.java @@ -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. + *

+ * 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: + *

+ * The output file is controlled from within the template using the {@code #MODELLO-VELOCITY#SAVE-OUTPUT-TO} + * VTL directive. + * 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}. + *

+ * {@code #MODELLO-VELOCITY#SAVE-OUTPUT-TO ${package.replace('.','/')}/${className}.java} + */ +@Mojo( name = "velocity", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true ) +public class ModelloVelocityMojo + extends AbstractModelloGeneratorMojo +{ + /** + * 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 + * Velocity Users Guide. + */ + @Parameter + private List templates; + + /** + * A list of parameters using the syntax {@code key=value}. + * Those parameters will be made accessible to the templates. + */ + @Parameter + private List params; + + protected String getGeneratorType() + { + return "velocity"; + } + + protected void customizeParameters( Properties parameters ) + { + super.customizeParameters( parameters ); + Map 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() ); + 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; + } +} diff --git a/modello-maven-plugin/src/site/apt/index.apt b/modello-maven-plugin/src/site/apt/index.apt index 82d761491..62617a45b 100644 --- a/modello-maven-plugin/src/site/apt/index.apt +++ b/modello-maven-plugin/src/site/apt/index.apt @@ -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 @@ -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. [] diff --git a/modello-plugins/modello-plugin-velocity/pom.xml b/modello-plugins/modello-plugin-velocity/pom.xml new file mode 100644 index 000000000..31e6d1771 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/pom.xml @@ -0,0 +1,31 @@ + + + + modello-plugins + org.codehaus.modello + 2.1.0-SNAPSHOT + + + 4.0.0 + modello-plugin-velocity + Modello Velocity Plugin + + Modello Velocity Plugin generates files from the Modello model using Velocity templates. + + + + + org.codehaus.modello + modello-plugin-xml + + + org.codehaus.plexus + plexus-utils + + + org.apache.velocity + velocity-engine-core + 2.3 + + + diff --git a/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java new file mode 100644 index 000000000..6267c5478 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java @@ -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. + *

+ * 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: + *

+ *

+ * {@code #set ( $ancestors = $Helper.ancestors( $class ) )} + *

+ */ +@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 ancestors( ModelClass clazz ) + { + List 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 xmlFields( ModelClass modelClass ) + { + List 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 fields = new ArrayList<>(); + for ( int i = classes.size() - 1; i >= 0; i-- ) + { + modelClass = classes.get( i ); + Iterator 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; + } +} diff --git a/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/VelocityGenerator.java b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/VelocityGenerator.java new file mode 100644 index 000000000..6a01969fb --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/VelocityGenerator.java @@ -0,0 +1,169 @@ +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.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.runtime.RuntimeInstance; +import org.codehaus.modello.ModelloException; +import org.codehaus.modello.ModelloParameterConstants; +import org.codehaus.modello.model.Model; +import org.codehaus.modello.model.Version; +import org.codehaus.modello.plugin.AbstractModelloGenerator; +import org.codehaus.plexus.util.io.CachingWriter; + +public class VelocityGenerator + extends AbstractModelloGenerator +{ + public static final String VELOCITY_TEMPLATES = "modello.velocity.templates"; + + public static final String VELOCITY_PARAMETERS = "modello.velocity.parameters"; + + public static final String MODELLO_VELOCITY_OUTPUT = "#MODELLO-VELOCITY#SAVE-OUTPUT-TO "; + + @Override + public void generate( Model model, Properties parameters ) throws ModelloException + { + try + { + Map params = ( Map ) Objects.requireNonNull( parameters.get( VELOCITY_PARAMETERS ) ); + String templates = getParameter( parameters, VELOCITY_TEMPLATES ); + String output = getParameter( parameters, ModelloParameterConstants.OUTPUT_DIRECTORY ); + + Properties props = new Properties(); + props.put( "resource.loader.file.path", getParameter( parameters, "basedir" ) ); + RuntimeInstance velocity = new RuntimeInstance(); + velocity.init( props ); + + VelocityContext context = new VelocityContext(); + for ( Map.Entry prop : parameters.entrySet() ) + { + context.put( prop.getKey().toString(), prop.getValue() ); + } + for ( Map.Entry prop : params.entrySet() ) + { + context.put( prop.getKey(), prop.getValue() ); + } + Version version = new Version( getParameter( parameters, ModelloParameterConstants.VERSION ) ); + context.put( "version", version ); + context.put( "model", model ); + context.put( "Helper", new Helper( version ) ); + + for ( String templatePath : templates.split( "," ) ) + { + Template template = velocity.getTemplate( templatePath ); + context.put( "generatedBy", "Generated by Modello Velocity from " + templatePath + " template, any modifications will be overwritten." ); + + try ( Writer w = new RedirectingWriter( Paths.get( output ) ) ) + { + template.merge( context, w ); + } + } + } + catch ( Exception e ) + { + throw new ModelloException( "Unable to run velocity template", e ); + } + + } + + static class RedirectingWriter extends Writer + { + Path dir; + StringBuilder sb = new StringBuilder(); + Writer current; + + RedirectingWriter( Path dir ) + { + this.dir = dir; + } + + @Override + public void write( char[] cbuf, int off, int len ) throws IOException + { + for ( int i = 0; i < len; i++ ) + { + if ( cbuf[ off + i ] == '\n' ) + { + if ( sb.length() > 0 && sb.charAt( sb.length() - 1 ) == '\r' ) + { + sb.setLength( sb.length() - 1 ); + } + writeLine( sb.toString() ); + sb.setLength( 0 ); + } + else + { + sb.append( cbuf[ off + i ] ); + } + } + } + + protected void writeLine( String line ) throws IOException + { + if ( line.startsWith( MODELLO_VELOCITY_OUTPUT ) ) + { + String file = line.substring( MODELLO_VELOCITY_OUTPUT.length() ); + if ( current != null ) + { + current.close(); + } + Path out = dir.resolve( file ); + Files.createDirectories( out.getParent() ); + current = new CachingWriter( out, StandardCharsets.UTF_8 ); + } + else if ( current != null ) + { + current.write( line ); + current.write( "\n" ); + } + } + + @Override + public void flush() throws IOException + { + if ( current != null ) + { + current.flush(); + } + } + + @Override + public void close() throws IOException + { + if ( current != null ) + { + current.close(); + current = null; + } + } + } + +} diff --git a/modello-plugins/modello-plugin-velocity/src/main/resources/META-INF/plexus/components.xml b/modello-plugins/modello-plugin-velocity/src/main/resources/META-INF/plexus/components.xml new file mode 100644 index 000000000..ed745b2fc --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/resources/META-INF/plexus/components.xml @@ -0,0 +1,31 @@ + + + + + + + + org.codehaus.modello.plugin.ModelloGenerator + velocity + org.codehaus.modello.plugin.velocity.VelocityGenerator + per-lookup + + + diff --git a/modello-plugins/modello-plugin-velocity/src/site/site.xml b/modello-plugins/modello-plugin-velocity/src/site/site.xml new file mode 100644 index 000000000..f5f579108 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/site/site.xml @@ -0,0 +1,22 @@ + + + + + Modello + + + + + + + + + + + + + + + diff --git a/modello-plugins/modello-plugin-velocity/src/site/xdoc/index.xml b/modello-plugins/modello-plugin-velocity/src/site/xdoc/index.xml new file mode 100644 index 000000000..e260e5642 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/site/xdoc/index.xml @@ -0,0 +1,46 @@ + + + + + + Modello Velocity Plugin + Guillaume Nodet + + + + +
+ +

Modello Velocity Plugin generates files from the Modello model using Velocity templates.

+ +
+ +
+ +

The plugin is configured with a list of template files to evaluate.

+

During template evaluation, #MODELLO-VELOCITY#SAVE-OUTPUT-TO {relative path to file} pseudo macro is available to send the rendered content to a file.

+

The Velocity context contains some variables related to the Modello model context that you can use: + + + + + + + + + + + + + + + + + +
VariableTypeDescription
parameters configured in the pluginStringThe parameters values configured in the plugin as {key}={value}.
versionStringThe version of the model being used.
modelModelThe Modello model.
HelperHelperA helper tool with classical functions useful to generate content from a Modello model API.
generatedByString"Generated by Modello Velocity from " + templatePath + " template, any modifications will be overwritten."
+

+
+ + + +
diff --git a/modello-plugins/pom.xml b/modello-plugins/pom.xml index 07bc0ff9e..9c61cfe8f 100644 --- a/modello-plugins/pom.xml +++ b/modello-plugins/pom.xml @@ -29,6 +29,7 @@ modello-plugin-jackson modello-plugin-snakeyaml modello-plugin-sax + modello-plugin-velocity diff --git a/pom.xml b/pom.xml index e9ba14e99..0e02c1755 100644 --- a/pom.xml +++ b/pom.xml @@ -314,6 +314,11 @@ modello-plugin-jsonschema ${project.version} + + org.codehaus.modello + modello-plugin-velocity + ${project.version} + org.eclipse.sisu org.eclipse.sisu.plexus @@ -330,11 +335,6 @@ plexus-utils 3.5.0 - - org.codehaus.plexus - plexus-velocity - 2.0 - org.sonatype.plexus plexus-build-api diff --git a/src/site/resources/modello-deps.png b/src/site/resources/modello-deps.png index b9d017176..af9990a8f 100644 Binary files a/src/site/resources/modello-deps.png and b/src/site/resources/modello-deps.png differ diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 0498116eb..b55e3db1d 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -14,7 +14,9 @@

Modello is a framework for code generation from a simple model. Modello generates code from a simple model format: based on a plugin architecture, various types of code and descriptors can be generated from the single model, including Java POJOs, XML/JSON/YAML marshallers/unmarshallers, XSD, JSON Schema - and documentation.

+ and documentation. + Starting with 2.1.0, content can even be generated from Velocity templates for maximum flexibility. +

@@ -33,10 +35,12 @@ + - + +

diff --git a/src/site/xdoc/modello-deps.odg b/src/site/xdoc/modello-deps.odg index 89901ad26..91e756395 100644 Binary files a/src/site/xdoc/modello-deps.odg and b/src/site/xdoc/modello-deps.odg differ