From d42216ee4f1de112c651735946324409dbf36fc5 Mon Sep 17 00:00:00 2001 From: sjaakd Date: Mon, 29 Sep 2014 20:53:13 +0200 Subject: [PATCH] #302 refactoring of MappingCreationProcessor: using Builders to address smaller concerns --- .../ap/conversion/ConversionProvider.java | 4 +- .../ap/conversion/DateToStringConversion.java | 4 +- .../ap/conversion/ReverseConversion.java | 2 +- .../ap/conversion/SimpleConversion.java | 6 +- .../mapstruct/ap/model/AssignmentFactory.java | 103 ++ .../mapstruct/ap/model/BeanMappingMethod.java | 341 +++++- .../ap/model/{assignment => }/Direct.java | 4 +- .../mapstruct/ap/model/EnumMappingMethod.java | 167 ++- .../ap/model/IterableMappingMethod.java | 72 +- .../mapstruct/ap/model/MapMappingMethod.java | 102 +- .../mapstruct/ap/model/MappingContext.java | 171 +++ .../mapstruct/ap/model/MappingResolver.java | 63 + .../{assignment => }/MethodReference.java | 7 +- .../mapstruct/ap/model/PropertyMapping.java | 426 ++++++- .../{assignment => }/TypeConversion.java | 4 +- .../ap/model/assignment/AdderWrapper.java | 1 - .../ap/model/{ => assignment}/Assignment.java | 2 +- .../model/assignment/AssignmentFactory.java | 63 - .../model/assignment/AssignmentWrapper.java | 1 - .../GetterCollectionOrMapWrapper.java | 2 - .../ap/model/assignment/LocalVarWrapper.java | 1 - .../assignment/NewCollectionOrMapWrapper.java | 1 - .../ap/model/assignment/NullCheckWrapper.java | 2 - .../SetterCollectionOrMapWrapper.java | 1 - .../ap/model/assignment/SetterWrapper.java | 1 - .../ap/processor/MapperCreationProcessor.java | 1068 +---------------- ...Resolver.java => MappingResolverImpl.java} | 320 ++--- ....ftl => org.mapstruct.ap.model.Direct.ftl} | 0 ...rg.mapstruct.ap.model.MethodReference.ftl} | 0 ...org.mapstruct.ap.model.TypeConversion.ftl} | 0 30 files changed, 1669 insertions(+), 1270 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/model/AssignmentFactory.java rename processor/src/main/java/org/mapstruct/ap/model/{assignment => }/Direct.java (95%) create mode 100644 processor/src/main/java/org/mapstruct/ap/model/MappingContext.java create mode 100644 processor/src/main/java/org/mapstruct/ap/model/MappingResolver.java rename processor/src/main/java/org/mapstruct/ap/model/{assignment => }/MethodReference.java (96%) rename processor/src/main/java/org/mapstruct/ap/model/{assignment => }/TypeConversion.java (97%) rename processor/src/main/java/org/mapstruct/ap/model/{ => assignment}/Assignment.java (98%) delete mode 100644 processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentFactory.java rename processor/src/main/java/org/mapstruct/ap/processor/creation/{MappingResolver.java => MappingResolverImpl.java} (65%) rename processor/src/main/resources/{org.mapstruct.ap.model.assignment.Direct.ftl => org.mapstruct.ap.model.Direct.ftl} (100%) rename processor/src/main/resources/{org.mapstruct.ap.model.assignment.MethodReference.ftl => org.mapstruct.ap.model.MethodReference.ftl} (100%) rename processor/src/main/resources/{org.mapstruct.ap.model.assignment.TypeConversion.ftl => org.mapstruct.ap.model.TypeConversion.ftl} (100%) diff --git a/processor/src/main/java/org/mapstruct/ap/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/conversion/ConversionProvider.java index 3d8ba9fc8..4efc14c36 100644 --- a/processor/src/main/java/org/mapstruct/ap/conversion/ConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/conversion/ConversionProvider.java @@ -18,8 +18,8 @@ */ package org.mapstruct.ap.conversion; -import org.mapstruct.ap.model.Assignment; -import org.mapstruct.ap.model.assignment.TypeConversion; +import org.mapstruct.ap.model.assignment.Assignment; +import org.mapstruct.ap.model.TypeConversion; import org.mapstruct.ap.model.common.ConversionContext; /** diff --git a/processor/src/main/java/org/mapstruct/ap/conversion/DateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/conversion/DateToStringConversion.java index 614ca3957..21741f1c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/conversion/DateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/conversion/DateToStringConversion.java @@ -23,8 +23,8 @@ import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; -import org.mapstruct.ap.model.Assignment; -import org.mapstruct.ap.model.assignment.AssignmentFactory; +import org.mapstruct.ap.model.assignment.Assignment; +import org.mapstruct.ap.model.AssignmentFactory; import org.mapstruct.ap.model.common.ConversionContext; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/conversion/ReverseConversion.java b/processor/src/main/java/org/mapstruct/ap/conversion/ReverseConversion.java index 99bd24fd8..2ba0adc7b 100644 --- a/processor/src/main/java/org/mapstruct/ap/conversion/ReverseConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/conversion/ReverseConversion.java @@ -18,7 +18,7 @@ */ package org.mapstruct.ap.conversion; -import org.mapstruct.ap.model.Assignment; +import org.mapstruct.ap.model.assignment.Assignment; import org.mapstruct.ap.model.common.ConversionContext; /** diff --git a/processor/src/main/java/org/mapstruct/ap/conversion/SimpleConversion.java b/processor/src/main/java/org/mapstruct/ap/conversion/SimpleConversion.java index febec383b..a1ceb02bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/conversion/SimpleConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/conversion/SimpleConversion.java @@ -21,9 +21,9 @@ package org.mapstruct.ap.conversion; import java.util.Collections; import java.util.Set; -import org.mapstruct.ap.model.Assignment; -import org.mapstruct.ap.model.assignment.AssignmentFactory; -import org.mapstruct.ap.model.assignment.TypeConversion; +import org.mapstruct.ap.model.assignment.Assignment; +import org.mapstruct.ap.model.AssignmentFactory; +import org.mapstruct.ap.model.TypeConversion; import org.mapstruct.ap.model.common.ConversionContext; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/model/AssignmentFactory.java b/processor/src/main/java/org/mapstruct/ap/model/AssignmentFactory.java new file mode 100644 index 000000000..8c6e5ef74 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/AssignmentFactory.java @@ -0,0 +1,103 @@ +/** + * 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 org.mapstruct.ap.model.assignment.Assignment; +import java.util.List; +import java.util.Set; +import javax.tools.Diagnostic; +import org.mapstruct.ap.model.common.ConversionContext; +import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.Method; +import org.mapstruct.ap.model.source.SourceMethod; +import org.mapstruct.ap.model.source.builtin.BuiltInMethod; +import org.mapstruct.ap.model.source.selector.MethodSelectors; + +/** + * Factory class for creating all types of assignments + * + * @author Sjaak Derksen + */ +public class AssignmentFactory { + + private AssignmentFactory() { + } + + public static Assignment createTypeConversion(Set importTypes, List exceptionTypes, String expression) { + return new TypeConversion( importTypes, exceptionTypes, expression ); + } + + public static Assignment createMethodReference(Method method, MapperReference declaringMapper, + Type targetType) { + return new MethodReference( method, declaringMapper, targetType ); + } + + public static Assignment createMethodReference(BuiltInMethod method, ConversionContext contextParam) { + return new MethodReference( method, contextParam ); + } + + public static Direct createSimple(String sourceRef) { + return new Direct( sourceRef ); + } + + public static FactoryMethod createFactoryMethod( Type returnType, MappingContext ctx ) { + FactoryMethod result = null; + for ( SourceMethod method : ctx.getSourceModel() ) { + if ( !method.overridesMethod() && !method.isIterableMapping() && !method.isMapMapping() + && method.getSourceParameters().isEmpty() ) { + + List parameterTypes = MethodSelectors.getParameterTypes( + ctx.getTypeFactory(), + method.getParameters(), + null, + returnType + ); + + if ( method.matches( parameterTypes, returnType ) ) { + if ( result == null ) { + MapperReference mapperReference = findMapperReference( ctx.getMapperReferences(), method ); + result = new MethodReference( method, mapperReference, null ); + } + else { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Ambiguous factory methods: \"%s\" conflicts with \"%s\".", + result, + method + ), + method.getExecutable() + ); + } + } + } + } + return result; + } + + private static MapperReference findMapperReference( List mapperReferences, SourceMethod method ) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + return ref; + } + } + return null; + } +} + diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index 83f61f2f3..88968f500 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -18,15 +18,25 @@ */ package org.mapstruct.ap.model; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.lang.model.element.ExecutableElement; +import javax.tools.Diagnostic; +import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.SourceMethod; +import org.mapstruct.ap.option.ReportingPolicy; +import org.mapstruct.ap.util.Executables; +import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Strings; /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one @@ -44,7 +54,336 @@ public class BeanMappingMethod extends MappingMethod { private final FactoryMethod factoryMethod; - public BeanMappingMethod(SourceMethod method, + public static class Builder { + + private MappingContext ctx; + private SourceMethod method; + + public Builder mappingContext(MappingContext mappingContext) { + this.ctx = mappingContext; + return this; + } + + public Builder souceMethod( SourceMethod sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public BeanMappingMethod build() { + + // fetch settings from element to implement + ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy(); + CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy(); + + List propertyMappings = new ArrayList(); + Set mappedTargetProperties = new HashSet(); + Set ignoredTargetProperties = new HashSet(); + + if ( !reportErrorIfMappedPropertiesDontExist() ) { + return null; + } + + // collect all target accessors + List targetAccessors = new ArrayList(); + targetAccessors.addAll( method.getResultType().getSetters() ); + targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() ); + + for ( ExecutableElement targetAccessor : targetAccessors ) { + + String targetPropertyName = Executables.getPropertyName( targetAccessor ); + + Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName ); + + if ( mapping != null && mapping.isIgnored() ) { + ignoredTargetProperties.add( targetPropertyName ); + continue; + } + + // A target access is in general a setter method on the target object. However, in case of collections, + // the current target accessor can also be a getter method. + // The following if block, checks if the target accessor should be overruled by an add method. + if ( cmStrategy.equals( CollectionMappingStrategy.SETTER_PREFERRED ) + || cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { + + // first check if there's a setter method. + ExecutableElement adderMethod = null; + if ( Executables.isSetterMethod( targetAccessor ) ) { + Type targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType(); + // ok, the current accessor is a setter. So now the strategy determines what to use + if ( cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { + adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName ); + } + } + else if ( Executables.isGetterMethod( targetAccessor ) ) { + // the current accessor is a getter (no setter available). But still, an add method is according + // to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter. + Type targetType = ctx.getTypeFactory().getReturnType( targetAccessor ); + adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName ); + } + if ( adderMethod != null ) { + // an adder has been found (according strategy) so overrule current choice. + targetAccessor = adderMethod; + } + } + + PropertyMapping propertyMapping = null; + if ( mapping != null ) { + + if ( mapping.getSourceParameterName() != null ) { + // this is a parameterized property, so sourceParameter.property + Parameter parameter = method.getSourceParameter( mapping.getSourceParameterName() ); + + PropertyMapping.PropertyMappingBuilder builder = new PropertyMapping.PropertyMappingBuilder(); + propertyMapping = builder + .mappingContext( ctx ) + .souceMethod( method ) + .targetAccessor( targetAccessor ) + .targetPropertyName( targetPropertyName ) + .parameter( parameter ) + .build(); + + } + else if ( Executables.isSetterMethod( targetAccessor ) + || Executables.isGetterMethod( targetAccessor ) ) { + + if ( !mapping.getConstant().isEmpty() ) { + // its a constant + PropertyMapping.ConstantMappingBuilder builder = + new PropertyMapping.ConstantMappingBuilder(); + propertyMapping = builder + .mappingContext( ctx ) + .sourceMethod( method ) + .constantExpression( "\"" + mapping.getConstant() + "\"" ) + .targetAccessor( targetAccessor ) + .dateFormat( mapping.getDateFormat() ) + .qualifiers( mapping.getQualifiers() ) + .build(); + } + + else if ( !mapping.getJavaExpression().isEmpty() ) { + // its an expression + PropertyMapping.JavaExpressionMappingBuilder builder = + new PropertyMapping.JavaExpressionMappingBuilder(); + propertyMapping = builder + .mappingContext( ctx ) + .souceMethod( method ) + .javaExpression( mapping.getJavaExpression() ) + .targetAccessor( targetAccessor ) + .build(); + } + } + } + + if ( propertyMapping == null ) { + for ( Parameter sourceParameter : method.getSourceParameters() ) { + PropertyMapping.PropertyMappingBuilder builder = new PropertyMapping.PropertyMappingBuilder(); + PropertyMapping newPropertyMapping = builder + .mappingContext( ctx ) + .souceMethod( method ) + .targetAccessor( targetAccessor ) + .targetPropertyName( targetPropertyName ) + .parameter( sourceParameter ) + .build(); + + if ( propertyMapping != null && newPropertyMapping != null ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + "Several possible source properties for target property \"" + targetPropertyName + + "\".", + method.getExecutable() + ); + break; + } + else if ( newPropertyMapping != null ) { + propertyMapping = newPropertyMapping; + } + } + } + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + mappedTargetProperties.add( targetPropertyName ); + } + } + + Set targetProperties = Executables.getPropertyNames( targetAccessors ); + + reportErrorForUnmappedTargetPropertiesIfRequired( + method, + unmappedTargetPolicy, + targetProperties, + mappedTargetProperties, + ignoredTargetProperties + ); + FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); + return new BeanMappingMethod( method, propertyMappings, factoryMethod ); + } + + /** + * Returns the effective policy for reporting unmapped getReturnType properties. If explicitly set via + * {@code Mapper}, this value will be returned. Otherwise the value from the corresponding processor option will + * be returned. If that is not set either, the default value from {@code Mapper#unmappedTargetPolicy()} will be + * returned. + * + * @param element The type declaring the generated mapper type + * + * @return The effective policy for reporting unmapped getReturnType properties. + */ + private ReportingPolicy getEffectiveUnmappedTargetPolicy() { + MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ); + boolean setViaAnnotation = mapperSettings.isSetUnmappedTargetPolicy(); + ReportingPolicy annotationValue = ReportingPolicy.valueOf( mapperSettings.unmappedTargetPolicy() ); + + if ( setViaAnnotation + || ctx.getOptions().getUnmappedTargetPolicy() == null ) { + return annotationValue; + } + else { + return ctx.getOptions().getUnmappedTargetPolicy(); + } + } + + private CollectionMappingStrategy getEffectiveCollectionMappingStrategy() { + MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ); + return mapperSettings.getCollectionMappingStrategy(); + } + + private boolean reportErrorIfMappedPropertiesDontExist() { + // only report errors if this method itself is configured + if ( method.isConfiguredByReverseMappingMethod() ) { + return true; + } + + // collect all target accessors + List targetAccessors = new ArrayList(); + targetAccessors.addAll( method.getResultType().getSetters() ); + targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() ); + + Set targetProperties = Executables.getPropertyNames( targetAccessors ); + + boolean foundUnmappedProperty = false; + + for ( List mappedProperties : method.getMappings().values() ) { + for ( Mapping mappedProperty : mappedProperties ) { + if ( mappedProperty.isIgnored() ) { + continue; + } + + if ( mappedProperty.getSourceParameterName() != null ) { + Parameter sourceParameter = method.getSourceParameter( + mappedProperty.getSourceParameterName() + ); + + if ( sourceParameter == null ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Method has no parameter named \"%s\".", + mappedProperty.getSourceParameterName() + ), + method.getExecutable(), + mappedProperty.getMirror(), + mappedProperty.getSourceAnnotationValue() + ); + foundUnmappedProperty = true; + } + else { + if ( !hasSourceProperty( sourceParameter, mappedProperty.getSourcePropertyName() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "The type of parameter \"%s\" has no property named \"%s\".", + mappedProperty.getSourceParameterName(), + mappedProperty.getSourcePropertyName() + ), + method.getExecutable(), + mappedProperty.getMirror(), + mappedProperty.getSourceAnnotationValue() + ); + foundUnmappedProperty = true; + } + } + + } + else if ( mappedProperty.getConstant().isEmpty() + && mappedProperty.getJavaExpression().isEmpty() + && !hasSourceProperty( mappedProperty.getSourcePropertyName() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "No property named \"%s\" exists in source parameter(s).", + mappedProperty.getSourceName() + ), + method.getExecutable(), + mappedProperty.getMirror(), + mappedProperty.getSourceAnnotationValue() + ); + foundUnmappedProperty = true; + } + if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Unknown property \"%s\" in return type %s.", + mappedProperty.getTargetName(), + method.getResultType() + ), + method.getExecutable(), + mappedProperty.getMirror(), + mappedProperty.getTargetAnnotationValue() + ); + foundUnmappedProperty = true; + } + } + } + return !foundUnmappedProperty; + } + + private void reportErrorForUnmappedTargetPropertiesIfRequired( SourceMethod method, + ReportingPolicy unmappedTargetPolicy, + Set targetProperties, + Set mappedTargetProperties, + Set ignoredTargetProperties ) { + + Set unmappedTargetProperties = new HashSet(); + + for ( String property : targetProperties ) { + if ( !mappedTargetProperties.contains( property ) && !ignoredTargetProperties.contains( property ) ) { + unmappedTargetProperties.add( property ); + } + } + + if ( !unmappedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { + ctx.getMessager().printMessage( + unmappedTargetPolicy.getDiagnosticKind(), + MessageFormat.format( + "Unmapped target {0,choice,1#property|1 getters = parameter.getType().getGetters(); + return Executables.getPropertyNames( getters ).contains( propertyName ); + } + + } + + private BeanMappingMethod(SourceMethod method, List propertyMappings, FactoryMethod factoryMethod) { super( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/Direct.java b/processor/src/main/java/org/mapstruct/ap/model/Direct.java similarity index 95% rename from processor/src/main/java/org/mapstruct/ap/model/assignment/Direct.java rename to processor/src/main/java/org/mapstruct/ap/model/Direct.java index 2815c1f26..232c827f3 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/Direct.java +++ b/processor/src/main/java/org/mapstruct/ap/model/Direct.java @@ -16,12 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.model.assignment; +package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; import java.util.Collections; import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.ModelElement; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java index ca5afee69..3fb0886d3 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java @@ -18,11 +18,18 @@ */ package org.mapstruct.ap.model; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.tools.Diagnostic; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.source.EnumMapping; +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.util.Strings; /** * A {@link MappingMethod} which maps one enum type to another, optionally configured by one or more @@ -34,7 +41,165 @@ public class EnumMappingMethod extends MappingMethod { private final List enumMappings; - public EnumMappingMethod(Method method, List enumMappings) { + public static class Builder { + + private SourceMethod method; + private MappingContext ctx; + + public Builder mappingContext( MappingContext mappingContext ) { + this.ctx = mappingContext; + return this; + } + + public Builder souceMethod( SourceMethod sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public EnumMappingMethod build() { + + if ( !reportErrorIfMappedEnumConstantsDontExist( method ) + || !reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( method ) ) { + return null; + } + + List enumMappings = new ArrayList(); + + List sourceEnumConstants + = method.getSourceParameters().iterator().next().getType().getEnumConstants(); + Map> mappings = method.getMappings(); + + for ( String enumConstant : sourceEnumConstants ) { + List mappedConstants = mappings.get( enumConstant ); + + if ( mappedConstants == null ) { + enumMappings.add( new EnumMapping( enumConstant, enumConstant ) ); + } + else if ( mappedConstants.size() == 1 ) { + enumMappings.add( new EnumMapping( + enumConstant, mappedConstants.iterator().next().getTargetName() ) ); + } + else { + List targetConstants = new ArrayList( mappedConstants.size() ); + for ( Mapping mapping : mappedConstants ) { + targetConstants.add( mapping.getTargetName() ); + } + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "One enum constant must not be mapped to more than one target constant, " + + "but constant %s is mapped to %s.", + enumConstant, + Strings.join( targetConstants, ", " ) + ), + method.getExecutable() + ); + } + } + + return new EnumMappingMethod( method, enumMappings ); + } + + private boolean reportErrorIfMappedEnumConstantsDontExist( SourceMethod method ) { + // only report errors if this method itself is configured + if ( method.isConfiguredByReverseMappingMethod() ) { + return true; + } + + List sourceEnumConstants = + method.getSourceParameters().iterator().next().getType().getEnumConstants(); + List targetEnumConstants = method.getReturnType().getEnumConstants(); + + boolean foundIncorrectMapping = false; + + for ( List mappedConstants : method.getMappings().values() ) { + for ( Mapping mappedConstant : mappedConstants ) { + if ( mappedConstant.getSourceName() == null ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + "A source constant must be specified for mappings of an enum mapping method.", + method.getExecutable(), + mappedConstant.getMirror() + ); + foundIncorrectMapping = true; + } + else if ( !sourceEnumConstants.contains( mappedConstant.getSourceName() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Constant %s doesn't exist in enum type %s.", + mappedConstant.getSourceName(), + method.getSourceParameters().iterator().next().getType() + ), + method.getExecutable(), + mappedConstant.getMirror(), + mappedConstant.getSourceAnnotationValue() + ); + foundIncorrectMapping = true; + } + if ( mappedConstant.getTargetName() == null ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + "A target constant must be specified for mappings of an enum mapping method.", + method.getExecutable(), + mappedConstant.getMirror() + ); + foundIncorrectMapping = true; + } + else if ( !targetEnumConstants.contains( mappedConstant.getTargetName() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Constant %s doesn't exist in enum type %s.", + mappedConstant.getTargetName(), + method.getReturnType() + ), + method.getExecutable(), + mappedConstant.getMirror(), + mappedConstant.getTargetAnnotationValue() + ); + foundIncorrectMapping = true; + } + } + } + + return !foundIncorrectMapping; + } + + private boolean reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( + SourceMethod method ) { + + List sourceEnumConstants = + method.getSourceParameters().iterator().next().getType().getEnumConstants(); + List targetEnumConstants = method.getReturnType().getEnumConstants(); + Set mappedSourceEnumConstants = method.getMappings().keySet(); + List unmappedSourceEnumConstants = new ArrayList(); + + for ( String sourceEnumConstant : sourceEnumConstants ) { + if ( !targetEnumConstants.contains( sourceEnumConstant ) + && !mappedSourceEnumConstants.contains( sourceEnumConstant ) ) { + unmappedSourceEnumConstants.add( sourceEnumConstant ); + } + } + + if ( !unmappedSourceEnumConstants.isEmpty() ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "The following constants from the source enum have no corresponding constant in the " + + "target enum and must be be mapped via @Mapping: %s", + Strings.join( unmappedSourceEnumConstants, ", " ) + ), + method.getExecutable() + ); + } + + return unmappedSourceEnumConstants.isEmpty(); + } + + } + + private EnumMappingMethod( Method method, List enumMappings ) { super( method ); this.enumMappings = enumMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java index bd9dfd94e..eb5b573de 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -18,8 +18,12 @@ */ package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; +import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.assignment.TypeConversion; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import org.mapstruct.ap.model.assignment.SetterWrapper; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.source.Method; @@ -37,7 +41,71 @@ public class IterableMappingMethod extends MappingMethod { private final FactoryMethod factoryMethod; private final boolean overridden; - public IterableMappingMethod(Method method, Assignment parameterAssignment, FactoryMethod factoryMethod) { + public static class Builder { + + private Method method; + private MappingContext ctx; + private String dateFormat; + private List qualifiers; + + public Builder mappingContext( MappingContext mappingContext ) { + this.ctx = mappingContext; + return this; + } + + public Builder method( Method sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public Builder dateFormat( String dateFormat ) { + this.dateFormat = dateFormat; + return this; + } + + public Builder qualifiers( List qualifiers ) { + this.qualifiers = qualifiers; + return this; + } + + public IterableMappingMethod build( ) { + Type sourceElementType = + method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 ); + Type targetElementType = + method.getResultType().getTypeParameters().get( 0 ); + String conversionStr = + Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() ); + + + Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, + "collection element", + sourceElementType, + targetElementType, + null, // there is no targetPropertyName + dateFormat, + qualifiers, + conversionStr + ); + + if ( assignment == null ) { + String message = String.format( + "Can't create implementation of method %s. Found no method nor built-in conversion for mapping " + + "source element type into target element type.", + method + ); + method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message ); + } + + // target accessor is setter, so decorate assignment as setter + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + + FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); + return new IterableMappingMethod( method, assignment, factoryMethod ); + } + } + + + private IterableMappingMethod(Method method, Assignment parameterAssignment, FactoryMethod factoryMethod) { super( method ); this.elementAssignment = parameterAssignment; this.factoryMethod = factoryMethod; diff --git a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java index 390ed064a..91b6f3bd1 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java @@ -18,8 +18,12 @@ */ package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; +import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.assignment.TypeConversion; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import org.mapstruct.ap.model.assignment.LocalVarWrapper; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.source.Method; @@ -38,7 +42,101 @@ public class MapMappingMethod extends MappingMethod { private final FactoryMethod factoryMethod; private final boolean overridden; - public MapMappingMethod(Method method, Assignment keyAssignment, Assignment valueAssignment, + public static class Builder { + + private String keyDateFormat; + private String valueDateFormat; + private List keyQualifiers; + private List valueQualifiers; + private Method method; + private MappingContext ctx; + + public Builder mappingContext( MappingContext mappingContext ) { + this.ctx = mappingContext; + return this; + } + + public Builder method( Method sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public Builder keyDateFormat( String keyDateFormat ) { + this.keyDateFormat = keyDateFormat; + return this; + } + + public Builder valueDateFormat( String valueDateFormat ) { + this.valueDateFormat = valueDateFormat; + return this; + } + + public Builder keyQualifiers( List keyQualifiers ) { + this.keyQualifiers = keyQualifiers; + return this; + } + + public Builder valueQualifiers( List valueQualifiers ) { + this.valueQualifiers = valueQualifiers; + return this; + } + + public MapMappingMethod build() { + + List sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters(); + List resultTypeParams = method.getResultType().getTypeParameters(); + + // find mapping method or conversion for key + Type keySourceType = sourceTypeParams.get( 0 ); + Type keyTargetType = resultTypeParams.get( 0 ); + + Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment( method, + "map key", + keySourceType, + keyTargetType, + null, // there is no targetPropertyName + keyDateFormat, + keyQualifiers, + "entry.getKey()" + ); + + if ( keyAssignment == null ) { + String message = String.format( "Can't create implementation of method %s. Found no method nor " + + "built-in conversion for mapping source key type to target key type.", method ); + method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message ); + } + + // find mapping method or conversion for value + Type valueSourceType = sourceTypeParams.get( 1 ); + Type valueTargetType = resultTypeParams.get( 1 ); + + Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment( method, + "map value", + valueSourceType, + valueTargetType, + null, // there is no targetPropertyName + valueDateFormat, + valueQualifiers, + "entry.getValue()" + ); + + if ( valueAssignment == null ) { + String message = String.format( "Can't create implementation of method %s. Found no method nor " + + "built-in conversion for mapping source value type to target value type.", method ); + method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message ); + } + + FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); + + keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() ); + valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes() ); + + return new MapMappingMethod( method, keyAssignment, valueAssignment, factoryMethod ); + } + + } + + private MapMappingMethod(Method method, Assignment keyAssignment, Assignment valueAssignment, FactoryMethod factoryMethod) { super( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingContext.java b/processor/src/main/java/org/mapstruct/ap/model/MappingContext.java new file mode 100644 index 000000000..5fa9427cc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingContext.java @@ -0,0 +1,171 @@ +/** + * 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.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Messager; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import org.mapstruct.ap.model.common.TypeFactory; +import org.mapstruct.ap.model.source.SourceMethod; +import org.mapstruct.ap.option.Options; +import org.mapstruct.ap.prism.MapperPrism; +import org.mapstruct.ap.util.MapperConfig; + +/** + * This class provides the context for the builders. + * + *

+ * The following mappers make use of this context: + *

    + *
  • {@link BeanMappingMethod.Builder}
  • + *
  • {@link PropertyMappingMethod.Builder}
  • + *
  • {@link IterableMappingMethod.Builder}
  • + *
  • {@link MapMappingMethod.Builder}
  • + *
  • {@link EnumMappingMethod.Builder}
  • + *
+ *

+ *

+ * The context provides: + *

    + *
  • Input for the building process, such as the source model (mapping methods found) and mapper references.
  • + *
  • Required factory, utility, reporting methods for building the mappings.
  • + *
  • Means to harbor results produced by the builders, such as forged- and virtual mapping methods that should be + * generated in a later stage.
  • + *
+ *

+ * + * @author Sjaak Derksen + */ +public class MappingContext { + + private final TypeFactory typeFactory; + private final Elements elementUtils; + private final Types typeUtils; + private final Messager messager; + private final Options options; + private final TypeElement mapperTypeElement; + private final List sourceModel; + private final List mapperReferences; + private MappingResolver mappingResolver; + private final List mappingsToGenerate = new ArrayList(); + + /** + * Private methods which are not present in the original mapper interface and are added to map certain property + * types. + */ + private final Set usedVirtualMappings = new HashSet(); + + + + public MappingContext( TypeFactory typeFactory, + Elements elementUtils, + Types typeUtils, + Messager messager, + Options options, + TypeElement mapper, + List sourceModel ) { + this.typeFactory = typeFactory; + this.elementUtils = elementUtils; + this.typeUtils = typeUtils; + this.messager = messager; + this.options = options; + this.mapperTypeElement = mapper; + this.sourceModel = sourceModel; + this.mapperReferences = initReferencedMappers( mapper ); + } + + public TypeElement getMapperTypeElement() { + return mapperTypeElement; + } + + public List getSourceModel() { + return sourceModel; + } + + public List getMapperReferences() { + return mapperReferences; + } + + public TypeFactory getTypeFactory() { + return typeFactory; + } + + public Elements getElementUtils() { + return elementUtils; + } + + public Types getTypeUtils() { + return typeUtils; + } + + public Messager getMessager() { + return messager; + } + + public Options getOptions() { + return options; + } + + public MappingResolver getMappingResolver() { + return mappingResolver; + } + + public void setMappingResolver(MappingResolver mappingResolver) { + this.mappingResolver = mappingResolver; + } + + public List getMappingsToGenerate() { + return mappingsToGenerate; + } + + public Set getUsedVirtualMappings() { + return usedVirtualMappings; + } + + private List initReferencedMappers(TypeElement element) { + List result = new LinkedList(); + List variableNames = new LinkedList(); + + MapperConfig mapperPrism = MapperConfig.getInstanceOn( element ); + + for ( TypeMirror usedMapper : mapperPrism.uses() ) { + DefaultMapperReference mapperReference = DefaultMapperReference.getInstance( + typeFactory.getType( usedMapper ), + MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null, + typeFactory, + variableNames + ); + + result.add( mapperReference ); + variableNames.add( mapperReference.getVariableName() ); + } + + return result; + } + + + +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingResolver.java b/processor/src/main/java/org/mapstruct/ap/model/MappingResolver.java new file mode 100644 index 000000000..3a7873763 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingResolver.java @@ -0,0 +1,63 @@ +/** + * 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 org.mapstruct.ap.model.assignment.Assignment; +import java.util.List; +import javax.lang.model.type.TypeMirror; +import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.Method; + +/** + * + * @author Sjaak Derksen + */ +public interface MappingResolver { + + /** + * returns a parameter assignment + * + * @param mappingMethod target mapping method + * @param mappedElement used for error messages + * @param sourceType parameter to match + * @param targetType return type to match + * @param targetPropertyName name of the target property + * @param dateFormat used for formatting dates in build in methods that need context information + * @param qualifiers used for further select the appropriate mapping method based on class and name + * @param sourceReference call to source type as string + * + * @return an assignment to a method parameter, which can either be: + *
    + *
  1. MethodReference
  2. + *
  3. TypeConversion
  4. + *
  5. Direct Assignment (empty TargetAssignment)
  6. + *
  7. null, no assignment found
  8. + *
+ */ + Assignment getTargetAssignment( + Method mappingMethod, + String mappedElement, + Type sourceType, + Type targetType, + String targetPropertyName, + String dateFormat, + List qualifiers, + String sourceReference ); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java similarity index 96% rename from processor/src/main/java/org/mapstruct/ap/model/assignment/MethodReference.java rename to processor/src/main/java/org/mapstruct/ap/model/MethodReference.java index db03d823e..179ca5e0f 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java @@ -16,17 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.model.assignment; +package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.Assignment; -import org.mapstruct.ap.model.FactoryMethod; -import org.mapstruct.ap.model.MapperReference; -import org.mapstruct.ap.model.MappingMethod; import org.mapstruct.ap.model.common.ConversionContext; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java index 10c151b55..bdb9066be 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java @@ -18,10 +18,30 @@ */ package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; +import java.util.List; import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.DIRECT; +import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; +import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; +import org.mapstruct.ap.model.assignment.AdderWrapper; +import org.mapstruct.ap.model.assignment.GetterCollectionOrMapWrapper; +import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper; +import org.mapstruct.ap.model.assignment.NullCheckWrapper; +import org.mapstruct.ap.model.assignment.SetterCollectionOrMapWrapper; +import org.mapstruct.ap.model.assignment.SetterWrapper; import org.mapstruct.ap.model.common.ModelElement; +import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.ForgedMethod; +import org.mapstruct.ap.model.source.Mapping; +import org.mapstruct.ap.model.source.SourceMethod; +import org.mapstruct.ap.util.Executables; /** @@ -41,12 +61,414 @@ public class PropertyMapping extends ModelElement { private final Assignment assignment; + public static class PropertyMappingBuilder { + + private MappingContext ctx; + private SourceMethod method; + private ExecutableElement targetAccessor; + private String targetPropertyName; + private Parameter parameter; + + public PropertyMappingBuilder mappingContext(MappingContext mappingContext) { + this.ctx = mappingContext; + return this; + } + + public PropertyMappingBuilder souceMethod( SourceMethod sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public PropertyMappingBuilder targetAccessor( ExecutableElement targetAccessor ) { + this.targetAccessor = targetAccessor; + return this; + } + + public PropertyMappingBuilder targetPropertyName( String targetPropertyName ) { + this.targetPropertyName = targetPropertyName; + return this; + } + + public PropertyMappingBuilder parameter( Parameter parameter ) { + this.parameter = parameter; + return this; + } + + private enum TargetAccessorType { + + GETTER, + SETTER, + ADDER + } + + public PropertyMapping build() { + + // check if there's a mapping defined + Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName ); + String dateFormat = null; + List qualifiers = null; + String sourcePropertyName; + if ( mapping != null ) { + dateFormat = mapping.getDateFormat(); + qualifiers = mapping.getQualifiers(); + sourcePropertyName = mapping.getSourcePropertyName(); + } + else { + sourcePropertyName = targetPropertyName; + } + + List sourceGetters = parameter.getType().getGetters(); + + // then iterate over source accessors (assuming the source is a bean) + for ( ExecutableElement sourceAccessor : sourceGetters ) { + + List sourceMappings = method.getMappings().get( sourcePropertyName ); + if ( method.getMappings().containsKey( sourcePropertyName ) ) { + for ( Mapping sourceMapping : sourceMappings ) { + boolean mapsToOtherTarget = !sourceMapping.getTargetName().equals( targetPropertyName ); + if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) + && !mapsToOtherTarget ) { + return getPropertyMapping( sourceAccessor, dateFormat, qualifiers ); + } + } + } + else if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) ) { + return getPropertyMapping( sourceAccessor, dateFormat, qualifiers ); + } + } + return null; + } + + private PropertyMapping getPropertyMapping( ExecutableElement sourceAccessor, + String dateFormat, + List qualifiers ) { + + Type sourceType; + Type targetType; + TargetAccessorType targetAccessorType; + String sourceReference = parameter.getName() + "." + sourceAccessor.getSimpleName().toString() + "()"; + String iteratorReference = null; + boolean sourceIsCollection = false; + if ( Executables.isSetterMethod( targetAccessor ) ) { + sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor ); + targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType(); + targetAccessorType = TargetAccessorType.SETTER; + } + else if ( Executables.isAdderMethod( targetAccessor ) ) { + sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor ); + if ( sourceType.isCollectionType() ) { + sourceIsCollection = true; + sourceType = sourceType.getTypeParameters().get( 0 ); + iteratorReference = Executables.getElementNameForAdder( targetAccessor ); + } + targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType(); + targetAccessorType = TargetAccessorType.ADDER; + } + else { + sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor ); + targetType = ctx.getTypeFactory().getReturnType( targetAccessor ); + targetAccessorType = TargetAccessorType.GETTER; + } + String sourcePropertyName = Executables.getPropertyName( sourceAccessor ); + String mappedElement = "property '" + sourcePropertyName + "'"; + + Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, + mappedElement, + sourceType, + targetType, + targetPropertyName, + dateFormat, + qualifiers, + iteratorReference != null ? iteratorReference : sourceReference + ); + + if ( assignment == null ) { + assignment = forgeMapping( sourceType, targetType, sourceReference, method.getExecutable() ); + } + + if ( assignment != null ) { + + if ( targetType.isCollectionOrMapType() ) { + + // wrap the setter in the collection / map initializers + if ( targetAccessorType == TargetAccessorType.SETTER ) { + + // wrap the assignment in a new Map or Collection implementation if this is not done in a + // mapping method. Note, typeconversons do not apply to collections or maps + Assignment newCollectionOrMap = null; + if ( assignment.getType() == DIRECT ) { + newCollectionOrMap = + new NewCollectionOrMapWrapper( assignment, targetType.getImportTypes() ); + newCollectionOrMap = new SetterWrapper( newCollectionOrMap, method.getThrownTypes() ); + } + + // wrap the assignment in the setter method + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + + // target accessor is setter, so wrap the setter in setter map/ collection handling + assignment = new SetterCollectionOrMapWrapper( + assignment, + targetAccessor.getSimpleName().toString(), + newCollectionOrMap + ); + } + else { + // wrap the assignment in the setter method + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + + // target accessor is getter, so wrap the setter in getter map/ collection handling + assignment = new GetterCollectionOrMapWrapper( assignment ); + } + + // For collections and maps include a null check, when the assignment type is DIRECT. + // for mapping methods (builtin / custom), the mapping method is responsible for the + // null check. Typeconversions do not apply to collections and maps. + if ( assignment.getType() == DIRECT ) { + assignment = new NullCheckWrapper( assignment ); + } + } + else { + if ( targetAccessorType == TargetAccessorType.SETTER ) { + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + if ( !sourceType.isPrimitive() + && ( assignment.getType() == TYPE_CONVERTED + || assignment.getType() == TYPE_CONVERTED_MAPPED + || assignment.getType() == DIRECT && targetType.isPrimitive() ) ) { + // for primitive types null check is not possible at all, but a conversion needs + // a null check. + assignment = new NullCheckWrapper( assignment ); + } + } + else { + // TargetAccessorType must be ADDER + if ( sourceIsCollection ) { + assignment = new AdderWrapper( + assignment, + method.getThrownTypes(), + sourceReference, + sourceType + ); + } + else { + // Possibly adding null to a target collection. So should be surrounded by an null check. + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + assignment = new NullCheckWrapper( assignment ); + } + } + } + } + else { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Can't map property \"%s %s\" to \"%s %s\".", + sourceType, + sourcePropertyName, + targetType, + targetPropertyName + ), + method.getExecutable() + ); + } + return new PropertyMapping( + parameter.getName(), + targetAccessor.getSimpleName().toString(), + targetType, + assignment + ); + } + + private Assignment forgeMapping( Type sourceType, Type targetType, String sourceReference, Element element ) { + + Assignment assignment = null; + if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { + + ForgedMethod methodToGenerate = new ForgedMethod( sourceType, targetType, element ); + IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder( ); + + + IterableMappingMethod iterableMappingMethod = builder + .mappingContext( ctx ) + .method( methodToGenerate ) + .build(); + + if ( !ctx.getMappingsToGenerate().contains( iterableMappingMethod ) ) { + ctx.getMappingsToGenerate().add( iterableMappingMethod ); + } + assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType ); + assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); + + } + else if ( sourceType.isMapType() && targetType.isMapType() ) { + + ForgedMethod methodToGenerate = new ForgedMethod( sourceType, targetType, element ); + + MapMappingMethod.Builder builder = new MapMappingMethod.Builder( ); + MapMappingMethod mapMappingMethod = builder + .mappingContext( ctx ) + .method( methodToGenerate ) + .build(); + + if ( !ctx.getMappingsToGenerate().contains( mapMappingMethod ) ) { + ctx.getMappingsToGenerate().add( mapMappingMethod ); + } + assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType ); + assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); + } + return assignment; + } + } + + public static class ConstantMappingBuilder { + + private MappingContext ctx; + private SourceMethod method; + private String constantExpression; + private ExecutableElement targetAccessor; + private String dateFormat; + private List qualifiers; + + public ConstantMappingBuilder mappingContext(MappingContext mappingContext) { + this.ctx = mappingContext; + return this; + } + + public ConstantMappingBuilder sourceMethod( SourceMethod sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public ConstantMappingBuilder constantExpression( String constantExpression ) { + this.constantExpression = constantExpression; + return this; + } + + public ConstantMappingBuilder targetAccessor( ExecutableElement targetAccessor ) { + this.targetAccessor = targetAccessor; + return this; + } + + public ConstantMappingBuilder dateFormat( String dateFormat ) { + this.dateFormat = dateFormat; + return this; + } + + public ConstantMappingBuilder qualifiers( List qualifiers ) { + this.qualifiers = qualifiers; + return this; + } + + public PropertyMapping build() { + + // source + String mappedElement = "constant '" + constantExpression + "'"; + Type sourceType = ctx.getTypeFactory().getType( String.class ); + + // target + Type targetType; + if ( Executables.isSetterMethod( targetAccessor ) ) { + targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType(); + } + else { + targetType = ctx.getTypeFactory().getReturnType( targetAccessor ); + } + + String targetPropertyName = Executables.getPropertyName( targetAccessor ); + + Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, + mappedElement, + sourceType, + targetType, + targetPropertyName, + dateFormat, + qualifiers, + constantExpression + ); + + if ( assignment != null ) { + + // target accessor is setter, so decorate assignment as setter + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + + // wrap when dealing with getter only on target + if ( Executables.isGetterMethod( targetAccessor ) ) { + assignment = new GetterCollectionOrMapWrapper( assignment ); + } + } + else { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Can't map \"%s %s\" to \"%s %s\".", + sourceType, + constantExpression, + targetType, + targetPropertyName + ), + method.getExecutable() + ); + } + + return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment ); + } + } + + public static class JavaExpressionMappingBuilder { + + private MappingContext ctx; + private SourceMethod method; + private String javaExpression; + private ExecutableElement targetAccessor; + + public JavaExpressionMappingBuilder mappingContext(MappingContext mappingContext) { + this.ctx = mappingContext; + return this; + } + + public JavaExpressionMappingBuilder souceMethod( SourceMethod sourceMethod ) { + this.method = sourceMethod; + return this; + } + + public JavaExpressionMappingBuilder javaExpression( String javaExpression ) { + this.javaExpression = javaExpression; + return this; + } + + public JavaExpressionMappingBuilder targetAccessor( ExecutableElement targetAccessor ) { + this.targetAccessor = targetAccessor; + return this; + } + + public PropertyMapping build() { + + Assignment assignment = AssignmentFactory.createSimple( javaExpression ); + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + + Type targetType; + if ( Executables.isSetterMethod( targetAccessor ) ) { + targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType(); + } + else { + targetType = ctx.getTypeFactory().getReturnType( targetAccessor ); + + // target accessor is getter, so wrap the setter in getter map/ collection handling + assignment = new GetterCollectionOrMapWrapper( assignment ); + } + + return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment ); + } + + + } + + // Constructor for creating mappings of constant expressions. - public PropertyMapping(String targetAccessorName, Type targetType, Assignment propertyAssignment) { + private PropertyMapping(String targetAccessorName, Type targetType, Assignment propertyAssignment) { this( null, targetAccessorName, targetType, propertyAssignment ); } - public PropertyMapping(String sourceBeanName, String targetAccessorName, Type targetType, Assignment assignment) { + private PropertyMapping(String sourceBeanName, String targetAccessorName, Type targetType, Assignment assignment) { this.sourceBeanName = sourceBeanName; diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/TypeConversion.java b/processor/src/main/java/org/mapstruct/ap/model/TypeConversion.java similarity index 97% rename from processor/src/main/java/org/mapstruct/ap/model/assignment/TypeConversion.java rename to processor/src/main/java/org/mapstruct/ap/model/TypeConversion.java index 829aa6ad6..a77b23d9e 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/TypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/model/TypeConversion.java @@ -16,13 +16,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.model.assignment; +package org.mapstruct.ap.model; +import org.mapstruct.ap.model.assignment.Assignment; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.ModelElement; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/AdderWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/AdderWrapper.java index 49cf519b8..5480d9dc5 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/AdderWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/AdderWrapper.java @@ -23,7 +23,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.Type; /** diff --git a/processor/src/main/java/org/mapstruct/ap/model/Assignment.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/Assignment.java similarity index 98% rename from processor/src/main/java/org/mapstruct/ap/model/Assignment.java rename to processor/src/main/java/org/mapstruct/ap/model/assignment/Assignment.java index a14ed8092..4085ff7e0 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/Assignment.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/Assignment.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.model; +package org.mapstruct.ap.model.assignment; import java.util.List; import java.util.Set; diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentFactory.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentFactory.java deleted file mode 100644 index e307d376a..000000000 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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.assignment; - -import java.util.List; -import java.util.Set; -import org.mapstruct.ap.model.Assignment; -import org.mapstruct.ap.model.FactoryMethod; -import org.mapstruct.ap.model.MapperReference; -import org.mapstruct.ap.model.common.ConversionContext; -import org.mapstruct.ap.model.common.Type; -import org.mapstruct.ap.model.source.Method; -import org.mapstruct.ap.model.source.SourceMethod; -import org.mapstruct.ap.model.source.builtin.BuiltInMethod; - -/** - * Factory class for creating all types of assignments - * - * @author Sjaak Derksen - */ -public class AssignmentFactory { - - private AssignmentFactory() { - } - - public static Assignment createTypeConversion(Set importTypes, List exceptionTypes, String expression) { - return new TypeConversion( importTypes, exceptionTypes, expression ); - } - - public static FactoryMethod createFactory(SourceMethod method, MapperReference declaringMapper) { - return new MethodReference( method, declaringMapper, null ); - } - - public static Assignment createMethodReference(Method method, MapperReference declaringMapper, - Type targetType) { - return new MethodReference( method, declaringMapper, targetType ); - } - - public static Assignment createMethodReference(BuiltInMethod method, ConversionContext contextParam) { - return new MethodReference( method, contextParam ); - } - - public static Direct createSimple(String sourceRef) { - return new Direct( sourceRef ); - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentWrapper.java index 1e5901c84..5c6d20599 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/AssignmentWrapper.java @@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment; import java.util.List; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.ModelElement; import org.mapstruct.ap.model.common.Type; diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/GetterCollectionOrMapWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/GetterCollectionOrMapWrapper.java index 2bcf383d2..a5ce9f64c 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/GetterCollectionOrMapWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/GetterCollectionOrMapWrapper.java @@ -18,8 +18,6 @@ */ package org.mapstruct.ap.model.assignment; -import org.mapstruct.ap.model.Assignment; - /** * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/LocalVarWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/LocalVarWrapper.java index cf3fb7693..e89bc1108 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/LocalVarWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/LocalVarWrapper.java @@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment; import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.Type; /** diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/NewCollectionOrMapWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/NewCollectionOrMapWrapper.java index 935e8fb5b..07e85917a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/NewCollectionOrMapWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/NewCollectionOrMapWrapper.java @@ -21,7 +21,6 @@ package org.mapstruct.ap.model.assignment; import java.util.HashSet; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.Type; /** diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/NullCheckWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/NullCheckWrapper.java index 162dbd208..a01bed66c 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/NullCheckWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/NullCheckWrapper.java @@ -18,8 +18,6 @@ */ package org.mapstruct.ap.model.assignment; -import org.mapstruct.ap.model.Assignment; - /** * Wraps the assignment in a null check. * diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterCollectionOrMapWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterCollectionOrMapWrapper.java index a62313abd..4f33e9175 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterCollectionOrMapWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterCollectionOrMapWrapper.java @@ -21,7 +21,6 @@ package org.mapstruct.ap.model.assignment; import java.util.HashSet; import java.util.Set; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.Type; /** diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterWrapper.java index 110f9bd15..152eb113f 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/SetterWrapper.java @@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment; import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.model.Assignment; import org.mapstruct.ap.model.common.Type; /** diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index 0615045d6..98ea0eea1 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -18,18 +18,13 @@ */ package org.mapstruct.ap.processor; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.processing.Messager; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; @@ -38,48 +33,24 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; -import org.mapstruct.CollectionMappingStrategy; -import org.mapstruct.ap.model.Assignment; -import static org.mapstruct.ap.model.Assignment.AssignmentType.DIRECT; -import static org.mapstruct.ap.model.Assignment.AssignmentType.TYPE_CONVERTED; -import static org.mapstruct.ap.model.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; import org.mapstruct.ap.model.BeanMappingMethod; import org.mapstruct.ap.model.Decorator; -import org.mapstruct.ap.model.DefaultMapperReference; import org.mapstruct.ap.model.DelegatingMethod; import org.mapstruct.ap.model.EnumMappingMethod; -import org.mapstruct.ap.model.FactoryMethod; import org.mapstruct.ap.model.IterableMappingMethod; import org.mapstruct.ap.model.MapMappingMethod; import org.mapstruct.ap.model.Mapper; import org.mapstruct.ap.model.MapperReference; import org.mapstruct.ap.model.MappingMethod; -import org.mapstruct.ap.model.PropertyMapping; -import org.mapstruct.ap.model.assignment.AdderWrapper; -import org.mapstruct.ap.model.assignment.AssignmentFactory; -import org.mapstruct.ap.model.assignment.GetterCollectionOrMapWrapper; -import org.mapstruct.ap.model.assignment.LocalVarWrapper; -import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper; -import org.mapstruct.ap.model.assignment.NullCheckWrapper; -import org.mapstruct.ap.model.assignment.SetterCollectionOrMapWrapper; -import org.mapstruct.ap.model.assignment.SetterWrapper; -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.EnumMapping; -import org.mapstruct.ap.model.source.ForgedMethod; 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.selector.MethodSelectors; import org.mapstruct.ap.option.Options; -import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.prism.DecoratedWithPrism; -import org.mapstruct.ap.prism.MapperPrism; -import org.mapstruct.ap.processor.creation.MappingResolver; -import org.mapstruct.ap.util.Executables; +import org.mapstruct.ap.model.MappingContext; +import org.mapstruct.ap.processor.creation.MappingResolverImpl; import org.mapstruct.ap.util.MapperConfig; -import org.mapstruct.ap.util.Strings; /** * A {@link ModelElementProcessor} which creates a {@link Mapper} from the given @@ -89,19 +60,13 @@ import org.mapstruct.ap.util.Strings; */ public class MapperCreationProcessor implements ModelElementProcessor, Mapper> { - private enum TargetAccessorType { - GETTER, - SETTER, - ADDER - } private Elements elementUtils; private Types typeUtils; private Messager messager; private Options options; private TypeFactory typeFactory; - private MappingResolver mappingResolver; - private final List mappingsToGenerate = new ArrayList(); + private MappingContext mappingContext; @Override public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List sourceModel) { @@ -110,8 +75,17 @@ public class MapperCreationProcessor implements ModelElementProcessor methods) { - List mapperReferences = getReferencedMappers( element ); - List mappingMethods = getMappingMethods( mapperReferences, methods, element ); - mappingMethods.addAll( mappingResolver.getVirtualMethodsToGenerate() ); - mappingMethods.addAll( mappingsToGenerate ); + List mapperReferences = mappingContext.getMapperReferences(); + List mappingMethods = getMappingMethods( methods ); + mappingMethods.addAll( mappingContext.getUsedVirtualMappings() ); + mappingMethods.addAll( mappingContext.getMappingsToGenerate() ); Mapper mapper = new Mapper.Builder() .element( element ) @@ -140,36 +114,6 @@ public class MapperCreationProcessor implements ModelElementProcessor methods) { DecoratedWithPrism decoratorPrism = DecoratedWithPrism.getInstanceOn( element ); @@ -245,27 +189,6 @@ public class MapperCreationProcessor implements ModelElementProcessor getReferencedMappers(TypeElement element) { - List mapperReferences = new LinkedList(); - List variableNames = new LinkedList(); - - MapperConfig mapperPrism = MapperConfig.getInstanceOn( element ); - - for ( TypeMirror usedMapper : mapperPrism.uses() ) { - DefaultMapperReference mapperReference = DefaultMapperReference.getInstance( - typeFactory.getType( usedMapper ), - MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null, - typeFactory, - variableNames - ); - - mapperReferences.add( mapperReference ); - variableNames.add( mapperReference.getVariableName() ); - } - - return mapperReferences; - } - private SortedSet getExtraImports(TypeElement element) { SortedSet extraImports = new TreeSet(); @@ -280,8 +203,7 @@ public class MapperCreationProcessor implements ModelElementProcessor getMappingMethods(List mapperReferences, List methods, - TypeElement element) { + private List getMappingMethods(List methods ) { List mappingMethods = new ArrayList(); for ( SourceMethod method : methods ) { @@ -293,6 +215,8 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences, List methods, - Type returnType) { - FactoryMethod result = null; - for ( SourceMethod method : methods ) { - if ( !method.overridesMethod() && !method.isIterableMapping() && !method.isMapMapping() - && method.getSourceParameters().isEmpty() ) { - - List parameterTypes = - MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), null, returnType ); - - if ( method.matches( parameterTypes, returnType ) ) { - if ( result == null ) { - MapperReference mapperReference = findMapperReference( mapperReferences, method ); - result = AssignmentFactory.createFactory( method, mapperReference ); - } - else { - messager.printMessage( - Kind.ERROR, - String.format( - "Ambiguous factory methods: \"%s\" conflicts with \"%s\".", - result, - method - ), - method.getExecutable() - ); - } - } - } - } - return result; - } - private void reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(SourceMethod method) { if ( method.getReturnType().getTypeMirror().getKind() != TypeKind.VOID && method.getReturnType().isInterface() && @@ -449,239 +353,6 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences, - List methods, - SourceMethod method, - ExecutableElement targetAccessor, - String targetPropertyName, - Parameter parameter) { - - // check if there's a mapping defined - Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName ); - String dateFormat = null; - List qualifiers = null; - String sourcePropertyName; - if ( mapping != null ) { - dateFormat = mapping.getDateFormat(); - qualifiers = mapping.getQualifiers(); - sourcePropertyName = mapping.getSourcePropertyName(); - } - else { - sourcePropertyName = targetPropertyName; - } - - List sourceGetters = parameter.getType().getGetters(); - - // then iterate over source accessors (assuming the source is a bean) - for ( ExecutableElement sourceAccessor : sourceGetters ) { - - List sourceMappings = method.getMappings().get( sourcePropertyName ); - if ( method.getMappings().containsKey( sourcePropertyName ) ) { - for ( Mapping sourceMapping : sourceMappings ) { - boolean mapsToOtherTarget = !sourceMapping.getTargetName().equals( targetPropertyName ); - if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) && - !mapsToOtherTarget ) { - return getPropertyMapping( - mapperReferences, - methods, - method, - parameter, - sourceAccessor, - targetAccessor, - targetPropertyName, - dateFormat, - qualifiers - ); - } - } - } - else if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) ) { - return getPropertyMapping( - mapperReferences, - methods, - method, - parameter, - sourceAccessor, - targetAccessor, - targetPropertyName, - dateFormat, - qualifiers - ); - } - } - return null; - } - - private BeanMappingMethod getBeanMappingMethod(List mapperReferences, List methods, - SourceMethod method, TypeElement element) { - - // fetch settings from element to implement - ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element ); - CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy( element ); - - List propertyMappings = new ArrayList(); - Set mappedTargetProperties = new HashSet(); - Set ignoredTargetProperties = new HashSet(); - - if ( !reportErrorIfMappedPropertiesDontExist( method ) ) { - return null; - } - - // collect all target accessors - List targetAccessors = new ArrayList(); - targetAccessors.addAll( method.getResultType().getSetters() ); - targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() ); - - for ( ExecutableElement targetAccessor : targetAccessors ) { - String targetPropertyName = Executables.getPropertyName( targetAccessor ); - - Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName ); - - if ( mapping != null && mapping.isIgnored() ) { - ignoredTargetProperties.add( targetPropertyName ); - continue; - } - - // A target access is in general a setter method on the target object. However, in case of collections, - // the current target accessor can also be a getter method. - // - // The following if block, checks if the target accessor should be overruled by an add method. - if ( cmStrategy.equals( CollectionMappingStrategy.SETTER_PREFERRED ) || - cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { - - // first check if there's a setter method. - ExecutableElement adderMethod = null; - if ( Executables.isSetterMethod( targetAccessor ) ) { - Type targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); - // ok, the current accessor is a setter. So now the strategy determines what to use - if ( cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) { - adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName ); - } - } - else if ( Executables.isGetterMethod( targetAccessor ) ) { - // the current accessor is a getter (no setter available). But still, an add method is according - // to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter. - Type targetType = typeFactory.getReturnType( targetAccessor ); - adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName ); - } - if ( adderMethod != null ) { - // an adder has been found (according strategy) so overrule current choice. - targetAccessor = adderMethod; - } - } - - PropertyMapping propertyMapping = null; - if ( mapping != null ) { - if ( mapping.getSourceParameterName() != null ) { - // this is a parameterized property, so sourceParameter.property - Parameter parameter = method.getSourceParameter( mapping.getSourceParameterName() ); - propertyMapping = getPropertyMapping( - mapperReferences, - methods, - method, - targetAccessor, - targetPropertyName, - parameter - ); - } - else if ( Executables.isSetterMethod( targetAccessor ) || - Executables.isGetterMethod( targetAccessor ) ) { - - if ( !mapping.getConstant().isEmpty() ) { - // its a constant - propertyMapping = getConstantMapping( - mapperReferences, - methods, - method, - "\"" + mapping.getConstant() + "\"", - targetAccessor, - mapping.getDateFormat(), - mapping.getQualifiers() - ); - } - - else if ( !mapping.getJavaExpression().isEmpty() ) { - // its an expression - propertyMapping = getJavaExpressionMapping( - method, - mapping.getJavaExpression(), - targetAccessor - ); - } - } - } - - if ( propertyMapping == null ) { - for ( Parameter sourceParameter : method.getSourceParameters() ) { - PropertyMapping newPropertyMapping = getPropertyMapping( - mapperReferences, - methods, - method, - targetAccessor, - targetPropertyName, - sourceParameter - ); - if ( propertyMapping != null && newPropertyMapping != null ) { - messager.printMessage( - Kind.ERROR, - "Several possible source properties for target property \"" + targetPropertyName + "\".", - method.getExecutable() - ); - break; - } - else if ( newPropertyMapping != null ) { - propertyMapping = newPropertyMapping; - } - } - } - - if ( propertyMapping != null ) { - propertyMappings.add( propertyMapping ); - mappedTargetProperties.add( targetPropertyName ); - } - } - - Set targetProperties = Executables.getPropertyNames( targetAccessors ); - - reportErrorForUnmappedTargetPropertiesIfRequired( - method, - unmappedTargetPolicy, - targetProperties, - mappedTargetProperties, - ignoredTargetProperties - ); - - FactoryMethod factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() ); - return new BeanMappingMethod( method, propertyMappings, factoryMethod ); - } - - private void reportErrorForUnmappedTargetPropertiesIfRequired(SourceMethod method, - ReportingPolicy unmappedTargetPolicy, - Set targetProperties, - Set mappedTargetProperties, - Set ignoredTargetProperties) { - - Set unmappedTargetProperties = new HashSet(); - - for ( String property : targetProperties ) { - if ( !mappedTargetProperties.contains( property ) && !ignoredTargetProperties.contains( property ) ) { - unmappedTargetProperties.add( property ); - } - } - - if ( !unmappedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { - messager.printMessage( - unmappedTargetPolicy.getDiagnosticKind(), - MessageFormat.format( - "Unmapped target {0,choice,1#property|1 rawMethods, SourceMethod method) { for ( SourceMethod oneMethod : rawMethods ) { if ( oneMethod.reverses( method ) ) { @@ -690,631 +361,4 @@ public class MapperCreationProcessor implements ModelElementProcessor getters = parameter.getType().getGetters(); - return Executables.getPropertyNames( getters ).contains( propertyName ); - } - - private boolean reportErrorIfMappedPropertiesDontExist(SourceMethod method) { - // only report errors if this method itself is configured - if ( method.isConfiguredByReverseMappingMethod() ) { - return true; - } - - // collect all target accessors - List targetAccessors = new ArrayList(); - targetAccessors.addAll( method.getResultType().getSetters() ); - targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() ); - - Set targetProperties = Executables.getPropertyNames( targetAccessors ); - - boolean foundUnmappedProperty = false; - - for ( List mappedProperties : method.getMappings().values() ) { - for ( Mapping mappedProperty : mappedProperties ) { - if ( mappedProperty.isIgnored() ) { - continue; - } - - if ( mappedProperty.getSourceParameterName() != null ) { - Parameter sourceParameter = method.getSourceParameter( mappedProperty.getSourceParameterName() ); - - if ( sourceParameter == null ) { - messager.printMessage( - Kind.ERROR, - String.format( - "Method has no parameter named \"%s\".", - mappedProperty.getSourceParameterName() - ), - method.getExecutable(), - mappedProperty.getMirror(), - mappedProperty.getSourceAnnotationValue() - ); - foundUnmappedProperty = true; - } - else { - if ( !hasSourceProperty( sourceParameter, mappedProperty.getSourcePropertyName() ) ) { - messager.printMessage( - Kind.ERROR, - String.format( - "The type of parameter \"%s\" has no property named \"%s\".", - mappedProperty.getSourceParameterName(), - mappedProperty.getSourcePropertyName() - ), - method.getExecutable(), - mappedProperty.getMirror(), - mappedProperty.getSourceAnnotationValue() - ); - foundUnmappedProperty = true; - } - } - - } - else if ( mappedProperty.getConstant().isEmpty() && - mappedProperty.getJavaExpression().isEmpty() && - !hasSourceProperty( method, mappedProperty.getSourcePropertyName() ) ) { - messager.printMessage( - Kind.ERROR, - String.format( - "No property named \"%s\" exists in source parameter(s).", - mappedProperty.getSourceName() - ), - method.getExecutable(), - mappedProperty.getMirror(), - mappedProperty.getSourceAnnotationValue() - ); - foundUnmappedProperty = true; - } - if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) { - messager.printMessage( - Kind.ERROR, - String.format( - "Unknown property \"%s\" in return type %s.", - mappedProperty.getTargetName(), - method.getResultType() - ), - method.getExecutable(), - mappedProperty.getMirror(), - mappedProperty.getTargetAnnotationValue() - ); - foundUnmappedProperty = true; - } - } - } - return !foundUnmappedProperty; - } - - private PropertyMapping getPropertyMapping(List mapperReferences, - List methods, - SourceMethod method, - Parameter parameter, - ExecutableElement sourceAccessor, - ExecutableElement targetAccessor, - String targetPropertyName, - String dateFormat, - List qualifiers) { - - Type sourceType; - Type targetType; - TargetAccessorType targetAccessorType; - String sourceReference = parameter.getName() + "." + sourceAccessor.getSimpleName().toString() + "()"; - String iteratorReference = null; - boolean sourceIsCollection = false; - if ( Executables.isSetterMethod( targetAccessor ) ) { - sourceType = typeFactory.getReturnType( sourceAccessor ); - targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); - targetAccessorType = TargetAccessorType.SETTER; - } - else if ( Executables.isAdderMethod( targetAccessor ) ) { - sourceType = typeFactory.getReturnType( sourceAccessor ); - if ( sourceType.isCollectionType() ) { - sourceIsCollection = true; - sourceType = sourceType.getTypeParameters().get( 0 ); - iteratorReference = Executables.getElementNameForAdder( targetAccessor ); - } - targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); - targetAccessorType = TargetAccessorType.ADDER; - } - else { - sourceType = typeFactory.getReturnType( sourceAccessor ); - targetType = typeFactory.getReturnType( targetAccessor ); - targetAccessorType = TargetAccessorType.GETTER; - } - String sourcePropertyName = Executables.getPropertyName( sourceAccessor ); - String mappedElement = "property '" + sourcePropertyName + "'"; - - Assignment assignment = mappingResolver.getTargetAssignment( - method, - mappedElement, - mapperReferences, - methods, - sourceType, - targetType, - targetPropertyName, - dateFormat, - qualifiers, - iteratorReference != null ? iteratorReference : sourceReference - ); - - if ( assignment == null ) { - assignment = forgeMapping( - mapperReferences, - methods, - sourceType, - targetType, - sourceReference, - method.getExecutable() - ); - } - - if ( assignment != null ) { - - if ( targetType.isCollectionOrMapType() ) { - - // wrap the setter in the collection / map initializers - if ( targetAccessorType == TargetAccessorType.SETTER ) { - - // wrap the assignment in a new Map or Collection implementation if this is not done in a mapping - // method. Note, typeconversons do not apply to collections or maps - Assignment newCollectionOrMap = null; - if ( assignment.getType() == DIRECT ) { - newCollectionOrMap = new NewCollectionOrMapWrapper( assignment, targetType.getImportTypes() ); - newCollectionOrMap = new SetterWrapper( newCollectionOrMap, method.getThrownTypes() ); - } - - // wrap the assignment in the setter method - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - - // target accessor is setter, so wrap the setter in setter map/ collection handling - assignment = new SetterCollectionOrMapWrapper( - assignment, - targetAccessor.getSimpleName().toString(), - newCollectionOrMap - ); - } - else { - // wrap the assignment in the setter method - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - - // target accessor is getter, so wrap the setter in getter map/ collection handling - assignment = new GetterCollectionOrMapWrapper( assignment ); - } - - // For collections and maps include a null check, when the assignment type is DIRECT. - // for mapping methods (builtin / custom), the mapping method is responsible for the - // null check. Typeconversions do not apply to collections and maps. - if ( assignment.getType() == DIRECT ) { - assignment = new NullCheckWrapper( assignment ); - } - } - else { - if ( targetAccessorType == TargetAccessorType.SETTER ) { - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - if ( !sourceType.isPrimitive() && - ( assignment.getType() == TYPE_CONVERTED || - assignment.getType() == TYPE_CONVERTED_MAPPED || - assignment.getType() == DIRECT && targetType.isPrimitive() ) ) { - // for primitive types null check is not possible at all, but a conversion needs - // a null check. - assignment = new NullCheckWrapper( assignment ); - } - } - else { - // TargetAccessorType must be ADDER - if ( sourceIsCollection ) { - assignment = - new AdderWrapper( assignment, method.getThrownTypes(), sourceReference, sourceType ); - } - else { - // Possibly adding null to a target collection. So should be surrounded by an null check. - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - assignment = new NullCheckWrapper( assignment ); - } - } - } - } - else { - messager.printMessage( - Kind.ERROR, - String.format( - "Can't map property \"%s %s\" to \"%s %s\".", - sourceType, - sourcePropertyName, - targetType, - targetPropertyName - ), - method.getExecutable() - ); - } - return new PropertyMapping( - parameter.getName(), - targetAccessor.getSimpleName().toString(), - targetType, - assignment - ); - } - - /** - * Creates a {@link PropertyMapping} representing the mapping of the given constant expression into the target - * property. - */ - private PropertyMapping getConstantMapping(List mapperReferences, - List methods, - SourceMethod method, - String constantExpression, - ExecutableElement targetAccessor, - String dateFormat, - List qualifiers) { - - // source - String mappedElement = "constant '" + constantExpression + "'"; - Type sourceType = typeFactory.getType( String.class ); - - // target - Type targetType; - if ( Executables.isSetterMethod( targetAccessor ) ) { - targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); - } - else { - targetType = typeFactory.getReturnType( targetAccessor ); - } - String targetPropertyName = Executables.getPropertyName( targetAccessor ); - - Assignment assignment = mappingResolver.getTargetAssignment( - method, - mappedElement, - mapperReferences, - methods, - sourceType, - targetType, - targetPropertyName, - dateFormat, - qualifiers, - constantExpression - ); - - if ( assignment != null ) { - - // target accessor is setter, so decorate assignment as setter - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - - // wrap when dealing with getter only on target - if ( Executables.isGetterMethod( targetAccessor ) ) { - assignment = new GetterCollectionOrMapWrapper( assignment ); - } - } - else { - messager.printMessage( - Kind.ERROR, - String.format( - "Can't map \"%s %s\" to \"%s %s\".", - sourceType, - constantExpression, - targetType, - targetPropertyName - ), - method.getExecutable() - ); - } - - return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment ); - } - - /** - * Creates a {@link PropertyMapping} representing the mapping of the given java expression into the target - * property. - */ - private PropertyMapping getJavaExpressionMapping(SourceMethod method, - String javaExpression, - ExecutableElement targetAccessor) { - - Assignment assignment = AssignmentFactory.createSimple( javaExpression ); - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - - Type targetType; - if ( Executables.isSetterMethod( targetAccessor ) ) { - targetType = typeFactory.getSingleParameter( targetAccessor ).getType(); - } - else { - targetType = typeFactory.getReturnType( targetAccessor ); - - // target accessor is getter, so wrap the setter in getter map/ collection handling - assignment = new GetterCollectionOrMapWrapper( assignment ); - } - - return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment ); - } - - private IterableMappingMethod getIterableMappingMethod(List mapperReferences, - List methods, - Method method, - String dateFormat, - List qualifiers) { - Type sourceElementType = method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 ); - Type targetElementType = method.getResultType().getTypeParameters().get( 0 ); - String conversionStr = Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() ); - - Assignment assignment = mappingResolver.getTargetAssignment( - method, - "collection element", - mapperReferences, - methods, - sourceElementType, - targetElementType, - null, // there is no targetPropertyName - dateFormat, - qualifiers, - conversionStr - ); - - if ( assignment == null ) { - String message = String.format( - "Can't create implementation of method %s. Found no method nor built-in conversion for mapping " - + "source element type into target element type.", - method - ); - method.printMessage( messager, Kind.ERROR, message ); - } - - // target accessor is setter, so decorate assignment as setter - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - - FactoryMethod factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() ); - return new IterableMappingMethod( method, assignment, factoryMethod ); - } - - private MapMappingMethod getMapMappingMethod(List mapperReferences, - List methods, - Method method, - String keyDateFormat, - String valueDateFormat, - List keyQualifiers, - List valueQualifiers ) { - List sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters(); - List resultTypeParams = method.getResultType().getTypeParameters(); - - // find mapping method or conversion for key - Type keySourceType = sourceTypeParams.get( 0 ); - Type keyTargetType = resultTypeParams.get( 0 ); - - Assignment keyAssignment = mappingResolver.getTargetAssignment( - method, - "map key", - mapperReferences, - methods, - keySourceType, - keyTargetType, - null, // there is no targetPropertyName - keyDateFormat, - keyQualifiers, - "entry.getKey()" - ); - - if ( keyAssignment == null ) { - String message = String.format( "Can't create implementation of method %s. Found no method nor built-in " - + "conversion for mapping source key type to target key type.", method ); - method.printMessage( messager, Kind.ERROR, message ); - } - - // find mapping method or conversion for value - Type valueSourceType = sourceTypeParams.get( 1 ); - Type valueTargetType = resultTypeParams.get( 1 ); - - Assignment valueAssignment = mappingResolver.getTargetAssignment( - method, - "map value", - mapperReferences, - methods, - valueSourceType, - valueTargetType, - null, // there is no targetPropertyName - valueDateFormat, - valueQualifiers, - "entry.getValue()" - ); - - if ( valueAssignment == null ) { - String message = String.format( "Can't create implementation of method %s. Found no method nor built-in " - + "conversion for mapping source value type to target value type.", method ); - method.printMessage( messager, Kind.ERROR, message ); - } - - FactoryMethod factoryMethod = getFactoryMethod( mapperReferences, methods, method.getReturnType() ); - - keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() ); - valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes() ); - - return new MapMappingMethod( method, keyAssignment, valueAssignment, factoryMethod ); - } - - private EnumMappingMethod getEnumMappingMethod(SourceMethod method) { - if ( !reportErrorIfMappedEnumConstantsDontExist( method ) || - !reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( method ) ) { - return null; - } - - List enumMappings = new ArrayList(); - - List sourceEnumConstants = method.getSourceParameters().iterator().next().getType().getEnumConstants(); - Map> mappings = method.getMappings(); - - for ( String enumConstant : sourceEnumConstants ) { - List mappedConstants = mappings.get( enumConstant ); - - if ( mappedConstants == null ) { - enumMappings.add( new EnumMapping( enumConstant, enumConstant ) ); - } - else if ( mappedConstants.size() == 1 ) { - enumMappings.add( new EnumMapping( enumConstant, mappedConstants.iterator().next().getTargetName() ) ); - } - else { - List targetConstants = new ArrayList( mappedConstants.size() ); - for ( Mapping mapping : mappedConstants ) { - targetConstants.add( mapping.getTargetName() ); - } - messager.printMessage( - Kind.ERROR, - String.format( - "One enum constant must not be mapped to more than one target constant, but constant %s is " + - "mapped to %s.", - enumConstant, - Strings.join( targetConstants, ", " ) - ), - method.getExecutable() - ); - } - } - - return new EnumMappingMethod( method, enumMappings ); - } - - private boolean reportErrorIfMappedEnumConstantsDontExist(SourceMethod method) { - // only report errors if this method itself is configured - if ( method.isConfiguredByReverseMappingMethod() ) { - return true; - } - - List sourceEnumConstants = method.getSourceParameters().iterator().next().getType().getEnumConstants(); - List targetEnumConstants = method.getReturnType().getEnumConstants(); - - boolean foundIncorrectMapping = false; - - for ( List mappedConstants : method.getMappings().values() ) { - for ( Mapping mappedConstant : mappedConstants ) { - if ( mappedConstant.getSourceName() == null ) { - messager.printMessage( - Kind.ERROR, - "A source constant must be specified for mappings of an enum mapping method.", - method.getExecutable(), - mappedConstant.getMirror() - ); - foundIncorrectMapping = true; - } - else if ( !sourceEnumConstants.contains( mappedConstant.getSourceName() ) ) { - messager.printMessage( - Kind.ERROR, - String.format( - "Constant %s doesn't exist in enum type %s.", - mappedConstant.getSourceName(), - method.getSourceParameters().iterator().next().getType() - ), - method.getExecutable(), - mappedConstant.getMirror(), - mappedConstant.getSourceAnnotationValue() - ); - foundIncorrectMapping = true; - } - if ( mappedConstant.getTargetName() == null ) { - messager.printMessage( - Kind.ERROR, - "A target constant must be specified for mappings of an enum mapping method.", - method.getExecutable(), - mappedConstant.getMirror() - ); - foundIncorrectMapping = true; - } - else if ( !targetEnumConstants.contains( mappedConstant.getTargetName() ) ) { - messager.printMessage( - Kind.ERROR, - String.format( - "Constant %s doesn't exist in enum type %s.", - mappedConstant.getTargetName(), - method.getReturnType() - ), - method.getExecutable(), - mappedConstant.getMirror(), - mappedConstant.getTargetAnnotationValue() - ); - foundIncorrectMapping = true; - } - } - } - - return !foundIncorrectMapping; - } - - private boolean reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( - SourceMethod method) { - - List sourceEnumConstants = method.getSourceParameters().iterator().next().getType().getEnumConstants(); - List targetEnumConstants = method.getReturnType().getEnumConstants(); - Set mappedSourceEnumConstants = method.getMappings().keySet(); - List unmappedSourceEnumConstants = new ArrayList(); - - for ( String sourceEnumConstant : sourceEnumConstants ) { - if ( !targetEnumConstants.contains( sourceEnumConstant ) && - !mappedSourceEnumConstants.contains( sourceEnumConstant ) ) { - unmappedSourceEnumConstants.add( sourceEnumConstant ); - } - } - - if ( !unmappedSourceEnumConstants.isEmpty() ) { - messager.printMessage( - Kind.ERROR, - String.format( - "The following constants from the source enum have no corresponding constant in the target enum " + - "and must be be mapped via @Mapping: %s", - Strings.join( unmappedSourceEnumConstants, ", " ) - ), - method.getExecutable() - ); - } - - return unmappedSourceEnumConstants.isEmpty(); - } - - private MapperReference findMapperReference(List mapperReferences, SourceMethod method) { - for ( MapperReference ref : mapperReferences ) { - if ( ref.getType().equals( method.getDeclaringMapper() ) ) { - return ref; - } - } - return null; - } - - public Assignment forgeMapping( List mapperReferences, - List methods, - Type sourceType, - Type targetType, - String sourceReference, - Element element) { - Assignment assignment = null; - if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { - - ForgedMethod methodToGenerate - = new ForgedMethod( sourceType, targetType, element ); - IterableMappingMethod iterableMappingMethod - = getIterableMappingMethod( mapperReferences, methods, methodToGenerate, null, null ); - if ( !mappingsToGenerate.contains( iterableMappingMethod ) ) { - mappingsToGenerate.add( iterableMappingMethod ); - } - assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType ); - assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); - - } - else if ( sourceType.isMapType() && targetType.isMapType() ) { - - ForgedMethod methodToGenerate - = new ForgedMethod( sourceType, targetType, element ); - MapMappingMethod mapMappingMethod - = getMapMappingMethod( mapperReferences, methods, methodToGenerate, null, null, null, null ); - if ( !mappingsToGenerate.contains( mapMappingMethod ) ) { - mappingsToGenerate.add( mapMappingMethod ); - } - assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType ); - assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); - } - return assignment; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolver.java b/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java similarity index 65% rename from processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolver.java rename to processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java index a015d2106..04b9d9b08 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java @@ -18,28 +18,26 @@ */ package org.mapstruct.ap.processor.creation; +import org.mapstruct.ap.model.MappingResolver; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.annotation.processing.Messager; 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.conversion.ConversionProvider; import org.mapstruct.ap.conversion.Conversions; -import org.mapstruct.ap.model.Assignment; +import org.mapstruct.ap.model.assignment.Assignment; import org.mapstruct.ap.model.MapperReference; +import org.mapstruct.ap.model.MappingContext; import org.mapstruct.ap.model.VirtualMappingMethod; -import org.mapstruct.ap.model.assignment.AssignmentFactory; -import org.mapstruct.ap.model.assignment.Direct; +import org.mapstruct.ap.model.AssignmentFactory; +import org.mapstruct.ap.model.Direct; import org.mapstruct.ap.model.common.ConversionContext; import org.mapstruct.ap.model.common.DefaultConversionContext; 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.SourceMethod; import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods; @@ -67,30 +65,29 @@ import org.mapstruct.ap.util.Strings; * * @author Sjaak Derksen */ -public class MappingResolver { +public class MappingResolverImpl implements MappingResolver { - private final Messager messager; - private final TypeFactory typeFactory; private final Conversions conversions; private final BuiltInMappingMethods builtInMethods; - private final Types typeUtils; - private final MethodSelectors methodSelectors; + private final MappingContext mappingContext; + + + /** * Private methods which are not present in the original mapper interface and are added to map certain property - * types. + types. + * @param mappingContext */ - private final Set virtualMethods; - - - public MappingResolver(Messager messager, TypeFactory typeFactory, Elements elementUtils, Types typeUtils) { - this.messager = messager; - this.typeFactory = typeFactory; - this.conversions = new Conversions( elementUtils, typeFactory ); - this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.virtualMethods = new HashSet(); - this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, typeFactory ); - this.typeUtils = typeUtils; + public MappingResolverImpl( MappingContext mappingContext ) { + this.conversions = new Conversions( mappingContext.getElementUtils(), mappingContext.getTypeFactory() ); + this.builtInMethods = new BuiltInMappingMethods( mappingContext.getTypeFactory() ); + this.methodSelectors = new MethodSelectors( + mappingContext.getTypeUtils(), + mappingContext.getElementUtils(), + mappingContext.getTypeFactory() + ); + this.mappingContext = mappingContext; } @@ -99,8 +96,6 @@ public class MappingResolver { * * @param mappingMethod target mapping method * @param mappedElement used for error messages - * @param mapperReferences list of references to mapper - * @param methods list of candidate methods * @param sourceType parameter to match * @param targetType return type to match * @param targetPropertyName name of the target property @@ -116,10 +111,10 @@ public class MappingResolver { *
  • null, no assignment found
  • * */ - public Assignment getTargetAssignment( Method mappingMethod, + @Override + public Assignment getTargetAssignment( + Method mappingMethod, String mappedElement, - List mapperReferences, - List methods, Type sourceType, Type targetType, String targetPropertyName, @@ -129,22 +124,19 @@ public class MappingResolver { ResolvingAttempt attempt = new ResolvingAttempt( mappingMethod, mappedElement, - mapperReferences, - methods, targetPropertyName, dateFormat, qualifiers, sourceReference, - this + conversions, + builtInMethods, + methodSelectors, + mappingContext ); return attempt.getTargetAssignment( sourceType, targetType ); } - public Set getVirtualMethodsToGenerate() { - return virtualMethods; - } - private static class ResolvingAttempt { @@ -156,7 +148,10 @@ public class MappingResolver { private final String dateFormat; private final List qualifiers; private final String sourceReference; - private final MappingResolver context; + private final Conversions conversions; + private final BuiltInMappingMethods builtInMethods; + private final MethodSelectors methodSelectors; + private final MappingContext mappingContext; // resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches, // second doesn't. In that case, the first builtin method should not lead to a virtual method @@ -165,22 +160,26 @@ public class MappingResolver { private ResolvingAttempt( Method mappingMethod, String mappedElement, - List mapperReferences, - List methods, String targetPropertyName, String dateFormat, List qualifiers, String sourceReference, - MappingResolver context ) { + Conversions conversions, + BuiltInMappingMethods builtInMethods, + MethodSelectors methodSelectors, + MappingContext mappingContext ) { this.mappingMethod = mappingMethod; this.mappedElement = mappedElement; - this.mapperReferences = mapperReferences; - this.methods = methods; + this.mapperReferences = mappingContext.getMapperReferences(); + this.methods = mappingContext.getSourceModel(); this.targetPropertyName = targetPropertyName; this.dateFormat = dateFormat; this.qualifiers = qualifiers; this.sourceReference = sourceReference; - this.context = context; + this.conversions = conversions; + this.builtInMethods = builtInMethods; + this.methodSelectors = methodSelectors; + this.mappingContext = mappingContext; this.virtualMethodCandidates = new HashSet(); } @@ -190,12 +189,12 @@ public class MappingResolver { Assignment referencedMethod = resolveViaMethod( sourceType, targetType ); if ( referencedMethod != null ) { referencedMethod.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); - context.virtualMethods.addAll( virtualMethodCandidates ); + mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates ); return referencedMethod; } // then direct assignable - if ( sourceType.isAssignableTo( targetType ) || context.isPropertyMappable( sourceType, targetType ) ) { + if ( sourceType.isAssignableTo( targetType ) || isPropertyMappable( sourceType, targetType ) ) { Assignment simpleAssignment = AssignmentFactory.createSimple( sourceReference ); return simpleAssignment; } @@ -210,21 +209,21 @@ public class MappingResolver { // 2 step method, first: method(method(souurce)) referencedMethod = resolveViaMethodAndMethod( sourceType, targetType ); if ( referencedMethod != null ) { - context.virtualMethods.addAll( virtualMethodCandidates ); + mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates ); return referencedMethod; } // 2 step method, then: method(conversion(souurce)) referencedMethod = resolveViaConversionAndMethod( sourceType, targetType ); if ( referencedMethod != null ) { - context.virtualMethods.addAll( virtualMethodCandidates ); + mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates ); return referencedMethod; } // 2 step method, finally: conversion(method(souurce)) conversion = resolveViaMethodAndConversion( sourceType, targetType ); if ( conversion != null ) { - context.virtualMethods.addAll( virtualMethodCandidates ); + mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates ); return conversion; } @@ -233,13 +232,14 @@ public class MappingResolver { } private Assignment resolveViaConversion( Type sourceType, Type targetType ) { - ConversionProvider conversionProvider = context.conversions.getConversion( sourceType, targetType ); + ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); if ( conversionProvider == null ) { return null; } - ConversionContext ctx = new DefaultConversionContext( context.typeFactory, targetType, dateFormat ); + ConversionContext ctx = + new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat ); return conversionProvider.to( ctx ); } @@ -259,11 +259,12 @@ public class MappingResolver { // then a matching built-in method BuiltInMethod matchingBuiltInMethod = - getBestMatch( context.builtInMethods.getBuiltInMethods(), sourceType, targetType ); + getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); if ( matchingBuiltInMethod != null ) { virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) ); - ConversionContext ctx = new DefaultConversionContext( context.typeFactory, targetType, dateFormat ); + ConversionContext ctx = + new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat ); Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx ); methodReference.setAssignment( AssignmentFactory.createSimple( sourceReference ) ); return methodReference; @@ -285,7 +286,7 @@ public class MappingResolver { private Assignment resolveViaMethodAndMethod( Type sourceType, Type targetType ) { List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( context.builtInMethods.getBuiltInMethods() ); + methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); Assignment methodRefY = null; @@ -331,7 +332,7 @@ public class MappingResolver { private Assignment resolveViaConversionAndMethod( Type sourceType, Type targetType ) { List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( context.builtInMethods.getBuiltInMethods() ); + methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); Assignment methodRefY = null; @@ -373,7 +374,7 @@ public class MappingResolver { private Assignment resolveViaMethodAndConversion( Type sourceType, Type targetType ) { List methodXCandidates = new ArrayList( methods ); - methodXCandidates.addAll( context.builtInMethods.getBuiltInMethods() ); + methodXCandidates.addAll( builtInMethods.getBuiltInMethods() ); Assignment conversionYRef = null; @@ -404,7 +405,7 @@ public class MappingResolver { private T getBestMatch( List methods, Type sourceType, Type returnType ) { - List candidates = context.methodSelectors.getMatchingMethods( + List candidates = methodSelectors.getMatchingMethods( mappingMethod, methods, sourceType, @@ -423,7 +424,7 @@ public class MappingResolver { returnType, Strings.join( candidates, ", " ) ); - mappingMethod.printMessage( context.messager, Kind.ERROR, errorMsg ); + mappingMethod.printMessage( mappingContext.getMessager(), Kind.ERROR, errorMsg ); } if ( !candidates.isEmpty() ) { @@ -453,114 +454,115 @@ public class MappingResolver { } return null; } - } - /** - * Whether the specified property can be mapped from source to target or not. A mapping if possible if one of - * the following conditions is true: - *
      - *
    • the source type is assignable to the target type
    • - *
    • a mapping method exists
    • - *
    • a built-in conversion exists
    • - *
    • the property is of a collection or map type and the constructor of the target type (either itself or its - * implementation type) accepts the source type.
    • - *
    - * - * @param property The property mapping to check. - * - * @return {@code true} if the specified property can be mapped, {@code false} otherwise. - */ - private boolean isPropertyMappable(Type sourceType, Type targetType) { - boolean collectionOrMapTargetTypeHasCompatibleConstructor = false; + /** + * Whether the specified property can be mapped from source to target or not. A mapping if possible if one of + * the following conditions is true: + *
      + *
    • the source type is assignable to the target type
    • + *
    • a mapping method exists
    • + *
    • a built-in conversion exists
    • + *
    • the property is of a collection or map type and the constructor of the target type (either itself or its + * implementation type) accepts the source type.
    • + *
    + * + * @param property The property mapping to check. + * + * @return {@code true} if the specified property can be mapped, {@code false} otherwise. + */ + private boolean isPropertyMappable( Type sourceType, Type targetType ) { + boolean collectionOrMapTargetTypeHasCompatibleConstructor = false; - if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { - collectionOrMapTargetTypeHasCompatibleConstructor = collectionTypeHasCompatibleConstructor( - sourceType, - targetType.getImplementationType() != null ? - targetType.getImplementationType() : targetType - ); + if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { + collectionOrMapTargetTypeHasCompatibleConstructor = collectionTypeHasCompatibleConstructor( + sourceType, + targetType.getImplementationType() != null + ? targetType.getImplementationType() : targetType + ); + } + + if ( sourceType.isMapType() && targetType.isMapType() ) { + collectionOrMapTargetTypeHasCompatibleConstructor = mapTypeHasCompatibleConstructor( + sourceType, + targetType.getImplementationType() != null + ? targetType.getImplementationType() : targetType + ); + } + + if ( ( ( targetType.isCollectionType() || targetType.isMapType() ) + && collectionOrMapTargetTypeHasCompatibleConstructor ) ) { + return true; + } + + return false; } - if ( sourceType.isMapType() && targetType.isMapType() ) { - collectionOrMapTargetTypeHasCompatibleConstructor = mapTypeHasCompatibleConstructor( - sourceType, - targetType.getImplementationType() != null ? - targetType.getImplementationType() : targetType - ); + /** + * Whether the given target type has a single-argument constructor which accepts the given source type. + * + * @param sourceType the source type + * @param targetType the target type + * + * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} + * otherwise. + */ + private boolean collectionTypeHasCompatibleConstructor( Type sourceType, Type targetType ) { + // note (issue #127): actually this should check for the presence of a matching constructor, with help of + // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead + // we just check whether the target type is parameterized in a way that it implicitly should have a + // constructor which accepts the source type + + TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty() + ? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror() + : sourceType.getTypeParameters().get( 0 ).getTypeMirror(); + + TypeMirror targetElementType = targetType.getTypeParameters().isEmpty() + ? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror() + : targetType.getTypeParameters().get( 0 ).getTypeMirror(); + + return mappingContext.getTypeUtils().isAssignable( sourceElementType, targetElementType ); } - if ( ( ( targetType.isCollectionType() || targetType.isMapType() ) && - collectionOrMapTargetTypeHasCompatibleConstructor ) ) { - return true; + /** + * Whether the given target type has a single-argument constructor which accepts the given source type. + * + * @param sourceType the source type + * @param targetType the target type + * + * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} + * otherwise. + */ + private boolean mapTypeHasCompatibleConstructor( Type sourceType, Type targetType ) { + // note (issue #127): actually this should check for the presence of a matching constructor, with help of + // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead + // we just check whether the target type is parameterized in a way that it implicitly should have a + // constructor which accepts the source type + + TypeMirror sourceKeyType; + TypeMirror targetKeyType; + TypeMirror sourceValueType; + TypeMirror targetValueType; + + if ( sourceType.getTypeParameters().isEmpty() ) { + sourceKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror(); + sourceValueType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror(); + } + else { + sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror(); + sourceValueType = sourceType.getTypeParameters().get( 1 ).getTypeMirror(); + } + + if ( targetType.getTypeParameters().isEmpty() ) { + targetKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror(); + targetValueType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror(); + } + else { + targetKeyType = targetType.getTypeParameters().get( 0 ).getTypeMirror(); + targetValueType = targetType.getTypeParameters().get( 1 ).getTypeMirror(); + } + + return mappingContext.getTypeUtils().isAssignable( sourceKeyType, targetKeyType ) + && mappingContext.getTypeUtils().isAssignable( sourceValueType, targetValueType ); } - - return false; - } - /** - * Whether the given target type has a single-argument constructor which accepts the given source type. - * - * @param sourceType the source type - * @param targetType the target type - * - * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} - * otherwise. - */ - private boolean collectionTypeHasCompatibleConstructor(Type sourceType, Type targetType) { - // note (issue #127): actually this should check for the presence of a matching constructor, with help of - // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead we - // just check whether the target type is parameterized in a way that it implicitly should have a constructor - // which accepts the source type - - TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty() ? - typeFactory.getType( Object.class ).getTypeMirror() : - sourceType.getTypeParameters().get( 0 ).getTypeMirror(); - - TypeMirror targetElementType = targetType.getTypeParameters().isEmpty() ? - typeFactory.getType( Object.class ).getTypeMirror() : - targetType.getTypeParameters().get( 0 ).getTypeMirror(); - - return typeUtils.isAssignable( sourceElementType, targetElementType ); - } - - /** - * Whether the given target type has a single-argument constructor which accepts the given source type. - * - * @param sourceType the source type - * @param targetType the target type - * - * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} - * otherwise. - */ - private boolean mapTypeHasCompatibleConstructor(Type sourceType, Type targetType) { - // note (issue #127): actually this should check for the presence of a matching constructor, with help of - // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead we - // just check whether the target type is parameterized in a way that it implicitly should have a constructor - // which accepts the source type - - TypeMirror sourceKeyType = null; - TypeMirror targetKeyType = null; - TypeMirror sourceValueType = null; - TypeMirror targetValueType = null; - - if ( sourceType.getTypeParameters().isEmpty() ) { - sourceKeyType = typeFactory.getType( Object.class ).getTypeMirror(); - sourceValueType = typeFactory.getType( Object.class ).getTypeMirror(); - } - else { - sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror(); - sourceValueType = sourceType.getTypeParameters().get( 1 ).getTypeMirror(); - } - - if ( targetType.getTypeParameters().isEmpty() ) { - targetKeyType = typeFactory.getType( Object.class ).getTypeMirror(); - targetValueType = typeFactory.getType( Object.class ).getTypeMirror(); - } - else { - targetKeyType = targetType.getTypeParameters().get( 0 ).getTypeMirror(); - targetValueType = targetType.getTypeParameters().get( 1 ).getTypeMirror(); - } - - return typeUtils.isAssignable( sourceKeyType, targetKeyType ) && - typeUtils.isAssignable( sourceValueType, targetValueType ); } } diff --git a/processor/src/main/resources/org.mapstruct.ap.model.assignment.Direct.ftl b/processor/src/main/resources/org.mapstruct.ap.model.Direct.ftl similarity index 100% rename from processor/src/main/resources/org.mapstruct.ap.model.assignment.Direct.ftl rename to processor/src/main/resources/org.mapstruct.ap.model.Direct.ftl diff --git a/processor/src/main/resources/org.mapstruct.ap.model.assignment.MethodReference.ftl b/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl similarity index 100% rename from processor/src/main/resources/org.mapstruct.ap.model.assignment.MethodReference.ftl rename to processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl diff --git a/processor/src/main/resources/org.mapstruct.ap.model.assignment.TypeConversion.ftl b/processor/src/main/resources/org.mapstruct.ap.model.TypeConversion.ftl similarity index 100% rename from processor/src/main/resources/org.mapstruct.ap.model.assignment.TypeConversion.ftl rename to processor/src/main/resources/org.mapstruct.ap.model.TypeConversion.ftl