#120 Propagate built-in methods as MappingMethod implementation to the Mapper in order to avoid the need for specific handling in the template

This commit is contained in:
Gunnar Morling 2014-02-22 19:39:28 +01:00
parent 34484c6686
commit 47ecc23fa0
8 changed files with 176 additions and 67 deletions

View File

@ -18,27 +18,35 @@
*/ */
package org.mapstruct.ap.builtin; package org.mapstruct.ap.builtin;
import org.mapstruct.ap.model.source.BuiltInMethod; import java.util.Arrays;
import java.util.HashSet; import java.util.List;
import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.source.BuiltInMethod;
/** /**
* Registry for all build in methods. * Registry for all built-in methods.
* *
* @author Sjaak Derksen * @author Sjaak Derksen
*/ */
public class BuiltInMappingMethods extends HashSet<BuiltInMethod> { public class BuiltInMappingMethods {
public BuiltInMappingMethods( TypeFactory typeFactory ) { private final List<BuiltInMethod> builtInMethods;
add( new JaxbElemToValue( typeFactory ) ); public BuiltInMappingMethods(TypeFactory typeFactory) {
add( new ListOfJaxbElemToListOfValue( typeFactory ) ); builtInMethods = Arrays.asList(
add( new DateToXmlGregorianCalendar( typeFactory ) ); new JaxbElemToValue( typeFactory ),
add( new XmlGregorianCalendarToDate( typeFactory ) ); new ListOfJaxbElemToListOfValue( typeFactory ),
add( new StringToXmlGregorianCalendar( typeFactory ) ); new DateToXmlGregorianCalendar( typeFactory ),
add( new XmlGregorianCalendarToString( typeFactory ) ); new XmlGregorianCalendarToDate( typeFactory ),
add( new CalendarToXmlGregorianCalendar( typeFactory ) ); new StringToXmlGregorianCalendar( typeFactory ),
add( new XmlGregorianCalendarToCalendar( typeFactory ) ); new XmlGregorianCalendarToString( typeFactory ),
new CalendarToXmlGregorianCalendar( typeFactory ),
new XmlGregorianCalendarToCalendar( typeFactory )
);
}
public List<BuiltInMethod> getBuiltInMethods() {
return builtInMethods;
} }
} }

View File

@ -18,11 +18,9 @@
*/ */
package org.mapstruct.ap.model; package org.mapstruct.ap.model;
import org.mapstruct.ap.model.source.BuiltInMethod;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
@ -54,14 +52,13 @@ public class Mapper extends ModelElement {
private final List<Annotation> annotations; private final List<Annotation> annotations;
private final List<MappingMethod> mappingMethods; private final List<MappingMethod> mappingMethods;
private final List<MapperReference> referencedMappers; private final List<MapperReference> referencedMappers;
private final Set<BuiltInMethod> builtInMethods;
private final boolean suppressGeneratorTimestamp; private final boolean suppressGeneratorTimestamp;
private final Accessibility accessibility; private final Accessibility accessibility;
private Mapper(TypeFactory typeFactory, String packageName, boolean superTypeIsInterface, String interfaceName, private Mapper(TypeFactory typeFactory, String packageName, boolean superTypeIsInterface, String interfaceName,
String implementationName, List<MappingMethod> mappingMethods, String implementationName, List<MappingMethod> mappingMethods,
List<MapperReference> referencedMappers, boolean suppressGeneratorTimestamp, List<MapperReference> referencedMappers, boolean suppressGeneratorTimestamp,
Set<BuiltInMethod> builtInMethods, Accessibility accessibility) { Accessibility accessibility) {
this.packageName = packageName; this.packageName = packageName;
this.superTypeIsInterface = superTypeIsInterface; this.superTypeIsInterface = superTypeIsInterface;
this.interfaceName = interfaceName; this.interfaceName = interfaceName;
@ -69,7 +66,6 @@ public class Mapper extends ModelElement {
this.annotations = new ArrayList<Annotation>(); this.annotations = new ArrayList<Annotation>();
this.mappingMethods = mappingMethods; this.mappingMethods = mappingMethods;
this.referencedMappers = referencedMappers; this.referencedMappers = referencedMappers;
this.builtInMethods = builtInMethods;
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.typeFactory = typeFactory; this.typeFactory = typeFactory;
this.accessibility = accessibility; this.accessibility = accessibility;
@ -81,7 +77,6 @@ public class Mapper extends ModelElement {
private TypeElement element; private TypeElement element;
private List<MappingMethod> mappingMethods; private List<MappingMethod> mappingMethods;
private List<MapperReference> mapperReferences; private List<MapperReference> mapperReferences;
private Set<BuiltInMethod> builtInMethods;
private Elements elementUtils; private Elements elementUtils;
private boolean suppressGeneratorTimestamp; private boolean suppressGeneratorTimestamp;
@ -101,11 +96,6 @@ public class Mapper extends ModelElement {
return this; return this;
} }
public Builder builtInMethods(Set<BuiltInMethod> builtInMethods) {
this.builtInMethods = builtInMethods;
return this;
}
public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) {
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
return this; return this;
@ -131,7 +121,6 @@ public class Mapper extends ModelElement {
mappingMethods, mappingMethods,
mapperReferences, mapperReferences,
suppressGeneratorTimestamp, suppressGeneratorTimestamp,
builtInMethods,
Accessibility.fromModifiers( element.getModifiers() ) Accessibility.fromModifiers( element.getModifiers() )
); );
} }
@ -158,12 +147,6 @@ public class Mapper extends ModelElement {
addWithDependents( importedTypes, annotation.getType() ); addWithDependents( importedTypes, annotation.getType() );
} }
for ( BuiltInMethod builtInMethod : builtInMethods ) {
for ( Type type : builtInMethod.getImportTypes() ) {
addWithDependents( importedTypes, type );
}
}
return importedTypes; return importedTypes;
} }
@ -250,8 +233,4 @@ public class Mapper extends ModelElement {
public Accessibility getAccessibility() { public Accessibility getAccessibility() {
return accessibility; return accessibility;
} }
public Set<BuiltInMethod> getBuiltInMethods() {
return builtInMethods;
}
} }

View File

@ -0,0 +1,83 @@
/**
* Copyright 2012-2014 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.
*/
package org.mapstruct.ap.model;
import java.util.Set;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.BuiltInMethod;
/**
* A mapping method which is not based on an actual method declared in the original mapper interface but is added as
* private method to map a certain source/target type combination. Based on a {@link BuiltInMethod}.
*
* @author Gunnar Morling
*/
public class VirtualMappingMethod extends MappingMethod {
private final String templateName;
private final Set<Type> importTypes;
public VirtualMappingMethod(BuiltInMethod method) {
super( method );
this.importTypes = method.getImportTypes();
this.templateName = method.getClass().getName() + ".ftl";
}
@Override
public String getTemplateName() {
return templateName;
}
@Override
public Set<Type> getImportTypes() {
return importTypes;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( templateName == null ) ? 0 : templateName.hashCode() );
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
VirtualMappingMethod other = (VirtualMappingMethod) obj;
if ( templateName == null ) {
if ( other.templateName != null ) {
return false;
}
}
else if ( !templateName.equals( other.templateName ) ) {
return false;
}
return true;
}
}

View File

@ -22,18 +22,16 @@ import java.io.Writer;
import java.util.Set; import java.util.Set;
import org.mapstruct.ap.writer.FreeMarkerModelElementWriter; import org.mapstruct.ap.writer.FreeMarkerModelElementWriter;
import org.mapstruct.ap.writer.FreeMarkerWritable;
import org.mapstruct.ap.writer.Writable; import org.mapstruct.ap.writer.Writable;
/** /**
* Base class of all model elements. * Base class of all model elements. Implements the {@link Writable} contract to write model elements into source code
* <p> * files.
* Implements the {@link Writable} contract to write model elements into source code files using FreeMarker templates.
* By default, the fully-qualified class name of the given model element type, appended with the extension {@code *.ftl}
* is used as template file name.
* *
* @author Gunnar Morling * @author Gunnar Morling
*/ */
public abstract class ModelElement implements Writable { public abstract class ModelElement extends FreeMarkerWritable {
@Override @Override
public void write(Context context, Writer writer) throws Exception { public void write(Context context, Writer writer) throws Exception {

View File

@ -36,13 +36,10 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.conversion.ConversionProvider; import org.mapstruct.ap.conversion.ConversionProvider;
import org.mapstruct.ap.conversion.Conversions; import org.mapstruct.ap.conversion.Conversions;
import org.mapstruct.ap.model.common.DefaultConversionContext;
import org.mapstruct.ap.model.source.BuiltInMethod;
import org.mapstruct.ap.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.model.BeanMappingMethod; import org.mapstruct.ap.model.BeanMappingMethod;
import org.mapstruct.ap.model.DefaultMapperReference; import org.mapstruct.ap.model.DefaultMapperReference;
import org.mapstruct.ap.model.IterableMappingMethod; import org.mapstruct.ap.model.IterableMappingMethod;
@ -53,11 +50,15 @@ import org.mapstruct.ap.model.MappingMethod;
import org.mapstruct.ap.model.MethodReference; import org.mapstruct.ap.model.MethodReference;
import org.mapstruct.ap.model.PropertyMapping; import org.mapstruct.ap.model.PropertyMapping;
import org.mapstruct.ap.model.TypeConversion; import org.mapstruct.ap.model.TypeConversion;
import org.mapstruct.ap.model.VirtualMappingMethod;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.DefaultConversionContext;
import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.BuiltInMethod;
import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.option.Options; import org.mapstruct.ap.option.Options;
import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.option.ReportingPolicy;
@ -82,7 +83,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private TypeFactory typeFactory; private TypeFactory typeFactory;
private Conversions conversions; private Conversions conversions;
private BuiltInMappingMethods builtInMethods; private BuiltInMappingMethods builtInMethods;
private Set<BuiltInMethod> usedBuiltInMethods;
/**
* Private methods which are not present in the original mapper interface and are added to map certain property
* types.
*/
private Set<VirtualMappingMethod> virtualMethods;
@Override @Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) { public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
@ -95,7 +101,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
this.conversions = new Conversions( elementUtils, typeFactory ); this.conversions = new Conversions( elementUtils, typeFactory );
this.builtInMethods = new BuiltInMappingMethods( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.usedBuiltInMethods = new HashSet<BuiltInMethod>(); this.virtualMethods = new HashSet<VirtualMappingMethod>();
return getMapper( mapperTypeElement, sourceModel ); return getMapper( mapperTypeElement, sourceModel );
} }
@ -109,6 +115,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element ); ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
List<MapperReference> mapperReferences = getReferencedMappers( element ); List<MapperReference> mapperReferences = getReferencedMappers( element );
List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy ); List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy );
mappingMethods.addAll( virtualMethods );
Mapper mapper = new Mapper.Builder() Mapper mapper = new Mapper.Builder()
.element( element ) .element( element )
@ -117,10 +124,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.suppressGeneratorTimestamp( options.isSuppressGeneratorTimestamp() ) .suppressGeneratorTimestamp( options.isSuppressGeneratorTimestamp() )
.typeFactory( typeFactory ) .typeFactory( typeFactory )
.elementUtils( elementUtils ) .elementUtils( elementUtils )
.builtInMethods( new HashSet<BuiltInMethod>( usedBuiltInMethods ) )
.build(); .build();
usedBuiltInMethods.clear();
return mapper; return mapper;
} }
@ -582,7 +587,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods // then try BuiltInMethods
if ( propertyMappingMethod == null ) { if ( propertyMappingMethod == null ) {
propertyMappingMethod = getMappingMethodReference( propertyMappingMethod = getMappingMethodReference(
getBestMatch( method, mappedElement, builtInMethods, sourceType, targetType ), getBestMatch( method, mappedElement, builtInMethods.getBuiltInMethods(), sourceType, targetType ),
targetType, targetType,
dateFormat ); dateFormat );
} }
@ -639,7 +644,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods // then try BuiltInMethods
if ( elementMappingMethod == null ) { if ( elementMappingMethod == null ) {
elementMappingMethod = getMappingMethodReference( elementMappingMethod = getMappingMethodReference(
getBestMatch( method, "collection element", builtInMethods, sourceElementType, getBestMatch( method, "collection element", builtInMethods.getBuiltInMethods(), sourceElementType,
targetElementType ), targetElementType ),
targetElementType, targetElementType,
dateFormat ); dateFormat );
@ -695,7 +700,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods // then try BuiltInMethods
if ( keyMappingMethod == null ) { if ( keyMappingMethod == null ) {
keyMappingMethod = getMappingMethodReference( keyMappingMethod = getMappingMethodReference(
getBestMatch( method, "map key", builtInMethods, sourceKeyType, targetKeyType ), getBestMatch( method, "map key", builtInMethods.getBuiltInMethods(), sourceKeyType, targetKeyType ),
targetKeyType, targetKeyType,
keyDateFormat ); keyDateFormat );
} }
@ -708,7 +713,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods // then try BuiltInMethods
if ( valueMappingMethod == null ) { if ( valueMappingMethod == null ) {
valueMappingMethod = getMappingMethodReference( valueMappingMethod = getMappingMethodReference(
getBestMatch( method, "map value", builtInMethods, sourceValueType, targetValueType ), getBestMatch( method, "map value", builtInMethods.getBuiltInMethods(), sourceValueType, targetValueType ),
targetValueType, targetValueType,
valueDateFormat ); valueDateFormat );
} }
@ -844,7 +849,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private MethodReference getMappingMethodReference( BuiltInMethod method, Type returnType, String dateFormat ) { private MethodReference getMappingMethodReference( BuiltInMethod method, Type returnType, String dateFormat ) {
if ( method != null ) { if ( method != null ) {
usedBuiltInMethods.add( method ); virtualMethods.add( new VirtualMappingMethod( method ) );
ConversionContext ctx = new DefaultConversionContext( typeFactory, returnType, dateFormat ); ConversionContext ctx = new DefaultConversionContext( typeFactory, returnType, dateFormat );
return new MethodReference( method, ctx ); return new MethodReference( method, ctx );
} }

View File

@ -41,13 +41,9 @@ import org.mapstruct.ap.writer.Writable.Context;
*/ */
public class FreeMarkerModelElementWriter { public class FreeMarkerModelElementWriter {
public void write(Writable writable, Context context, Writer writer) throws Exception { public void write(FreeMarkerWritable writable, Context context, Writer writer) throws Exception {
write( writable, writable.getClass().getName() + ".ftl", context, writer );
}
public void write(Writable writable, String templateName, Context context, Writer writer) throws Exception {
Configuration configuration = context.get( Configuration.class ); Configuration configuration = context.get( Configuration.class );
Template template = configuration.getTemplate( templateName ); Template template = configuration.getTemplate( writable.getTemplateName() );
template.process( template.process(
new ExternalParamsTemplateModel( new ExternalParamsTemplateModel(
new BeanModel( writable, BeansWrapper.getDefaultInstance() ), new BeanModel( writable, BeansWrapper.getDefaultInstance() ),

View File

@ -0,0 +1,45 @@
/**
* Copyright 2012-2014 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.
*/
package org.mapstruct.ap.writer;
import java.io.Writer;
/**
* A {@link Writable} which uses the FreeMarker template engine to generate the output.
*
* @author Gunnar Morling
*/
public abstract class FreeMarkerWritable implements Writable {
@Override
public void write(Context context, Writer writer) throws Exception {
new FreeMarkerModelElementWriter().write( this, context, writer );
}
/**
* Returns the name of the template to be used for a specific writable type. By default, the fully-qualified class
* name of the given model element type, appended with the extension {@code *.ftl} is used as template file name,
* but this can be customized by overriding this method if required.
*
* @return the name of the template. Must not be {@code null}.
*/
protected String getTemplateName() {
return getClass().getName() + ".ftl";
}
}

View File

@ -40,9 +40,4 @@ import ${importedType.fullyQualifiedName};
<#nt> <@includeModel object=mappingMethod/> <#nt> <@includeModel object=mappingMethod/>
</#list> </#list>
<#list builtInMethods as builtInMethod>
<#nt> <@includeModel object=builtInMethod/>
</#list>
} }