diff --git a/processor/src/main/java/org/mapstruct/ap/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/model/GeneratedType.java index 20fea924e..42e852d16 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/model/GeneratedType.java @@ -155,7 +155,13 @@ public abstract class GeneratedType extends ModelElement { typeToAdd.getPackageName() != null && !typeToAdd.getPackageName().equals( packageName ) && !typeToAdd.getPackageName().startsWith( "java.lang" ) ) { - collection.add( typeToAdd ); + + if ( typeToAdd.isArrayType() ) { + collection.add( typeToAdd.getComponentType() ); + } + else { + collection.add( typeToAdd ); + } } addWithDependents( collection, typeToAdd.getImplementationType() ); 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 aa81ff20d..28c72c66a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -20,10 +20,12 @@ package org.mapstruct.ap.model; import java.util.List; import java.util.Set; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import org.mapstruct.ap.model.assignment.Assignment; +import org.mapstruct.ap.model.assignment.LocalVarWrapper; import org.mapstruct.ap.model.assignment.SetterWrapper; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; @@ -74,13 +76,21 @@ public class IterableMappingMethod extends MappingMethod { } public IterableMappingMethod build() { - Type sourceElementType = - method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 ); - Type targetElementType = - method.getResultType().getTypeParameters().get( 0 ); - String loopVariableName = - Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() ); + Type sourceParameterType = method.getSourceParameters().iterator().next().getType(); + Type resultType = method.getResultType(); + Type sourceElementType = sourceParameterType.isArrayType() ? sourceParameterType.getComponentType() : + sourceParameterType.getTypeParameters().get( 0 ); + Type targetElementType = resultType.isArrayType() ? resultType.getComponentType() : + resultType.getTypeParameters().get( 0 ); + + + String elementTypeName = sourceParameterType.isArrayType() ? + sourceParameterType.getComponentType().getName() : + sourceParameterType.getTypeParameters().get( 0 ).getName(); + + String loopVariableName = + Strings.getSaveVariableName( elementTypeName, method.getParameterNames() ); Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, @@ -103,21 +113,25 @@ public class IterableMappingMethod extends MappingMethod { } // target accessor is setter, so decorate assignment as setter - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - + if ( resultType.isArrayType() ) { + assignment = new LocalVarWrapper( assignment, method.getThrownTypes() ); + } + else { + assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + } // mapNullToDefault NullValueMappingPrism prism = NullValueMappingPrism.getInstanceOn( method.getExecutable() ); boolean mapNullToDefault = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ).isMapToDefault( prism ); MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); + return new IterableMappingMethod( - method, - assignment, - factoryMethod, - mapNullToDefault, - loopVariableName - ); + method, + assignment, + factoryMethod, + mapNullToDefault, + loopVariableName ); } } @@ -149,7 +163,6 @@ public class IterableMappingMethod extends MappingMethod { @Override public Set getImportTypes() { Set types = super.getImportTypes(); - if ( elementAssignment != null ) { types.addAll( elementAssignment.getImportTypes() ); } @@ -169,10 +182,59 @@ public class IterableMappingMethod extends MappingMethod { return loopVariableName; } + public String getDefaultValue() { + TypeKind kind = getResultElementType().getTypeMirror().getKind(); + switch ( kind ) { + case BOOLEAN: + return "false"; + case BYTE: + case SHORT: + case INT: + case CHAR: /*"'\u0000'" would have been better, but depends on platformencoding */ + return "0"; + case LONG: + return "0L"; + case FLOAT: + return "0.0f"; + case DOUBLE: + return "0.0d"; + default: + return "null"; + } + } + public MethodReference getFactoryMethod() { return this.factoryMethod; } + public Type getSourceElementType() { + Type sourceParameterType = getSourceParameter().getType(); + + if ( sourceParameterType.isArrayType() ) { + return sourceParameterType.getComponentType(); + } + else { + return sourceParameterType.getTypeParameters().get( 0 ); + } + } + + public Type getResultElementType() { + if ( getResultType().isArrayType() ) { + return getResultType().getComponentType(); + } + else { + return getResultType().getTypeParameters().get( 0 ); + } + } + + public String getIndex1Name() { + return Strings.getSaveVariableName( "i", loopVariableName, getSourceParameter().getName(), getResultName() ); + } + + public String getIndex2Name() { + return Strings.getSaveVariableName( "j", loopVariableName, getSourceParameter().getName(), getResultName() ); + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java index ab3b4ab6a..e09160f31 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java @@ -28,7 +28,8 @@ 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.Method; -import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Strings.getSaveVariableName; +import static org.mapstruct.ap.util.Strings.join; /** * A method implemented or referenced by a {@link Mapper} class. @@ -78,8 +79,15 @@ public abstract class MappingMethod extends ModelElement { } public String getResultName() { - return targetParameter != null ? targetParameter.getName() : - Strings.getSaveVariableName( getResultType().getName(), getParameterNames() ); + if ( targetParameter != null ) { + return targetParameter.getName(); + } + else if ( getResultType().isArrayType() ) { + return getSaveVariableName( getResultType().getComponentType().getName() + "Tmp", getParameterNames() ); + } + else { + return getSaveVariableName( getResultType().getName(), getParameterNames() ); + } } public Type getReturnType() { @@ -124,6 +132,6 @@ public abstract class MappingMethod extends ModelElement { @Override public String toString() { - return returnType + " " + getName() + "(" + Strings.join( parameters, ", " ) + ")"; + return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")"; } } 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 5c94375ce..a6ca797bf 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java @@ -18,6 +18,7 @@ */ package org.mapstruct.ap.model; +import java.util.Arrays; import java.util.List; import java.util.Set; import javax.lang.model.element.ExecutableElement; @@ -25,6 +26,7 @@ import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import org.mapstruct.ap.model.assignment.AdderWrapper; +import org.mapstruct.ap.model.assignment.ArrayCopyWrapper; import org.mapstruct.ap.model.assignment.Assignment; import org.mapstruct.ap.model.assignment.GetterCollectionOrMapWrapper; import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper; @@ -153,6 +155,11 @@ public class PropertyMapping extends ModelElement { if ( targetType.isCollectionOrMapType() ) { assignment = assignCollection( targetType, targetAccessorType, assignment ); } + else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.getType() == DIRECT ) { + Type arrayType = ctx.getTypeFactory().getType( Arrays.class ); + assignment = new ArrayCopyWrapper( assignment, targetPropertyName, arrayType, targetType ); + assignment = new NullCheckWrapper( assignment ); + } else { assignment = assignObject( sourceType, targetType, targetAccessorType, assignment ); } @@ -367,7 +374,8 @@ public class PropertyMapping extends ModelElement { ExecutableElement element) { Assignment assignment = null; - if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { + if ( ( sourceType.isCollectionType() || sourceType.isArrayType() ) + && ( targetType.isCollectionType() || targetType.isArrayType() ) ) { ForgedMethod methodToGenerate = new ForgedMethod( sourceType, targetType, element ); IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); diff --git a/processor/src/main/java/org/mapstruct/ap/model/assignment/ArrayCopyWrapper.java b/processor/src/main/java/org/mapstruct/ap/model/assignment/ArrayCopyWrapper.java new file mode 100644 index 000000000..afeaf0adf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/assignment/ArrayCopyWrapper.java @@ -0,0 +1,60 @@ +/** + * 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.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.model.common.Type; +import static org.mapstruct.ap.util.Strings.decapitalize; +import static org.mapstruct.ap.util.Strings.getSaveVariableName; + +/** + * Decorates the assignment as a Map or Collection constructor + * + * @author Sjaak Derksen + */ +public class ArrayCopyWrapper extends AssignmentWrapper { + + private final String targetPropertyName; + private final Type arraysType; + private final Type targetType; + + public ArrayCopyWrapper(Assignment decoratedAssignment, String targetPropertyName, Type arraysType, + Type targetType) { + super( decoratedAssignment ); + this.targetPropertyName = targetPropertyName; + this.arraysType = arraysType; + this.targetType = targetType; + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet(); + imported.addAll( getAssignment().getImportTypes() ); + imported.add( arraysType ); + imported.add( targetType ); + return imported; + } + + public String getLocalVarName() { + return getSaveVariableName( decapitalize( targetPropertyName ), Collections.emptyList() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java index a3b813340..e1d6132b2 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java @@ -62,6 +62,7 @@ public class Type extends ModelElement implements Comparable { private final List typeParameters; private final Type implementationType; + private final Type componentType; private final String packageName; private final String name; @@ -85,7 +86,7 @@ public class Type extends ModelElement implements Comparable { //CHECKSTYLE:OFF public Type(Types typeUtils, Elements elementUtils, TypeMirror typeMirror, TypeElement typeElement, - List typeParameters, Type implementationType, String packageName, String name, + List typeParameters, Type implementationType, Type componentType, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, boolean isCollectionType, boolean isMapType, boolean isImported) { @@ -95,6 +96,7 @@ public class Type extends ModelElement implements Comparable { this.typeMirror = typeMirror; this.typeElement = typeElement; this.typeParameters = typeParameters; + this.componentType = componentType; this.implementationType = implementationType; this.packageName = packageName; @@ -147,6 +149,10 @@ public class Type extends ModelElement implements Comparable { return typeParameters; } + public Type getComponentType() { + return componentType; + } + public boolean isPrimitive() { return typeMirror.getKind().isPrimitive(); } @@ -182,8 +188,13 @@ public class Type extends ModelElement implements Comparable { return implementationType; } + /** + * Whether this type is a sub-type of {@link Iterable} or an array type. + * + * @return {@code true} if this type is a sub-type of {@link Iterable} or an array type, {@code false} otherwise. + */ public boolean isIterableType() { - return isIterableType; + return isIterableType || isArrayType(); } public boolean isCollectionType() { @@ -198,13 +209,32 @@ public class Type extends ModelElement implements Comparable { return isCollectionType || isMapType; } + public boolean isArrayType() { + return componentType != null; + } + public String getFullyQualifiedName() { return qualifiedName; } + /** + * The name of this type as to be used within import statements. + */ + public String getImportName() { + return isArrayType() ? qualifiedName.substring( 0, qualifiedName.length() - 2 ) : qualifiedName; + } + @Override public Set getImportTypes() { - return implementationType != null ? Collections.singleton( implementationType ) : Collections.emptySet(); + if ( implementationType != null ) { + return Collections.singleton( implementationType ); + } + else if ( componentType != null ) { + return Collections.singleton( componentType ); + } + else { + return Collections.emptySet(); + } } /** @@ -244,6 +274,7 @@ public class Type extends ModelElement implements Comparable { typeElement, typeParameters, implementationType, + componentType, packageName, name, qualifiedName, diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java index d1405ad83..3b8046760 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java @@ -43,6 +43,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.PrimitiveType; @@ -134,12 +135,14 @@ public class TypeFactory { boolean isInterface; String name; String packageName; - TypeElement typeElement; String qualifiedName; + TypeElement typeElement; + Type componentType; - DeclaredType declaredType = mirror.getKind() == TypeKind.DECLARED ? (DeclaredType) mirror : null; - if ( declaredType != null ) { + if ( mirror.getKind() == TypeKind.DECLARED ) { + DeclaredType declaredType = (DeclaredType) mirror; + isEnumType = declaredType.asElement().getKind() == ElementKind.ENUM; isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE; name = declaredType.asElement().getSimpleName().toString(); @@ -154,14 +157,40 @@ public class TypeFactory { packageName = null; qualifiedName = name; } + + componentType = null; + } + else if ( mirror.getKind() == TypeKind.ARRAY ) { + TypeMirror componentTypeMirror = getComponentType( mirror ); + + if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) { + DeclaredType declaredType = (DeclaredType) componentTypeMirror; + TypeElement componentTypeElement = + declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); + + name = componentTypeElement.getSimpleName().toString() + "[]"; + packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); + qualifiedName = componentTypeElement.getQualifiedName().toString() + "[]"; + } + else { + name = mirror.toString(); + packageName = null; + qualifiedName = name; + } + + isEnumType = false; + isInterface = false; + typeElement = null; + componentType = getType( componentTypeMirror ); } else { isEnumType = false; isInterface = false; - typeElement = null; name = mirror.toString(); packageName = null; qualifiedName = name; + typeElement = null; + componentType = null; } return new Type( @@ -170,6 +199,7 @@ public class TypeFactory { typeElement, getTypeParameters( mirror ), implementationType, + componentType, packageName, name, qualifiedName, @@ -354,6 +384,7 @@ public class TypeFactory { implementationType.getTypeElement(), getTypeParameters( mirror ), null, + null, implementationType.getPackageName(), implementationType.getName(), implementationType.getFullyQualifiedName(), @@ -369,6 +400,15 @@ public class TypeFactory { return null; } + private TypeMirror getComponentType(TypeMirror mirror) { + if ( mirror.getKind() != TypeKind.ARRAY ) { + return null; + } + + ArrayType arrayType = (ArrayType) mirror; + return arrayType.getComponentType(); + } + private boolean isImported(String name, String qualifiedName) { String importedType = importedQualifiedTypesBySimpleName.get( name ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java index 3729a4ae2..355012f0d 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java @@ -51,7 +51,7 @@ public class ForgedMethod implements Method { * @param positionHintElement element used to for reference to the position in the source file. */ public ForgedMethod(Type sourceType, Type targetType, ExecutableElement positionHintElement) { - String sourceParamName = Strings.decapitalize( sourceType.getName() ); + String sourceParamName = Strings.decapitalize( sourceType.getName().replace( "[]", "" ) ); String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); this.parameters = Arrays.asList( new Parameter( sourceParamSafeName, sourceType ) ); this.returnType = targetType; @@ -72,7 +72,7 @@ public class ForgedMethod implements Method { * @param positionHintElement element used to for reference to the position in the source file. */ public ForgedMethod(String name, Type sourceType, Type targetType, ExecutableElement positionHintElement) { - String sourceParamName = Strings.decapitalize( sourceType.getName() ); + String sourceParamName = Strings.decapitalize( sourceType.getName().replace( "[]", "" ) ); String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); this.parameters = Arrays.asList( new Parameter( sourceParamSafeName, sourceType ) ); this.returnType = targetType; @@ -83,9 +83,10 @@ public class ForgedMethod implements Method { private String getName(Type type) { StringBuilder builder = new StringBuilder(); for ( Type typeParam : type.getTypeParameters() ) { - builder.append( typeParam.getName() ); + + builder.append( typeParam.getName().replace( "[]", "Array" ) ); } - builder.append( type.getName() ); + builder.append( type.getName().replace( "[]", "Array" ) ); return builder.toString(); } diff --git a/processor/src/main/resources/org.mapstruct.ap.model.GeneratedType.ftl b/processor/src/main/resources/org.mapstruct.ap.model.GeneratedType.ftl index f814d52a8..1c23da94b 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.GeneratedType.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.GeneratedType.ftl @@ -21,7 +21,7 @@ package ${packageName}; <#list importTypes as importedType> -import ${importedType.fullyQualifiedName}; +import ${importedType.importName}; @Generated( diff --git a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl index 798a3ba94..071f58783 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl @@ -22,30 +22,59 @@ <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) <@throws/> { if ( ${sourceParameter.name} == null ) { <#if !mapNullToDefault> + <#-- returned target type starts to miss-align here with target handed via param, TODO is this right? --> return<#if returnType.name != "void"> null; <#else> - <#if existingInstanceMapping> - ${resultName}.clear(); - return<#if returnType.name != "void"> ${resultName} ; + <#if resultType.arrayType> + <#if existingInstanceMapping> + <#-- we can't clear an existing array, so we've got to clear by setting values to default --> + for (int ${index2Name} = 0; ${index2Name} < ${resultName}.length; ${index2Name}++ ) { + ${resultName}[${index2Name}] = ${defaultValue}; + } + return<#if returnType.name != "void"> ${resultName}; + <#else> + return new <@includeModel object=resultElementType/>[]; + <#else> - return <@returnObjectCreation/>; + <#if existingInstanceMapping> + ${resultName}.clear(); + return<#if returnType.name != "void"> ${resultName}; + <#else> + return <@iterableCreation/>; + } - <#if existingInstanceMapping> - ${resultName}.clear(); + <#if resultType.arrayType> + <#if !existingInstanceMapping> + <@includeModel object=resultElementType/>[] ${resultName} = new <@includeModel object=resultElementType/>[ <@iterableSize/> ]; + + int ${index1Name} = 0; + for ( <@includeModel object=sourceElementType/> ${loopVariableName} : ${sourceParameter.name} ) { + <#if existingInstanceMapping> + if ( ( ${index1Name} >= ${resultName}.length ) || ( ${index1Name} >= <@iterableSize/> ) ) { + break; + } + + <@includeModel object=elementAssignment targetAccessorName=resultName+"[${index1Name}]" targetType=resultElementType isTargetDefined=true/> + ${index1Name}++; + } <#else> - <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> - <@localVarDefinition/> = <@returnObjectCreation/>; - + <#if existingInstanceMapping> + ${resultName}.clear(); + <#else> + <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> + <@iterableLocalVarDef/> ${resultName} = <@iterableCreation/>; + + + for ( <@includeModel object=sourceElementType/> ${loopVariableName} : ${sourceParameter.name} ) { + <@includeModel object=elementAssignment targetBeanName=resultName targetAccessorName="add" targetType=resultElementType/> + } + - for ( <@includeModel object=sourceParameter.type.typeParameters[0]/> ${loopVariableName} : ${sourceParameter.name} ) { - <@includeModel object=elementAssignment targetBeanName=resultName targetAccessorName="add" targetType=resultType.typeParameters[0]/> - } <#if returnType.name != "void"> - - return ${resultName}; + return ${resultName}; } <#macro throws> @@ -57,16 +86,25 @@ -<#macro localVarDefinition> +<#macro iterableSize> + <@compress single_line=true> + <#if sourceParameter.type.arrayType> + ${sourceParameter.name}.length + <#else> + ${sourceParameter.name}.size() + + + +<#macro iterableLocalVarDef> <@compress single_line=true> <#if resultType.fullyQualifiedName == "java.lang.Iterable"> <@includeModel object=resultType.implementationType/> <#else> <@includeModel object=resultType/> - ${resultName} + -<#macro returnObjectCreation> +<#macro iterableCreation> <@compress single_line=true> <#if factoryMethod??> <@includeModel object=factoryMethod/> @@ -79,4 +117,4 @@ () - \ No newline at end of file + diff --git a/processor/src/main/resources/org.mapstruct.ap.model.assignment.ArrayCopyWrapper.ftl b/processor/src/main/resources/org.mapstruct.ap.model.assignment.ArrayCopyWrapper.ftl new file mode 100644 index 000000000..887b9d9a1 --- /dev/null +++ b/processor/src/main/resources/org.mapstruct.ap.model.assignment.ArrayCopyWrapper.ftl @@ -0,0 +1,37 @@ +<#-- + + 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. + +--> +<#if (exceptionTypes?size == 0) > + <@includeModel object=ext.targetType/> ${localVarName} = <@_assignment/>; + ${ext.targetBeanName}.${ext.targetAccessorName}( Arrays.copyOf( ${localVarName}, ${localVarName}.length ) ); +<#else> + try { + <@includeModel object=ext.targetType/> ${localVarName} = <@_assignment/>; + ${ext.targetBeanName}.${ext.targetAccessorName}( Arrays.copyOf( ${localVarName}, ${localVarName}.length ) ); + } + <#list exceptionTypes as exceptionType> + catch ( <@includeModel object=exceptionType/> e ) { + throw new RuntimeException( e ); + } + + +<#macro _assignment> + <@includeModel object=assignment raw=ext.raw targetType=ext.targetType/> + \ No newline at end of file diff --git a/processor/src/main/resources/org.mapstruct.ap.model.assignment.LocalVarWrapper.ftl b/processor/src/main/resources/org.mapstruct.ap.model.assignment.LocalVarWrapper.ftl index cf48d2419..5b862b949 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.assignment.LocalVarWrapper.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.assignment.LocalVarWrapper.ftl @@ -19,9 +19,9 @@ --> <#if (exceptionTypes?size == 0) > - ${ext.targetType.name} ${ext.targetAccessorName} = <@includeModel object=assignment targetType=ext.targetType raw=ext.raw/>; + <#if !ext.isTargetDefined?? >${ext.targetType.name} ${ext.targetAccessorName} = <@includeModel object=assignment targetType=ext.targetType raw=ext.raw/>; <#else> - ${ext.targetType.name} ${ext.targetAccessorName}; + <#if !ext.isTargetDefined?? >${ext.targetType.name} ${ext.targetAccessorName}; try { ${ext.targetAccessorName} = <@includeModel object=assignment targetType=ext.targetType raw=ext.raw/>; } @@ -30,4 +30,4 @@ throw new RuntimeException( e ); } - \ No newline at end of file + diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java new file mode 100644 index 000000000..1ac87b342 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java @@ -0,0 +1,223 @@ +/** + * 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.test.array; + +import static org.fest.assertions.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.array.source.Scientist; +import org.mapstruct.ap.test.array.target.ScientistDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +@WithClasses( { Scientist.class, ScientistDto.class, ScienceMapper.class } ) +@RunWith(AnnotationProcessorTestRunner.class) +@IssueKey("108") +public class ArrayMappingTest { + + @Test + public void shouldCopyArraysInBean() { + + Scientist source = new Scientist("Bob"); + source.setPublications( new String[]{ "the Lancet", "Nature" } ); + + ScientistDto dto = ScienceMapper.INSTANCE.scientistToDto( source ); + + assertThat( dto ).isNotNull(); + assertThat( dto ).isNotEqualTo( source ); + assertThat( dto.getPublications() ).containsOnly( "the Lancet", "Nature"); + } + + @Test + public void shouldForgeMappingForIntToString() { + + Scientist source = new Scientist("Bob"); + source.setPublicationYears( new String[]{"1993", "1997"} ); + + ScientistDto dto = ScienceMapper.INSTANCE.scientistToDto( source ); + + assertThat( dto ).isNotNull(); + assertThat( dto.getPublicationYears() ).containsOnly( 1993, 1997 ); + } + + + @Test + public void shouldMapArrayToArray() { + ScientistDto[] dtos = ScienceMapper.INSTANCE + .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + } + + @Test + public void shouldMapListToArray() { + ScientistDto[] dtos = ScienceMapper.INSTANCE + .scientistsToDtos( Arrays.asList( new Scientist( "Bob" ), new Scientist( "Larry" ) ) ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + } + + @Test + public void shouldMapArrayToList() { + List dtos = ScienceMapper.INSTANCE + .scientistsToDtosAsList( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + } + + @Test + public void shouldMapArrayToArrayExistingSmallerSizedTarget() { + + ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ) }; + + ScientistDto[] target = ScienceMapper.INSTANCE + .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) }, existingTarget ); + + assertThat( target ).isNotNull(); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( target ).onProperty( "name" ).containsOnly( "Bob" ); + } + + @Test + public void shouldMapArrayToArrayExistingEqualSizedTarget() { + + ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ), new ScientistDto( "Bart" ) }; + + ScientistDto[] target = ScienceMapper.INSTANCE + .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) }, existingTarget ); + + assertThat( target ).isNotNull(); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( target ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + } + + @Test + public void shouldMapArrayToArrayExistingLargerSizedTarget() { + + ScientistDto[] existingTarget = + new ScientistDto[]{ new ScientistDto( "Jim" ), new ScientistDto( "Bart" ), new ScientistDto( "John" ) }; + + ScientistDto[] target = ScienceMapper.INSTANCE + .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) }, existingTarget ); + + assertThat( target ).isNotNull(); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( target ).onProperty( "name" ).containsOnly( "Bob", "Larry", "John" ); + } + + @Test + public void shouldMapTargetToNullWhenNullSource() { + // TODO: What about existing target? + + ScientistDto[] existingTarget = + new ScientistDto[]{ new ScientistDto( "Jim" ) }; + + ScientistDto[] target = ScienceMapper.INSTANCE.scientistsToDtos( null, existingTarget ); + + assertThat( target ).isNull(); + assertThat( existingTarget ).onProperty( "name" ).containsOnly( "Jim" ); + } + + @Test + public void shouldMapbooleanWhenReturnDefault() { + + boolean[] existingTarget = new boolean[]{true}; + boolean[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( false ); + assertThat( existingTarget ).containsOnly( false ); + } + + @Test + public void shouldMapshortWhenReturnDefault() { + short[] existingTarget = new short[]{ 5 }; + short[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new short[] { 0 } ); + assertThat( existingTarget ).containsOnly( new short[] { 0 } ); + } + + @Test + public void shouldMapcharWhenReturnDefault() { + char[] existingTarget = new char[]{ 'a' }; + char[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new char[] { 0 } ); + assertThat( existingTarget ).containsOnly( new char[] { 0 } ); + } + + @Test + public void shouldMapintWhenReturnDefault() { + int[] existingTarget = new int[]{ 5 }; + int[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new int[] { 0 } ); + assertThat( existingTarget ).containsOnly( new int[] { 0 } ); + } + + @Test + public void shouldMaplongWhenReturnDefault() { + long[] existingTarget = new long[]{ 5L }; + long[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new long[] { 0L } ); + assertThat( existingTarget ).containsOnly( new long[] { 0L } ); + } + + @Test + public void shouldMapfloatWhenReturnDefault() { + float[] existingTarget = new float[]{ 3.1f }; + float[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new float[] { 0.0f } ); + assertThat( existingTarget ).containsOnly( new float[] { 0.0f } ); + } + + @Test + public void shouldMapdoubleWhenReturnDefault() { + double[] existingTarget = new double[]{ 5.0d }; + double[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); + + assertThat( target ).containsOnly( new double[] { 0.0d } ); + assertThat( existingTarget ).containsOnly( new double[] { 0.0d } ); + } + + @Test + public void shouldVoidMapintWhenReturnNull() { + long[] existingTarget = new long[]{ 5L }; + ScienceMapper.INSTANCE.nvmMappingVoidReturnNull( null, existingTarget ); + assertThat( existingTarget ).containsOnly( new long[] { 5L } ); + } + + @Test + public void shouldVoidMapintWhenReturnDefault() { + long[] existingTarget = new long[]{ 5L }; + ScienceMapper.INSTANCE.nvmMappingVoidReturnDefault( null, existingTarget ); + assertThat( existingTarget ).containsOnly( new long[] { 0L } ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java new file mode 100644 index 000000000..4137406b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java @@ -0,0 +1,76 @@ +/** + * 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.test.array; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueMapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.array.source.Scientist; +import org.mapstruct.ap.test.array.target.ScientistDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ScienceMapper { + + ScienceMapper INSTANCE = Mappers.getMapper( ScienceMapper.class ); + + ScientistDto scientistToDto(Scientist scientist); + + ScientistDto[] scientistsToDtos(Scientist[] scientists); + + ScientistDto[] scientistsToDtos(List scientists); + + List scientistsToDtosAsList(Scientist[] scientists); + + ScientistDto[] scientistsToDtos(Scientist[] scientists, @MappingTarget ScientistDto[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + boolean[] nvmMapping(boolean[] source, @MappingTarget boolean[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + short[] nvmMapping(int[] source, @MappingTarget short[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + char[] nvmMapping(String[] source, @MappingTarget char[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + int[] nvmMapping(int[] source, @MappingTarget int[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + long[] nvmMapping(int[] source, @MappingTarget long[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + float[] nvmMapping(int[] source, @MappingTarget float[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + double[] nvmMapping(int[] source, @MappingTarget double[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + String[] nvmMapping(int[] source, @MappingTarget String[] target); + + void nvmMappingVoidReturnNull(int[] source, @MappingTarget long[] target); + + @NullValueMapping( NullValueMappingStrategy.RETURN_DEFAULT ) + void nvmMappingVoidReturnDefault(int[] source, @MappingTarget long[] target); + + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java b/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java new file mode 100644 index 000000000..4b911ecd6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java @@ -0,0 +1,56 @@ +/** + * 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.test.array.source; + +public class Scientist { + + private String name; + private String[] publications; + private String[] publicationYears; + + + public Scientist(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getPublications() { + return publications; + } + + public void setPublications(String[] publications) { + this.publications = publications; + } + + public String[] getPublicationYears() { + return publicationYears; + } + + public void setPublicationYears(String[] publicationYears) { + this.publicationYears = publicationYears; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/target/ScientistDto.java b/processor/src/test/java/org/mapstruct/ap/test/array/target/ScientistDto.java new file mode 100644 index 000000000..909325e9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/target/ScientistDto.java @@ -0,0 +1,40 @@ +package org.mapstruct.ap.test.array.target; + +public class ScientistDto { + + private String name; + private String[] publications; + private int[] publicationYears; + + public ScientistDto() { + } + + public ScientistDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getPublications() { + return publications; + } + + public void setPublications(String[] publications) { + this.publications = publications; + } + + public int[] getPublicationYears() { + return publicationYears; + } + + public void setPublicationYears(int[] publicationYears) { + this.publicationYears = publicationYears; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 492ae2450..d982606c3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -92,6 +92,20 @@ public class DateConversionTest { assertThat( stringDates ).containsExactly( "06.07.2013", "14.02.2013", "11.04.2013" ); } + @Test + public void shouldApplyStringConversionForArrayMethod() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + ); + + String[] stringDates = SourceTargetMapper.INSTANCE.stringListToDateArray( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); + } + @Test public void shouldApplyStringConversionForReverseIterableMethod() { List stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" ); @@ -105,4 +119,46 @@ public class DateConversionTest { new GregorianCalendar( 2013, 3, 11 ).getTime() ); } + + @Test + public void shouldApplyStringConversionForReverseArrayMethod() { + String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; + + List dates = SourceTargetMapper.INSTANCE.stringArrayToDateList( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + ); + } + + + @Test + public void shouldApplyStringConversionForReverseArrayArrayMethod() { + Date[] dates = new Date[]{ + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + }; + String[] stringDates = SourceTargetMapper.INSTANCE.dateArrayToStringArray( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); + } + + @Test + public void shouldApplyDateConversionForReverseArrayArrayMethod() { + + String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; + Date[] dates = SourceTargetMapper.INSTANCE.stringArrayToDateArray( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).isEqualTo( new Date[] { + new GregorianCalendar( 2013, 6, 6 ).getTime(), + new GregorianCalendar( 2013, 1, 14 ).getTime(), + new GregorianCalendar( 2013, 3, 11 ).getTime() + } ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java index d71d29856..c3d2eb13f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java @@ -41,6 +41,18 @@ public interface SourceTargetMapper { @IterableMapping(dateFormat = "dd.MM.yyyy") List stringListToDateList(List dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") + String[] stringListToDateArray(List dates); + @InheritInverseConfiguration List dateListToStringList(List strings); + + @InheritInverseConfiguration + List stringArrayToDateList(String[] dates); + + @IterableMapping(dateFormat = "dd.MM.yyyy") + String[] dateArrayToStringArray(Date[] dates); + + @InheritInverseConfiguration + Date[] stringArrayToDateArray(String[] dates); }