From 964f676af68d2b51362d2f339b22e027da5f03c0 Mon Sep 17 00:00:00 2001 From: luxmeter Date: Sat, 1 Aug 2015 14:58:22 +0200 Subject: [PATCH] #600 added defaultValue feature --- .../src/main/java/org/mapstruct/Mapping.java | 12 ++ core/src/main/java/org/mapstruct/Mapping.java | 12 ++ .../ap/internal/model/BeanMappingMethod.java | 2 + .../ap/internal/model/PropertyMapping.java | 81 ++++++--- .../ap/internal/model/source/Mapping.java | 48 ++++-- .../mapstruct/ap/internal/util/Message.java | 2 + .../ap/internal/model/BeanMappingMethod.ftl | 6 +- .../ap/internal/model/PropertyMapping.ftl | 3 +- .../model/assignment/NullCheckWrapper.ftl | 11 ++ .../model/assignment/SetterWrapper.ftl | 24 ++- .../ap/test/defaultvalue/CountryDts.java | 58 +++++++ .../ap/test/defaultvalue/CountryEntity.java | 59 +++++++ .../ap/test/defaultvalue/CountryMapper.java | 60 +++++++ .../test/defaultvalue/DefaultValueTest.java | 162 ++++++++++++++++++ .../ap/test/defaultvalue/ErroneousMapper.java | 34 ++++ .../test/defaultvalue/ErroneousMapper2.java | 34 ++++ .../ap/test/defaultvalue/Region.java | 31 ++++ 17 files changed, 598 insertions(+), 41 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper2.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/defaultvalue/Region.java diff --git a/core-jdk8/src/main/java/org/mapstruct/Mapping.java b/core-jdk8/src/main/java/org/mapstruct/Mapping.java index a61da963c..15cb8d0a4 100644 --- a/core-jdk8/src/main/java/org/mapstruct/Mapping.java +++ b/core-jdk8/src/main/java/org/mapstruct/Mapping.java @@ -154,4 +154,16 @@ public @interface Mapping { * @return the dependencies of the mapped property */ String[] dependsOn() default { }; + + /** + *

+ * In case the source property is null the provided default {@link String} value is set. + * If the designated target + * property is not of type {@code String}, the value will be converted by applying a matching conversion method or + * built-in conversion. + *

+ * Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping. + * @return Default value to set in case the source property is null. + */ + String defaultValue() default ""; } diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index e9f2eface..644ba5720 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -151,4 +151,16 @@ public @interface Mapping { * @return the dependencies of the mapped property */ String[] dependsOn() default { }; + + /** + *

+ * In case the source property is null the provided default {@link String} value is set. + * If the designated target + * property is not of type {@code String}, the value will be converted by applying a matching conversion method or + * built-in conversion. + *

+ * Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping. + * @return Default value to set in case the source property is null. + */ + String defaultValue() default ""; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index e18faa32c..24cb81dbf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -302,6 +302,7 @@ public class BeanMappingMethod extends MappingMethod { .dateFormat( mapping.getDateFormat() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) + .defaultValue( mapping.getDefaultValue() ) .build(); handledTargets.add( mapping.getTargetName() ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); @@ -424,6 +425,7 @@ public class BeanMappingMethod extends MappingMethod { .qualifiers( mapping != null ? mapping.getQualifiers() : null ) .resultType( mapping != null ? mapping.getResultType() : null ) .dateFormat( mapping != null ? mapping.getDateFormat() : null ) + .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) .build(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 37e55cd86..c15760d73 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -18,20 +18,6 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED_TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - import org.mapstruct.ap.internal.model.assignment.AdderWrapper; import org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper; import org.mapstruct.ap.internal.model.assignment.Assignment; @@ -53,6 +39,19 @@ import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED_TYPE_CONVERTED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; +import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; + /** * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to * {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the @@ -69,6 +68,8 @@ public class PropertyMapping extends ModelElement { private final Type targetType; private final Assignment assignment; private final List dependsOn; + private String defaultValue; + private Assignment defaultValueAssignment; @SuppressWarnings("unchecked") private static class MappingBuilderBase> { @@ -121,6 +122,7 @@ public class PropertyMapping extends ModelElement { // initial properties private String dateFormat; + private String defaultValue; private List qualifiers; private TypeMirror resultType; private SourceReference sourceReference; @@ -145,6 +147,11 @@ public class PropertyMapping extends ModelElement { return this; } + public PropertyMappingBuilder defaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + private enum TargetWriteAccessorType { GETTER, SETTER, @@ -237,10 +244,31 @@ public class PropertyMapping extends ModelElement { targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, assignment, - dependsOn + dependsOn, + getDefaultValueAssignment() ); } + private Assignment getDefaultValueAssignment() { + if ( defaultValue != null && !getSourceType().isPrimitive() ) { + PropertyMapping build = new ConstantMappingBuilder() + .constantExpression( '"' + defaultValue + '"' ) + .dateFormat( dateFormat ) + .qualifiers( qualifiers ) + .resultType( resultType ) + .dependsOn( dependsOn ) + .existingVariableNames( existingVariableNames ) + .mappingContext( ctx ) + .sourceMethod( method ) + .targetPropertyName( targetPropertyName ) + .targetReadAccessor( targetReadAccessor ) + .targetWriteAccessor( targetWriteAccessor ) + .build(); + return build.getAssignment(); + } + return null; + } + private Assignment assignObject(Type sourceType, Type targetType, TargetWriteAccessorType targetAccessorType, Assignment rhs) { @@ -657,7 +685,8 @@ public class PropertyMapping extends ModelElement { targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, assignment, - dependsOn + dependsOn, + null ); } } @@ -702,7 +731,8 @@ public class PropertyMapping extends ModelElement { targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, assignment, - dependsOn + dependsOn, + null ); } @@ -710,13 +740,14 @@ public class PropertyMapping extends ModelElement { // Constructor for creating mappings of constant expressions. private PropertyMapping(String name, String targetWriteAccessorName, String targetReadAccessorName, Type targetType, - Assignment propertyAssignment, List dependsOn) { - this( name, null, targetWriteAccessorName, targetReadAccessorName, targetType, propertyAssignment, dependsOn ); + Assignment propertyAssignment, List dependsOn, Assignment defaultValueAssignment ) { + this( name, null, targetWriteAccessorName, targetReadAccessorName, + targetType, propertyAssignment, dependsOn, defaultValueAssignment ); } private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, String targetReadAccessorName, Type targetType, Assignment assignment, - List dependsOn) { + List dependsOn, Assignment defaultValueAssignment ) { this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; @@ -724,6 +755,7 @@ public class PropertyMapping extends ModelElement { this.targetType = targetType; this.assignment = assignment; this.dependsOn = dependsOn != null ? dependsOn : Collections.emptyList(); + this.defaultValueAssignment = defaultValueAssignment; } /** @@ -753,6 +785,10 @@ public class PropertyMapping extends ModelElement { return assignment; } + public Assignment getDefaultValueAssignment() { + return defaultValueAssignment; + } + @Override public Set getImportTypes() { return assignment.getImportTypes(); @@ -762,6 +798,10 @@ public class PropertyMapping extends ModelElement { return dependsOn; } + public String getDefaultValue() { + return defaultValue; + } + @Override public String toString() { return "PropertyMapping {" @@ -770,6 +810,7 @@ public class PropertyMapping extends ModelElement { + "\n targetReadAccessorName='" + targetReadAccessorName + "\'," + "\n targetType=" + targetType + "," + "\n propertyAssignment=" + assignment + "," + + "\n defaultValueAssignment=" + defaultValueAssignment + "," + "\n dependsOn=" + dependsOn + "\n}"; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java index a3b8fbfcc..2f330c774 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java @@ -18,13 +18,12 @@ */ package org.mapstruct.ap.internal.model.source; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.MappingPrism; +import org.mapstruct.ap.internal.prism.MappingsPrism; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -33,13 +32,13 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.MappingPrism; -import org.mapstruct.ap.internal.prism.MappingsPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Represents a property mapping as configured via {@code @Mapping}. @@ -55,6 +54,7 @@ public class Mapping { private final String javaExpression; private final String targetName; private final String dateFormat; + private final String defaultValue; private final List qualifiers; private final TypeMirror resultType; private final boolean isIgnored; @@ -95,6 +95,7 @@ public class Mapping { public static Mapping fromMappingPrism(MappingPrism mappingPrism, ExecutableElement element, FormattingMessager messager) { + if ( mappingPrism.target().isEmpty() ) { messager.printMessage( element, @@ -117,11 +118,20 @@ public class Mapping { messager.printMessage( element, Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED ); return null; } + else if ( !mappingPrism.expression().isEmpty() && !mappingPrism.defaultValue().isEmpty() ) { + messager.printMessage( element, Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED ); + return null; + } + else if ( !mappingPrism.constant().isEmpty() && !mappingPrism.defaultValue().isEmpty() ) { + messager.printMessage( element, Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED ); + return null; + } String source = mappingPrism.source().isEmpty() ? null : mappingPrism.source(); String constant = mappingPrism.constant().isEmpty() ? null : mappingPrism.constant(); String expression = getExpression( mappingPrism, element, messager ); String dateFormat = mappingPrism.dateFormat().isEmpty() ? null : mappingPrism.dateFormat(); + String defaultValue = mappingPrism.defaultValue().isEmpty() ? null : mappingPrism.defaultValue(); boolean resultTypeIsDefined = !TypeKind.VOID.equals( mappingPrism.resultType().getKind() ); TypeMirror resultType = resultTypeIsDefined ? mappingPrism.resultType() : null; @@ -134,6 +144,7 @@ public class Mapping { expression, mappingPrism.target(), dateFormat, + defaultValue, mappingPrism.qualifiedBy(), mappingPrism.ignore(), mappingPrism.mirror, @@ -147,7 +158,7 @@ public class Mapping { @SuppressWarnings("checkstyle:parameternumber") private Mapping(String sourceName, String constant, String javaExpression, String targetName, - String dateFormat, List qualifiers, + String dateFormat, String defaultValue, List qualifiers, boolean isIgnored, AnnotationMirror mirror, AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, AnnotationValue dependsOnAnnotationValue, @@ -157,6 +168,7 @@ public class Mapping { this.javaExpression = javaExpression; this.targetName = targetName; this.dateFormat = dateFormat; + this.defaultValue = defaultValue; this.qualifiers = qualifiers; this.isIgnored = isIgnored; this.mirror = mirror; @@ -229,6 +241,10 @@ public class Mapping { return dateFormat; } + public String getDefaultValue() { + return defaultValue; + } + public List getQualifiers() { return qualifiers; } @@ -306,6 +322,7 @@ public class Mapping { null, // expression sourceName != null ? sourceName : targetName, dateFormat, + null, qualifiers, isIgnored, mirror, @@ -333,6 +350,7 @@ public class Mapping { javaExpression, targetName, dateFormat, + defaultValue, qualifiers, isIgnored, mirror, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 30e157511..77377ca67 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -44,6 +44,8 @@ public enum Message { PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED( "Source and constant are both defined in @Mapping, either define a source or a constant." ), PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED( "Source and expression are both defined in @Mapping, either define a source or an expression." ), PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED( "Expression and constant are both defined in @Mapping, either define an expression or a constant." ), + PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED( "Expression and default value are both defined in @Mapping, either define a defaultValue or an expression." ), + PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED( "Constant and default value are both defined in @Mapping, either define a defaultValue or a constant." ), PROPERTYMAPPING_INVALID_EXPRESSION( "Value must be given in the form \"java()\"." ), PROPERTYMAPPING_REVERSAL_PROBLEM( "Parameter %s cannot be reversed." ), PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no parameter named \"%s\"." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 5eca1030b..bc56e2e56 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -47,7 +47,7 @@ <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> if ( ${sourceParam.name} != null ) { <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> - <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> + <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> } @@ -55,14 +55,14 @@ <#list sourcePrimitiveParameters as sourceParam> <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> - <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> + <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> <#else> <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) { <#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping> - <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> + <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> <#if mapNullToDefault>} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index 9dd847339..9464a7dd5 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -23,4 +23,5 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName - targetType=targetType/> + targetType=targetType + defaultValueAssignment=defaultValueAssignment /> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl index ddb8c018f..73503c1cc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl @@ -20,9 +20,20 @@ --> if ( ${sourceReference} != null ) { <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetType=ext.targetType + defaultValue=ext.defaultValueAssignment/> +} +<#if ext.defaultValueAssignment?? > +else { + <@includeModel object=ext.defaultValueAssignment targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName targetType=ext.targetType/> } + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl index 79fbf8c0f..d39e1f573 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl @@ -19,10 +19,10 @@ --> <#if (thrownTypes?size == 0) > - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_assignment/> ); + <@assignment_w_defaultValue/> <#else> try { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_assignment/> ); + <@assignment_w_defaultValue/> } <#list thrownTypes as exceptionType> catch ( <@includeModel object=exceptionType/> e ) { @@ -30,11 +30,31 @@ } + <#macro _assignment> <@includeModel object=assignment targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetType=ext.targetType + defaultValueAssignment=ext.defaultValueAssignment/> + + +<#macro _defaultValueAssignment> + <@includeModel object=ext.defaultValueAssignment.assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetWriteAccessorName=ext.targetWriteAccessorName targetType=ext.targetType/> + + +<#macro assignment_w_defaultValue> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_assignment/> ); + <#-- if the assignee property is a primitive, defaulValueAssignment will not be set --> + <#if ext.defaultValueAssignment?? > + if ( ${sourceReference} == null ) { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_defaultValueAssignment/> ); + } + \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java new file mode 100644 index 000000000..7b7b6b1a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +public class CountryDts { + private String code; + private int id; + private long zipcode; + private String region; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public long getZipcode() { + return zipcode; + } + + public void setZipcode(long zipcode) { + this.zipcode = zipcode; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java new file mode 100644 index 000000000..90a9b7f3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java @@ -0,0 +1,59 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +public class CountryEntity { + private String code; + private Integer id; + private long zipcode; + private Region region; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public long getZipcode() { + return zipcode; + } + + public void setZipcode(long zipcode) { + this.zipcode = zipcode; + } + + public Region getRegion() { + return region; + } + + public void setRegion(Region region) { + this.region = region; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java new file mode 100644 index 000000000..bea860abe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java @@ -0,0 +1,60 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class CountryMapper { + public static final CountryMapper INSTANCE = Mappers.getMapper( CountryMapper.class ); + + @Mappings( { + @Mapping( target = "code", defaultValue = "DE" ), + @Mapping( target = "id", defaultValue = "42" ), + @Mapping( target = "zipcode", defaultValue = "1337" ), + @Mapping( target = "region", defaultValue = "someRegion" ), + } ) + public abstract CountryDts mapToCountryDts(CountryEntity country); + + @Mappings( { + @Mapping( target = "code", defaultValue = "DE" ), + @Mapping( target = "id", defaultValue = "42" ), + @Mapping( target = "zipcode", defaultValue = "1337" ), + @Mapping( target = "region", ignore = true ) + + } ) + public abstract void mapToCountryDts(CountryDts countryDts, @MappingTarget CountryEntity country); + + @Mappings( { + @Mapping( target = "code", defaultValue = "DE" ), + @Mapping( target = "id", defaultValue = "42" ), + @Mapping( target = "zipcode", defaultValue = "1337" ), + @Mapping( target = "region", ignore = true ) + + } ) + public abstract void mapToCountryDts(CountryEntity source, @MappingTarget CountryEntity target); + + protected String mapToString(Region region) { + return ( region != null ) ? region.getCode() : null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java new file mode 100644 index 000000000..abf16906b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java @@ -0,0 +1,162 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import java.text.ParseException; + +import static org.fest.assertions.Assertions.assertThat; + +@IssueKey( "600" ) +@RunWith( AnnotationProcessorTestRunner.class ) +@WithClasses( { + CountryEntity.class, + CountryDts.class +} ) +public class DefaultValueTest { + @Test + @WithClasses( { + Region.class, + CountryMapper.class + } ) + /** + * Checks: + *
    + *
  • On code: Using defaultValue without type conversion
  • + *
  • On id: Type conversion of the defaultValue (string expr to int)
  • + *
  • On name: Using ConstantExpression instead of defaultValue
  • + *
  • On zipcode: Ignoring defaultValue on primitive target types
  • + *
  • On region: Using defaultValue before the assignment by an intern method (mapToString)
  • + *
+ */ + public void shouldDefaultValueAndUseConstantExpression() { + CountryEntity countryEntity = new CountryEntity(); + + CountryDts countryDts = CountryMapper.INSTANCE.mapToCountryDts( countryEntity ); + + // id is null so it should fall back to the default value + assertThat( countryDts.getId() ).isEqualTo( 42 ); + + // code is null so it should fall back to the default value + assertThat( countryDts.getCode() ).isEqualTo( "DE" ); + + assertThat( countryDts.getZipcode() ).isEqualTo( 0 ); + + assertThat( countryDts.getRegion() ).isEqualTo( "someRegion" ); + } + + @Test + @WithClasses( { + Region.class, + CountryMapper.class + } ) + public void shouldIgnoreDefaultValue() { + CountryEntity countryEntity = new CountryEntity(); + countryEntity.setCode( "US" ); + Region region = new Region(); + region.setCode( "foobar" ); + countryEntity.setRegion( region ); + + CountryDts countryDts = CountryMapper.INSTANCE.mapToCountryDts( countryEntity ); + + // the source entity had a code set, so the default value shouldn't be used + assertThat( countryDts.getCode() ).isEqualTo( "US" ); + assertThat( countryDts.getRegion() ).isEqualTo( "foobar" ); + } + + @Test + @WithClasses( { + Region.class, + CountryMapper.class + } ) + public void shouldHandleUpdateMethodsFromDtsToEntity() { + CountryEntity countryEntity = new CountryEntity(); + CountryDts countryDts = new CountryDts(); + + CountryMapper.INSTANCE.mapToCountryDts( countryDts, countryEntity ); + + assertThat( countryEntity.getId() ).isEqualTo( 0 ); + // no code is set, so fall back to default value + assertThat( countryEntity.getCode() ).isEqualTo( "DE" ); + assertThat( countryEntity.getZipcode() ).isEqualTo( 0 ); + } + + @Test + @WithClasses( { + Region.class, + CountryMapper.class + } ) + public void shouldHandleUpdateMethodsFromEntityToEntity() { + CountryEntity source = new CountryEntity(); + CountryEntity target = new CountryEntity(); + + CountryMapper.INSTANCE.mapToCountryDts( source, target ); + + // no id is set, so fall back to default value + assertThat( target.getId() ).isEqualTo( 42 ); + // no code is set, so fall back to default value + assertThat( target.getCode() ).isEqualTo( "DE" ); + assertThat( target.getZipcode() ).isEqualTo( 0 ); + } + + @Test + @WithClasses( { + ErroneousMapper.class, + Region.class, + } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 33, + messageRegExp = "Constant and default value are both defined in @Mapping," + + " either define a defaultValue or a constant." ), + } + ) + public void errorOnDefaultValueAndConstant() throws ParseException { + } + + @Test + @WithClasses( { + ErroneousMapper2.class, + Region.class, + } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( type = ErroneousMapper2.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 33, + messageRegExp = "Expression and default value are both defined in @Mapping," + + " either define a defaultValue or an expression." ), + } + ) + public void errorOnDefaultValueAndExpression() throws ParseException { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper.java new file mode 100644 index 000000000..e7aca836c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class ErroneousMapper { + public static final ErroneousMapper INSTANCE = Mappers.getMapper( ErroneousMapper.class ); + + @Mappings( { + @Mapping( target = "code", defaultValue = "DE", constant = "FOOBAR" ), + } ) + public abstract CountryDts mapToCountryDts(CountryEntity country); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper2.java new file mode 100644 index 000000000..0d21466fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMapper2.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class ErroneousMapper2 { + public static final ErroneousMapper2 INSTANCE = Mappers.getMapper( ErroneousMapper2.class ); + + @Mappings( { + @Mapping( target = "code", defaultValue = "DE", expression = "java(;)" ), + } ) + public abstract CountryDts mapToCountryDts(CountryEntity country); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/Region.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/Region.java new file mode 100644 index 000000000..b10e18524 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/Region.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012-2015 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.test.defaultvalue; + +public class Region { + private String code; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +}