#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;
import org.mapstruct.ap.model.source.BuiltInMethod;
import java.util.HashSet;
import org.mapstruct.ap.model.common.TypeFactory;
import java.util.Arrays;
import java.util.List;
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
*/
public class BuiltInMappingMethods extends HashSet<BuiltInMethod> {
public class BuiltInMappingMethods {
public BuiltInMappingMethods( TypeFactory typeFactory ) {
private final List<BuiltInMethod> builtInMethods;
add( new JaxbElemToValue( typeFactory ) );
add( new ListOfJaxbElemToListOfValue( typeFactory ) );
add( new DateToXmlGregorianCalendar( typeFactory ) );
add( new XmlGregorianCalendarToDate( typeFactory ) );
add( new StringToXmlGregorianCalendar( typeFactory ) );
add( new XmlGregorianCalendarToString( typeFactory ) );
add( new CalendarToXmlGregorianCalendar( typeFactory ) );
add( new XmlGregorianCalendarToCalendar( typeFactory ) );
public BuiltInMappingMethods(TypeFactory typeFactory) {
builtInMethods = Arrays.asList(
new JaxbElemToValue( typeFactory ),
new ListOfJaxbElemToListOfValue( typeFactory ),
new DateToXmlGregorianCalendar( typeFactory ),
new XmlGregorianCalendarToDate( typeFactory ),
new StringToXmlGregorianCalendar( 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;
import org.mapstruct.ap.model.source.BuiltInMethod;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@ -54,14 +52,13 @@ public class Mapper extends ModelElement {
private final List<Annotation> annotations;
private final List<MappingMethod> mappingMethods;
private final List<MapperReference> referencedMappers;
private final Set<BuiltInMethod> builtInMethods;
private final boolean suppressGeneratorTimestamp;
private final Accessibility accessibility;
private Mapper(TypeFactory typeFactory, String packageName, boolean superTypeIsInterface, String interfaceName,
String implementationName, List<MappingMethod> mappingMethods,
List<MapperReference> referencedMappers, boolean suppressGeneratorTimestamp,
Set<BuiltInMethod> builtInMethods, Accessibility accessibility) {
Accessibility accessibility) {
this.packageName = packageName;
this.superTypeIsInterface = superTypeIsInterface;
this.interfaceName = interfaceName;
@ -69,7 +66,6 @@ public class Mapper extends ModelElement {
this.annotations = new ArrayList<Annotation>();
this.mappingMethods = mappingMethods;
this.referencedMappers = referencedMappers;
this.builtInMethods = builtInMethods;
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.typeFactory = typeFactory;
this.accessibility = accessibility;
@ -81,7 +77,6 @@ public class Mapper extends ModelElement {
private TypeElement element;
private List<MappingMethod> mappingMethods;
private List<MapperReference> mapperReferences;
private Set<BuiltInMethod> builtInMethods;
private Elements elementUtils;
private boolean suppressGeneratorTimestamp;
@ -101,11 +96,6 @@ public class Mapper extends ModelElement {
return this;
}
public Builder builtInMethods(Set<BuiltInMethod> builtInMethods) {
this.builtInMethods = builtInMethods;
return this;
}
public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) {
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
return this;
@ -131,7 +121,6 @@ public class Mapper extends ModelElement {
mappingMethods,
mapperReferences,
suppressGeneratorTimestamp,
builtInMethods,
Accessibility.fromModifiers( element.getModifiers() )
);
}
@ -158,12 +147,6 @@ public class Mapper extends ModelElement {
addWithDependents( importedTypes, annotation.getType() );
}
for ( BuiltInMethod builtInMethod : builtInMethods ) {
for ( Type type : builtInMethod.getImportTypes() ) {
addWithDependents( importedTypes, type );
}
}
return importedTypes;
}
@ -250,8 +233,4 @@ public class Mapper extends ModelElement {
public Accessibility getAccessibility() {
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 org.mapstruct.ap.writer.FreeMarkerModelElementWriter;
import org.mapstruct.ap.writer.FreeMarkerWritable;
import org.mapstruct.ap.writer.Writable;
/**
* Base class of all model elements.
* <p>
* 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.
* Base class of all model elements. Implements the {@link Writable} contract to write model elements into source code
* files.
*
* @author Gunnar Morling
*/
public abstract class ModelElement implements Writable {
public abstract class ModelElement extends FreeMarkerWritable {
@Override
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.Types;
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.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.DefaultMapperReference;
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.PropertyMapping;
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.Type;
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.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.option.Options;
import org.mapstruct.ap.option.ReportingPolicy;
@ -82,7 +83,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private TypeFactory typeFactory;
private Conversions conversions;
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
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.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.usedBuiltInMethods = new HashSet<BuiltInMethod>();
this.virtualMethods = new HashSet<VirtualMappingMethod>();
return getMapper( mapperTypeElement, sourceModel );
}
@ -109,6 +115,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
List<MapperReference> mapperReferences = getReferencedMappers( element );
List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy );
mappingMethods.addAll( virtualMethods );
Mapper mapper = new Mapper.Builder()
.element( element )
@ -117,10 +124,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.suppressGeneratorTimestamp( options.isSuppressGeneratorTimestamp() )
.typeFactory( typeFactory )
.elementUtils( elementUtils )
.builtInMethods( new HashSet<BuiltInMethod>( usedBuiltInMethods ) )
.build();
usedBuiltInMethods.clear();
return mapper;
}
@ -582,7 +587,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods
if ( propertyMappingMethod == null ) {
propertyMappingMethod = getMappingMethodReference(
getBestMatch( method, mappedElement, builtInMethods, sourceType, targetType ),
getBestMatch( method, mappedElement, builtInMethods.getBuiltInMethods(), sourceType, targetType ),
targetType,
dateFormat );
}
@ -639,7 +644,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods
if ( elementMappingMethod == null ) {
elementMappingMethod = getMappingMethodReference(
getBestMatch( method, "collection element", builtInMethods, sourceElementType,
getBestMatch( method, "collection element", builtInMethods.getBuiltInMethods(), sourceElementType,
targetElementType ),
targetElementType,
dateFormat );
@ -695,7 +700,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods
if ( keyMappingMethod == null ) {
keyMappingMethod = getMappingMethodReference(
getBestMatch( method, "map key", builtInMethods, sourceKeyType, targetKeyType ),
getBestMatch( method, "map key", builtInMethods.getBuiltInMethods(), sourceKeyType, targetKeyType ),
targetKeyType,
keyDateFormat );
}
@ -708,7 +713,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
// then try BuiltInMethods
if ( valueMappingMethod == null ) {
valueMappingMethod = getMappingMethodReference(
getBestMatch( method, "map value", builtInMethods, sourceValueType, targetValueType ),
getBestMatch( method, "map value", builtInMethods.getBuiltInMethods(), sourceValueType, targetValueType ),
targetValueType,
valueDateFormat );
}
@ -844,7 +849,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private MethodReference getMappingMethodReference( BuiltInMethod method, Type returnType, String dateFormat ) {
if ( method != null ) {
usedBuiltInMethods.add( method );
virtualMethods.add( new VirtualMappingMethod( method ) );
ConversionContext ctx = new DefaultConversionContext( typeFactory, returnType, dateFormat );
return new MethodReference( method, ctx );
}

View File

@ -41,13 +41,9 @@ import org.mapstruct.ap.writer.Writable.Context;
*/
public class FreeMarkerModelElementWriter {
public void write(Writable 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 {
public void write(FreeMarkerWritable writable, Context context, Writer writer) throws Exception {
Configuration configuration = context.get( Configuration.class );
Template template = configuration.getTemplate( templateName );
Template template = configuration.getTemplate( writable.getTemplateName() );
template.process(
new ExternalParamsTemplateModel(
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/>
</#list>
<#list builtInMethods as builtInMethod>
<#nt> <@includeModel object=builtInMethod/>
</#list>
}