From 6d73ee5d6cb3e635f7e26653070b359c5a72a64b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 23 Oct 2022 22:01:12 +0200 Subject: [PATCH] Add a velocity template based plugin --- modello-maven-plugin/pom.xml | 5 + .../modello/maven/ModelloVelocityMojo.java | 86 +++++++++ .../modello-plugin-velocity/pom.xml | 31 ++++ .../modello/plugin/velocity/Helper.java | 154 ++++++++++++++++ .../plugin/velocity/VelocityGenerator.java | 166 ++++++++++++++++++ .../resources/META-INF/plexus/components.xml | 31 ++++ .../src/main/site/site.xml | 22 +++ .../src/main/site/xdoc/index.xml | 20 +++ modello-plugins/pom.xml | 1 + pom.xml | 7 +- 10 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 modello-maven-plugin/src/main/java/org/codehaus/modello/maven/ModelloVelocityMojo.java create mode 100644 modello-plugins/modello-plugin-velocity/pom.xml create mode 100644 modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java create mode 100644 modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/VelocityGenerator.java create mode 100644 modello-plugins/modello-plugin-velocity/src/main/resources/META-INF/plexus/components.xml create mode 100644 modello-plugins/modello-plugin-velocity/src/main/site/site.xml create mode 100644 modello-plugins/modello-plugin-velocity/src/main/site/xdoc/index.xml diff --git a/modello-maven-plugin/pom.xml b/modello-maven-plugin/pom.xml index 0e3cf8bac..e02bcb98f 100644 --- a/modello-maven-plugin/pom.xml +++ b/modello-maven-plugin/pom.xml @@ -119,6 +119,11 @@ modello-plugin-xsd runtime + + org.codehaus.modello + modello-plugin-velocity + runtime + 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..e472b0710 --- /dev/null +++ b/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/ModelloVelocityMojo.java @@ -0,0 +1,86 @@ +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.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. + * + * @author Brett Porter + */ +@Mojo( name = "velocity", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true ) +public class ModelloVelocityMojo + extends AbstractModelloGeneratorMojo +{ + /** + * The output directory of the generated XML Schema. + */ + @Parameter( defaultValue = "${project.build.directory}/generated-sources/modello", required = true ) + private File outputDirectory; + + @Parameter + private List 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" ) ); + parameters.put( VelocityGenerator.VELOCITY_TEMPLATES, String.join( ",", templates ) ); + 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-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..0e9f285fd --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/Helper.java @@ -0,0 +1,154 @@ +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; + +@SuppressWarnings( "unused" ) +public class Helper +{ + private final Version version; + + public Helper( Version version ) + { + this.version = version; + } + + public String capitalise( String str ) + { + return StringUtils.isEmpty( str ) ? str : Character.toTitleCase( str.charAt( 0 ) ) + str.substring( 1 ); + } + + public String uncapitalise( String str ) + { + return StringUtils.isEmpty( str ) ? str : Character.toLowerCase( str.charAt( 0 ) ) + str.substring( 1 ); + } + + public String singular( String str ) + { + return AbstractModelloGenerator.singular( str ); + } + + 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; + } + + public XmlClassMetadata xmlClassMetadata( ModelClass clazz ) + { + return (XmlClassMetadata) clazz.getMetadata( XmlClassMetadata.ID ); + } + + public XmlFieldMetadata xmlFieldMetadata( ModelField field ) + { + return (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); + } + + public XmlAssociationMetadata xmAssociationMetadata( ModelField field ) + { + return (XmlAssociationMetadata) ( (ModelAssociation) field ) + .getAssociationMetadata( XmlAssociationMetadata.ID ); + } + + public boolean isFlatItems( ModelField field ) + { + return field instanceof ModelAssociation && xmAssociationMetadata( field ).isFlatItems(); + } + + 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..6f8263169 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/java/org/codehaus/modello/plugin/velocity/VelocityGenerator.java @@ -0,0 +1,166 @@ +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.template"; + + public static final String VELOCITY_PARAMETERS = "modello.velocity.parameters"; + + @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 ); + + 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#REDIRECT " ) ) + { + String file = line.substring( "#MODELLO-VELOCITY#REDIRECT ".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/main/site/site.xml b/modello-plugins/modello-plugin-velocity/src/main/site/site.xml new file mode 100644 index 000000000..f5f579108 --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/site/site.xml @@ -0,0 +1,22 @@ + + + + + Modello + + + + + + + + + + + + + + + diff --git a/modello-plugins/modello-plugin-velocity/src/main/site/xdoc/index.xml b/modello-plugins/modello-plugin-velocity/src/main/site/xdoc/index.xml new file mode 100644 index 000000000..1d9a1bb8e --- /dev/null +++ b/modello-plugins/modello-plugin-velocity/src/main/site/xdoc/index.xml @@ -0,0 +1,20 @@ + + + + + + Modello Velocity Plugin + Guillaume Nodet + + + + +
+ +

Modello Velocity Plugin generates files using the Velocity templating engine.

+ +
+ + + +
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 fcba6369c..c6f005201 100644 --- a/pom.xml +++ b/pom.xml @@ -313,6 +313,11 @@ modello-plugin-jsonschema ${project.version} + + org.codehaus.modello + modello-plugin-velocity + ${project.version} + org.eclipse.sisu org.eclipse.sisu.plexus @@ -327,7 +332,7 @@ org.codehaus.plexus plexus-utils - 3.4.1 + 3.4.2 org.codehaus.plexus