From 6e9abacdd6c1d3368f3a015eaf55fb7378c1d21d Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sat, 13 Jul 2013 13:52:58 +0200 Subject: [PATCH] #44 Creating template for writing MappingMethodReferences, enhancing templating mechanism to pass additional objects to directive --- .../writer/FreeMarkerModelElementWriter.java | 48 +++++++++++++-- .../ap/writer/ModelIncludeDirective.java | 61 ++++++++++++++++--- .../org/mapstruct/ap/writer/ModelWriter.java | 12 ++-- ...pstruct.ap.model.IterableMappingMethod.ftl | 2 +- ...struct.ap.model.MappingMethodReference.ftl | 21 +++++++ ...org.mapstruct.ap.model.PropertyMapping.ftl | 2 +- 6 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 processor/src/main/resources/org.mapstruct.ap.model.MappingMethodReference.ftl diff --git a/processor/src/main/java/org/mapstruct/ap/writer/FreeMarkerModelElementWriter.java b/processor/src/main/java/org/mapstruct/ap/writer/FreeMarkerModelElementWriter.java index 8ecd718a2..f2dd59f1a 100644 --- a/processor/src/main/java/org/mapstruct/ap/writer/FreeMarkerModelElementWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/writer/FreeMarkerModelElementWriter.java @@ -19,15 +19,24 @@ package org.mapstruct.ap.writer; import java.io.Writer; +import java.util.Map; +import freemarker.ext.beans.BeanModel; +import freemarker.ext.beans.BeansWrapper; +import freemarker.ext.beans.SimpleMapModel; import freemarker.template.Configuration; import freemarker.template.Template; +import freemarker.template.TemplateHashModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; import org.mapstruct.ap.model.ModelElement; import org.mapstruct.ap.model.ModelElement.Context; /** - * Delegate for writing given {@link ModelElements} into a {@link Writer} using - * FreeMarker templates. + * Delegate for writing given {@link ModelElement}s into a {@link Writer} using + * FreeMarker templates. Any parameters passed to the + * {@link ModelIncludeDirective} in addition to element itself can be accessed + * from within the template using the {@code ext} pseudo-element. * * @author Gunnar Morling */ @@ -39,8 +48,39 @@ public class FreeMarkerModelElementWriter { public void write(ModelElement modelElement, String templateName, Context context, Writer writer) throws Exception { Configuration configuration = context.get( Configuration.class ); - Template template = configuration.getTemplate( templateName ); - template.process( modelElement, writer ); + template.process( + new ExternalParamsTemplateModel( + new BeanModel( modelElement, BeansWrapper.getDefaultInstance() ), + new SimpleMapModel( context.get( Map.class ), BeansWrapper.getDefaultInstance() ) + ), + writer + ); + } + + private static class ExternalParamsTemplateModel implements TemplateHashModel { + + private final BeanModel object; + private final SimpleMapModel extParams; + + public ExternalParamsTemplateModel(BeanModel object, SimpleMapModel extParams) { + this.object = object; + this.extParams = extParams; + } + + @Override + public TemplateModel get(String key) throws TemplateModelException { + if ( key.equals( "ext" ) ) { + return extParams; + } + else { + return object.get( key ); + } + } + + @Override + public boolean isEmpty() throws TemplateModelException { + return object.isEmpty() && extParams.isEmpty(); + } } } diff --git a/processor/src/main/java/org/mapstruct/ap/writer/ModelIncludeDirective.java b/processor/src/main/java/org/mapstruct/ap/writer/ModelIncludeDirective.java index 845874013..bcf9771c7 100644 --- a/processor/src/main/java/org/mapstruct/ap/writer/ModelIncludeDirective.java +++ b/processor/src/main/java/org/mapstruct/ap/writer/ModelIncludeDirective.java @@ -19,10 +19,12 @@ package org.mapstruct.ap.writer; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import freemarker.core.Environment; import freemarker.ext.beans.BeanModel; +import freemarker.template.Configuration; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; @@ -40,10 +42,10 @@ import org.mapstruct.ap.writer.ModelWriter.DefaultModelElementWriterContext; */ public class ModelIncludeDirective implements TemplateDirectiveModel { - private DefaultModelElementWriterContext context; + private final Configuration configuration; - public ModelIncludeDirective(DefaultModelElementWriterContext context) { - this.context = context; + public ModelIncludeDirective(Configuration configuration) { + this.configuration = configuration; } @Override @@ -51,13 +53,8 @@ public class ModelIncludeDirective implements TemplateDirectiveModel { TemplateDirectiveBody body) throws TemplateException, IOException { - Object wrappedObject = ( (BeanModel) params.get( "object" ) ).getWrappedObject(); - - if ( !( wrappedObject instanceof ModelElement ) ) { - throw new IllegalArgumentException( "Given object isn't a ModelElement:" + wrappedObject ); - } - - ModelElement modelElement = (ModelElement) wrappedObject; + ModelElement modelElement = getModelElement( params ); + DefaultModelElementWriterContext context = createContext( params ); try { modelElement.write( context, env.getOut() ); @@ -75,4 +72,48 @@ public class ModelIncludeDirective implements TemplateDirectiveModel { throw new RuntimeException( e ); } } + + @SuppressWarnings("rawtypes") + private ModelElement getModelElement(Map params) { + if ( !params.containsKey( "object" ) ) { + throw new IllegalArgumentException( + "Object to be included must be passed to this directive via the 'object' parameter" + ); + } + + BeanModel objectModel = (BeanModel) params.get( "object" ); + + if ( objectModel == null ) { + throw new IllegalArgumentException( + "Object passed to this directive via the 'object' parameter must not be null" + ); + } + + if ( !( objectModel.getWrappedObject() instanceof ModelElement ) ) { + throw new IllegalArgumentException( "Given object isn't a ModelElement:" + objectModel.getWrappedObject() ); + } + + return (ModelElement) objectModel.getWrappedObject(); + } + + /** + * Creates a writer context providing access to the FreeMarker + * {@link Configuration} and a map with any additional parameters passed to + * the directive. + * + * @param params The parameter map passed to this directive. + * + * @return A writer context. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private DefaultModelElementWriterContext createContext(Map params) { + Map ext = new HashMap( params ); + ext.remove( "object" ); + + Map, Object> values = new HashMap, Object>(); + values.put( Configuration.class, configuration ); + values.put( Map.class, ext ); + + return new DefaultModelElementWriterContext( values ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/writer/ModelWriter.java b/processor/src/main/java/org/mapstruct/ap/writer/ModelWriter.java index 9285053cd..6c59a9cf6 100644 --- a/processor/src/main/java/org/mapstruct/ap/writer/ModelWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/writer/ModelWriter.java @@ -56,7 +56,7 @@ public class ModelWriter { CONFIGURATION.setObjectWrapper( new DefaultObjectWrapper() ); CONFIGURATION.setSharedVariable( "includeModel", - new ModelIncludeDirective( new DefaultModelElementWriterContext( CONFIGURATION ) ) + new ModelIncludeDirective( CONFIGURATION ) ); } @@ -64,7 +64,9 @@ public class ModelWriter { try { BufferedWriter writer = new BufferedWriter( sourceFile.openWriter() ); - model.write( new DefaultModelElementWriterContext( CONFIGURATION ), writer ); + Map, Object> values = new HashMap, Object>(); + values.put( Configuration.class, CONFIGURATION ); + model.write( new DefaultModelElementWriterContext( values ), writer ); writer.flush(); writer.close(); @@ -85,10 +87,10 @@ public class ModelWriter { */ static class DefaultModelElementWriterContext implements Context { - private Map, Object> values = new HashMap, Object>(); + private Map, Object> values; - private DefaultModelElementWriterContext(Configuration configuration) { - values.put( Configuration.class, configuration ); + DefaultModelElementWriterContext(Map, Object> values) { + this.values = new HashMap, Object>( values ); } @Override diff --git a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl index 50c2f2333..062c0dbcb 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl @@ -42,7 +42,7 @@ <#else> - ${targetType.name?uncap_first}.add( ${elementMappingMethod.name}( ${sourceType.typeParameters[0].name?uncap_first} ) ); + ${targetType.name?uncap_first}.add( <@includeModel object=elementMappingMethod input="${sourceType.typeParameters[0].name?uncap_first}"/> ); } diff --git a/processor/src/main/resources/org.mapstruct.ap.model.MappingMethodReference.ftl b/processor/src/main/resources/org.mapstruct.ap.model.MappingMethodReference.ftl new file mode 100644 index 000000000..574bab8ae --- /dev/null +++ b/processor/src/main/resources/org.mapstruct.ap.model.MappingMethodReference.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + and/or other contributors as indicated by the @authors tag. See the + copyright.txt file in the distribution for a full listing of all + contributors. + + Licensed 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. + +--> +<#if declaringMapper??>${declaringMapper.name?uncap_first}.${name}( ${ext.input} ) \ No newline at end of file diff --git a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl index 17ae84738..ccac872d9 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl @@ -20,7 +20,7 @@ --> <#-- a) invoke mapping method --> <#if mappingMethod??> - ${targetBeanName}.${targetAccessorName}( <#if mappingMethod.declaringMapper??>${mappingMethod.declaringMapper.name?uncap_first}.${mappingMethod.name}( ${sourceBeanName}.${sourceAccessorName}() ) ); + ${targetBeanName}.${targetAccessorName}( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()"/> ); <#-- b) simple conversion --> <#elseif conversion??> <#if sourceType.primitive == false>