From 0192bdaf836212108edbb97a5e55ce0ca1b61dd1 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 30 Jul 2017 20:53:45 +0200 Subject: [PATCH] #571 Add Constructor Injection for Annotation Based Component Model - Allow configuring of injection strategy (Constructor / Field) - Default injection strategy is Field --- .../java/org/mapstruct/InjectionStrategy.java | 34 +++++ .../src/main/java/org/mapstruct/Mapper.java | 10 ++ .../main/java/org/mapstruct/MapperConfig.java | 12 ++ .../internal/model/AnnotatedConstructor.java | 79 ++++++++++ .../model/AnnotationMapperReference.java | 32 +++- .../ap/internal/model/Constructor.java | 8 +- .../ap/internal/model/GeneratedType.java | 21 ++- .../ap/internal/model/PropertyMapping.java | 10 +- .../prism/InjectionStrategyPrism.java | 30 ++++ ...nnotationBasedComponentModelProcessor.java | 142 ++++++++++++++++-- .../processor/CdiComponentProcessor.java | 5 + .../ap/internal/util/MapperConfiguration.java | 10 ++ .../internal/model/AnnotatedConstructor.ftl | 35 +++++ .../model/AnnotationMapperReference.ftl | 10 +- .../spring/constructor/PersonMapper.java | 38 +++++ .../constructor/PersonMapperDecorator.java | 39 +++++ .../constructor/SpringDecoratorTest.java | 107 +++++++++++++ .../spring/{ => field}/PersonMapper.java | 5 +- .../{ => field}/PersonMapperDecorator.java | 2 +- .../{ => field}/SpringDecoratorTest.java | 2 +- .../constructor/ConstructorJsr330Config.java | 29 ++++ .../CustomerJsr330ConstructorMapper.java | 38 +++++ .../GenderJsr330ConstructorMapper.java | 38 +++++ .../Jsr330ConstructorMapperTest.java | 107 +++++++++++++ .../field/CustomerJsr330FieldMapper.java | 33 ++++ .../jsr330/field/FieldJsr330Config.java | 29 ++++ .../jsr330/field/GenderJsr330FieldMapper.java | 38 +++++ .../jsr330/field/Jsr330FieldMapperTest.java | 109 ++++++++++++++ .../injectionstrategy/shared/CustomerDto.java | 45 ++++++ .../shared/CustomerEntity.java | 45 ++++++ .../test/injectionstrategy/shared/Gender.java | 28 ++++ .../injectionstrategy/shared/GenderDto.java | 28 ++++ .../constructor/ConstructorSpringConfig.java | 29 ++++ .../CustomerSpringConstructorMapper.java | 37 +++++ .../GenderSpringConstructorMapper.java | 38 +++++ .../SpringConstructorMapperTest.java | 107 +++++++++++++ .../field/CustomerSpringFieldMapper.java | 33 ++++ .../spring/field/FieldSpringConfig.java | 29 ++++ .../spring/field/GenderSpringFieldMapper.java | 38 +++++ .../spring/field/SpringFieldMapperTest.java | 106 +++++++++++++ .../ap/test/prism/EnumPrismsTest.java | 8 + 41 files changed, 1584 insertions(+), 39 deletions(-) create mode 100644 core-common/src/main/java/org/mapstruct/InjectionStrategy.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java rename processor/src/test/java/org/mapstruct/ap/test/decorator/spring/{ => field}/PersonMapper.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/decorator/spring/{ => field}/PersonMapperDecorator.java (96%) rename processor/src/test/java/org/mapstruct/ap/test/decorator/spring/{ => field}/SpringDecoratorTest.java (98%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/GenderJsr330ConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/GenderJsr330FieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/Gender.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/GenderDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/GenderSpringConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/GenderSpringFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java diff --git a/core-common/src/main/java/org/mapstruct/InjectionStrategy.java b/core-common/src/main/java/org/mapstruct/InjectionStrategy.java new file mode 100644 index 000000000..75728d139 --- /dev/null +++ b/core-common/src/main/java/org/mapstruct/InjectionStrategy.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012-2017 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; + +/** + * Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and + * JSR330. + * + * @author Kevin Grüneberg + */ +public enum InjectionStrategy { + + /** Annotations are written on the field **/ + FIELD, + + /** Annotations are written on the constructor **/ + CONSTRUCTOR +} diff --git a/core-common/src/main/java/org/mapstruct/Mapper.java b/core-common/src/main/java/org/mapstruct/Mapper.java index f41e63df4..85458b39e 100644 --- a/core-common/src/main/java/org/mapstruct/Mapper.java +++ b/core-common/src/main/java/org/mapstruct/Mapper.java @@ -160,6 +160,16 @@ public @interface Mapper { */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * Determines whether to use field or constructor injection. This is only used on annotated based component models + * such as CDI, Spring and JSR 330. + * + * If no strategy is configured, {@link InjectionStrategy#FIELD} will be used as default. + * + * @return strategy how to inject + */ + InjectionStrategy injectionStrategy() default InjectionStrategy.FIELD; + /** * If MapStruct could not find another mapping method or apply an automatic conversion it will try to generate a * sub-mapping method between the two beans. If this property is set to {@code true} MapStruct will not try to diff --git a/core-common/src/main/java/org/mapstruct/MapperConfig.java b/core-common/src/main/java/org/mapstruct/MapperConfig.java index 89db2bed5..fdf27a467 100644 --- a/core-common/src/main/java/org/mapstruct/MapperConfig.java +++ b/core-common/src/main/java/org/mapstruct/MapperConfig.java @@ -147,6 +147,18 @@ public @interface MapperConfig { */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * Determines whether to use field or constructor injection. This is only used on annotated based component models + * such as CDI, Spring and JSR 330. + * + * Can be overridden by the one on {@link Mapper}. + * + * If no strategy is configured, {@link InjectionStrategy#FIELD} will be used as default. + * + * @return strategy how to inject + */ + InjectionStrategy injectionStrategy() default InjectionStrategy.FIELD; + /** * If MapStruct could not find another mapping method or apply an automatic conversion it will try to generate a * sub-mapping method between the two beans. If this property is set to {@code true} MapStruct will not try to diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java new file mode 100644 index 000000000..7a12cf61d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java @@ -0,0 +1,79 @@ +/** + * Copyright 2012-2017 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.internal.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents a constructor that is used for constructor injection. + * + * @author Kevin Grüneberg + */ +public class AnnotatedConstructor extends ModelElement implements Constructor { + + private final String name; + private final List mapperReferences; + private final List annotations; + private final boolean publicEmptyConstructor; + + public AnnotatedConstructor(String name, List mapperReferences, + List annotations, boolean publicEmptyConstructor) { + this.name = name; + this.mapperReferences = mapperReferences; + this.annotations = annotations; + this.publicEmptyConstructor = publicEmptyConstructor; + } + + @Override + public Set getImportTypes() { + Set types = new HashSet(); + + for ( MapperReference mapperReference : mapperReferences ) { + types.addAll( mapperReference.getImportTypes() ); + } + + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } + + return types; + } + + @Override + public String getName() { + return name; + } + + public List getMapperReferences() { + return mapperReferences; + } + + public List getAnnotations() { + return annotations; + } + + public boolean isPublicEmptyConstructor() { + return publicEmptyConstructor; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java index b913fd7d9..88d8dbe29 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java @@ -25,18 +25,28 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.Type; /** - * Mapper reference which is retrieved via Annotation-based dependency injection. + * Mapper reference which is retrieved via Annotation-based dependency injection.
+ * The dependency injection may vary between field and constructor injection. Thus, it is possible to define, whether to + * include annotations on the field. * * @author Gunnar Morling * @author Andreas Gudian + * @author Kevin Grüneberg */ public class AnnotationMapperReference extends MapperReference { private final List annotations; - public AnnotationMapperReference(Type type, String variableName, List annotations, boolean isUsed) { + private final boolean fieldFinal; + + private final boolean includeAnnotationsOnField; + + public AnnotationMapperReference(Type type, String variableName, List annotations, boolean isUsed, + boolean fieldFinal, boolean includeAnnotationsOnField) { super( type, variableName, isUsed ); this.annotations = annotations; + this.fieldFinal = fieldFinal; + this.includeAnnotationsOnField = includeAnnotationsOnField; } public List getAnnotations() { @@ -54,4 +64,22 @@ public class AnnotationMapperReference extends MapperReference { return types; } + + public boolean isFieldFinal() { + return fieldFinal; + } + + public boolean isIncludeAnnotationsOnField() { + return includeAnnotationsOnField; + } + + public AnnotationMapperReference withNewAnnotations(List annotations) { + return new AnnotationMapperReference( + getType(), + getVariableName(), + annotations, + isUsed(), + isFieldFinal(), + isIncludeAnnotationsOnField() ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java index b6e1916a5..0f07dd528 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java @@ -18,8 +18,12 @@ */ package org.mapstruct.ap.internal.model; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; + /** - * Basic interface class that facilitates an empty constructor + * Basic interface class that facilitates an empty constructor. * * @author Sjaak Derksen */ @@ -27,4 +31,6 @@ public interface Constructor { String getName(); + Set getImportTypes(); + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 98d499ef8..592620997 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -68,14 +68,9 @@ public abstract class GeneratedType extends ModelElement { // CHECKSTYLE:OFF protected GeneratedType(TypeFactory typeFactory, String packageName, String name, String superClassName, - String interfacePackage, String interfaceName, - List methods, - List fields, - Options options, - VersionInformation versionInformation, - Accessibility accessibility, - SortedSet extraImportedTypes, - Constructor constructor ) { + String interfacePackage, String interfaceName, List methods, + List fields, Options options, VersionInformation versionInformation, + Accessibility accessibility, SortedSet extraImportedTypes, Constructor constructor) { this.packageName = packageName; this.name = name; this.superClassName = superClassName; @@ -169,6 +164,10 @@ public abstract class GeneratedType extends ModelElement { return accessibility; } + public void setConstructor(Constructor constructor) { + this.constructor = constructor; + } + @Override public SortedSet getImportTypes() { SortedSet importedTypes = new TreeSet(); @@ -196,6 +195,12 @@ public abstract class GeneratedType extends ModelElement { addIfImportRequired( importedTypes, extraImport ); } + if ( constructor != null ) { + for ( Type type : constructor.getImportTypes() ) { + addIfImportRequired( importedTypes, type ); + } + } + return importedTypes; } 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 6a3cda066..b893cf312 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,11 +18,6 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT; -import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS; -import static org.mapstruct.ap.internal.util.Collections.first; -import static org.mapstruct.ap.internal.util.Collections.last; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -61,6 +56,11 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.ValueProvider; import org.mapstruct.ap.internal.util.accessor.Accessor; +import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT; +import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS; +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.last; + /** * 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 diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java new file mode 100644 index 000000000..18860bc37 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java @@ -0,0 +1,30 @@ +/** + * Copyright 2012-2017 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.internal.prism; + +/** + * Prism for the enum {@link org.mapstruct.InjectionStrategy}. + * + * @author Kevin Grüneberg + */ +public enum InjectionStrategyPrism { + + FIELD, + CONSTRUCTOR; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index bd59486a4..421ce0d99 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -20,27 +20,32 @@ package org.mapstruct.ap.internal.processor; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.ListIterator; +import java.util.Set; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.model.AnnotatedConstructor; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.AnnotationMapperReference; import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Field; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.MapperReference; +import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.prism.InjectionStrategyPrism; import org.mapstruct.ap.internal.util.MapperConfiguration; /** - * An {@link ModelElementProcessor} which converts the given {@link Mapper} - * object into an annotation based component model in case a matching model is selected as - * target component model for this mapper. + * An {@link ModelElementProcessor} which converts the given {@link Mapper} object into an annotation based component + * model in case a matching model is selected as target component model for this mapper. * * @author Gunnar Morling * @author Andreas Gudian + * @author Kevin Grüneberg */ public abstract class AnnotationBasedComponentModelProcessor implements ModelElementProcessor { @@ -50,8 +55,10 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) { this.typeFactory = context.getTypeFactory(); - String componentModel = MapperConfiguration.getInstanceOn( mapperTypeElement ) - .componentModel( context.getOptions() ); + MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn( mapperTypeElement ); + + String componentModel = mapperConfiguration.componentModel( context.getOptions() ); + InjectionStrategyPrism injectionStrategy = mapperConfiguration.getInjectionStrategy(); if ( !getComponentModelIdentifier().equalsIgnoreCase( componentModel ) ) { return mapper; @@ -65,21 +72,26 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle mapper.removeDecorator(); } else if ( mapper.getDecorator() != null ) { - adjustDecorator( mapper ); + adjustDecorator( mapper, injectionStrategy ); } List annotations = getMapperReferenceAnnotations(); ListIterator iterator = mapper.getReferencedMappers().listIterator(); + while ( iterator.hasNext() ) { MapperReference reference = iterator.next(); iterator.remove(); - iterator.add( replacementMapperReference( reference, annotations ) ); + iterator.add( replacementMapperReference( reference, annotations, injectionStrategy ) ); + } + + if ( injectionStrategy == InjectionStrategyPrism.CONSTRUCTOR ) { + buildConstructors( mapper ); } return mapper; } - protected void adjustDecorator(Mapper mapper) { + protected void adjustDecorator(Mapper mapper, InjectionStrategyPrism injectionStrategy) { Decorator decorator = mapper.getDecorator(); for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { @@ -88,17 +100,111 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle decorator.removeConstructor(); - List annotations = getDelegatorReferenceAnnotations( mapper ); List replacement = new ArrayList(); if ( !decorator.getMethods().isEmpty() ) { for ( Field field : decorator.getFields() ) { - replacement.add( replacementMapperReference( field, annotations ) ); + replacement.add( replacementMapperReference( field, annotations, injectionStrategy ) ); } } decorator.setFields( replacement ); } + private void buildConstructors(Mapper mapper) { + if ( !mapper.getReferencedMappers().isEmpty() ) { + AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper ); + + if ( !annotatedConstructor.getMapperReferences().isEmpty() ) { + mapper.setConstructor( annotatedConstructor ); + } + } + + Decorator decorator = mapper.getDecorator(); + + if ( decorator != null ) { + AnnotatedConstructor decoratorConstructor = buildAnnotatedConstructorForDecorator( decorator ); + if ( !decoratorConstructor.getMapperReferences().isEmpty() ) { + decorator.setConstructor( decoratorConstructor ); + } + } + } + + private AnnotatedConstructor buildAnnotatedConstructorForMapper(Mapper mapper) { + List mapperReferencesForConstructor = + new ArrayList( mapper.getReferencedMappers().size() ); + + for ( MapperReference mapperReference : mapper.getReferencedMappers() ) { + mapperReferencesForConstructor.add( (AnnotationMapperReference) mapperReference ); + } + + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + + removeDuplicateAnnotations( mapperReferencesForConstructor, mapperReferenceAnnotations ); + + return new AnnotatedConstructor( + mapper.getName(), + mapperReferencesForConstructor, + mapperReferenceAnnotations, + additionalPublicEmptyConstructor() ); + } + + private AnnotatedConstructor buildAnnotatedConstructorForDecorator(Decorator decorator) { + List mapperReferencesForConstructor = + new ArrayList( decorator.getFields().size() ); + + for ( Field field : decorator.getFields() ) { + if ( field instanceof AnnotationMapperReference ) { + mapperReferencesForConstructor.add( (AnnotationMapperReference) field ); + } + } + + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + + removeDuplicateAnnotations( mapperReferencesForConstructor, mapperReferenceAnnotations ); + + return new AnnotatedConstructor( + decorator.getName(), + mapperReferencesForConstructor, + mapperReferenceAnnotations, + additionalPublicEmptyConstructor() ); + } + + /** + * Removes duplicate constructor parameter annotations. If an annotation is already present on the constructor, it + * does not have be defined on the constructor parameter, too. For example, for CDI, the javax.inject.Inject + * annotation is on the constructor and does not need to be on the constructor parameters. + * + * @param annotationMapperReferences annotations to annotate the constructor parameter with + * @param mapperReferenceAnnotations annotations to annotate the constructor with + */ + private void removeDuplicateAnnotations(List annotationMapperReferences, + List mapperReferenceAnnotations) { + ListIterator mapperReferenceIterator = annotationMapperReferences.listIterator(); + + Set mapperReferenceAnnotationsTypes = new HashSet(); + for ( Annotation annotation : mapperReferenceAnnotations ) { + mapperReferenceAnnotationsTypes.add( annotation.getType() ); + } + + while ( mapperReferenceIterator.hasNext() ) { + AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next(); + mapperReferenceIterator.remove(); + + List qualifiers = new ArrayList(); + for ( Annotation annotation : annotationMapperReference.getAnnotations() ) { + if ( !mapperReferenceAnnotationsTypes.contains( annotation.getType() ) ) { + qualifiers.add( annotation ); + } + } + + mapperReferenceIterator.add( annotationMapperReference.withNewAnnotations( qualifiers ) ); + } + } + + protected boolean additionalPublicEmptyConstructor() { + return false; + } + protected List getDelegatorReferenceAnnotations(Mapper mapper) { return Collections.emptyList(); } @@ -106,16 +212,23 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle /** * @param originalReference the reference to be replaced * @param annotations the list of annotations - * + * @param injectionStrategyPrism strategy for injection * @return the mapper reference replacing the original one */ - protected MapperReference replacementMapperReference(Field originalReference, List annotations) { + protected MapperReference replacementMapperReference(Field originalReference, List annotations, + InjectionStrategyPrism injectionStrategyPrism) { + boolean finalField = + injectionStrategyPrism == InjectionStrategyPrism.CONSTRUCTOR && !additionalPublicEmptyConstructor(); + + boolean includeAnnotationsOnField = injectionStrategyPrism == InjectionStrategyPrism.FIELD; + return new AnnotationMapperReference( originalReference.getType(), originalReference.getVariableName(), annotations, - originalReference.isUsed() - ); + originalReference.isUsed(), + finalField, + includeAnnotationsOnField ); } /** @@ -125,7 +238,6 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle /** * @param mapper the mapper - * * @return the annotation(s) to be added at the mapper type implementation */ protected abstract List getTypeAnnotations(Mapper mapper); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java index 582439c05..cb4198778 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java @@ -55,4 +55,9 @@ public class CdiComponentProcessor extends AnnotationBasedComponentModelProcesso protected boolean requiresGenerationOfDecoratorClass() { return false; } + + @Override + protected boolean additionalPublicEmptyConstructor() { + return true; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java index e9118a585..721a7dfff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java @@ -29,6 +29,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.InjectionStrategyPrism; import org.mapstruct.ap.internal.prism.MapperConfigPrism; import org.mapstruct.ap.internal.prism.MapperPrism; import org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism; @@ -155,6 +156,15 @@ public class MapperConfiguration { } } + public InjectionStrategyPrism getInjectionStrategy() { + if ( mapperConfigPrism != null && mapperPrism.values.injectionStrategy() == null ) { + return InjectionStrategyPrism.valueOf( mapperConfigPrism.injectionStrategy() ); + } + else { + return InjectionStrategyPrism.valueOf( mapperPrism.injectionStrategy() ); + } + } + public NullValueMappingStrategyPrism getNullValueMappingStrategy() { if ( mapperConfigPrism != null && mapperPrism.values.nullValueMappingStrategy() == null ) { return NullValueMappingStrategyPrism.valueOf( mapperConfigPrism.nullValueMappingStrategy() ); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl new file mode 100644 index 000000000..78c3a76c4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl @@ -0,0 +1,35 @@ +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotatedConstructor" --> +<#-- + + Copyright 2012-2017 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 publicEmptyConstructor> +public ${name}() { +} + + +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + +public ${name}(<#list mapperReferences as mapperReference><#list mapperReference.annotations as annotation><@includeModel object=annotation/> <@includeModel object=mapperReference.type/> ${mapperReference.variableName}<#if mapperReference_has_next>, ) { + <#list mapperReferences as mapperReference> + this.${mapperReference.variableName} = ${mapperReference.variableName}; + +} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl index fa4e5dcfb..ac42d221e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl @@ -19,7 +19,9 @@ limitations under the License. --> -<#list annotations as annotation> -<#nt><@includeModel object=annotation/> - -private <@includeModel object=type/> ${variableName}; \ No newline at end of file +<#if includeAnnotationsOnField> + <#list annotations as annotation> + <#nt><@includeModel object=annotation/> + + +private <#if fieldFinal>final <@includeModel object=type/> ${variableName}; \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java new file mode 100644 index 000000000..7dd675c9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.decorator.spring.constructor; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java new file mode 100644 index 000000000..0f7c0e726 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012-2017 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.decorator.spring.constructor; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +public abstract class PersonMapperDecorator implements PersonMapper { + + @Autowired + @Qualifier("delegate") + private PersonMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java new file mode 100644 index 000000000..dd3aefcfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 2012-2017 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.decorator.spring.constructor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Calendar; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * Test for the application of decorators using component model spring. + * + * @author Andreas Gudian + */ +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@IssueKey("592") +@RunWith(AnnotationProcessorTestRunner.class) +@ComponentScan(basePackageClasses = SpringDecoratorTest.class) +@Configuration +public class SpringDecoratorTest { + + @Autowired + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @Before + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @After + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @Test + public void shouldInvokeDecoratorMethods() { + //given + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, 4, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + //when + PersonDto personDto = personMapper.personToPersonDto( person ); + + //then + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @Test + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + //given + Address address = new Address( "42 Ocean View Drive" ); + + //when + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + //then + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.java index decbb04a5..3fdbd0dd6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.java @@ -16,9 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.test.decorator.spring; +package org.mapstruct.ap.test.decorator.spring.field; import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.ap.test.decorator.Address; @@ -26,7 +27,7 @@ import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.FIELD) @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.java similarity index 96% rename from processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java rename to processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.java index 9870460fb..c9069cf9e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.test.decorator.spring; +package org.mapstruct.ap.test.decorator.spring.field; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java similarity index 98% rename from processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java rename to processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java index ddae3084f..e95fa9a6f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.test.decorator.spring; +package org.mapstruct.ap.test.decorator.spring.field; import static org.assertj.core.api.Assertions.assertThat; diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java new file mode 100644 index 000000000..d83d38f08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java @@ -0,0 +1,29 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = "jsr330", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ConstructorJsr330Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java new file mode 100644 index 000000000..2193fe58c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Kevin Grüneberg + */ +@Mapper( componentModel = "jsr330", + uses = GenderJsr330ConstructorMapper.class, + injectionStrategy = InjectionStrategy.CONSTRUCTOR ) +public interface CustomerJsr330ConstructorMapper { + + @Mapping(source = "gender", target = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/GenderJsr330ConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/GenderJsr330ConstructorMapper.java new file mode 100644 index 000000000..fa813ce84 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/GenderJsr330ConstructorMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.constructor; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Kevin Grüneberg + */ +@Mapper(config = ConstructorJsr330Config.class) +public interface GenderJsr330ConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java new file mode 100644 index 000000000..3f85a1353 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.constructor; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test constructor injection for component model spring. + * + * @author Kevin Grüneberg + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330ConstructorMapper.class, + GenderJsr330ConstructorMapper.class, + ConstructorJsr330Config.class +}) +@IssueKey("571") +@RunWith(AnnotationProcessorTestRunner.class) +@ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) +@Configuration +public class Jsr330ConstructorMapperTest { + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJsr330ConstructorMapper customerMapper; + private ConfigurableApplicationContext context; + + @Before + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @After + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @Test + public void shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @Test + public void shouldHaveConstructorInjection() { + generatedSource.forMapper( CustomerJsr330ConstructorMapper.class ) + .content() + .contains( "private final GenderJsr330ConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJsr330ConstructorMapperImpl(GenderJsr330ConstructorMapper" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java new file mode 100644 index 000000000..55dba208d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Kevin Grüneberg + */ +@Mapper(componentModel = "jsr330", uses = GenderJsr330FieldMapper.class, injectionStrategy = InjectionStrategy.FIELD) +public interface CustomerJsr330FieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java new file mode 100644 index 000000000..2dab52dc5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java @@ -0,0 +1,29 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = "jsr330", injectionStrategy = InjectionStrategy.FIELD) +public interface FieldJsr330Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/GenderJsr330FieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/GenderJsr330FieldMapper.java new file mode 100644 index 000000000..b72ec8078 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/GenderJsr330FieldMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.field; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Kevin Grüneberg + */ +@Mapper(config = FieldJsr330Config.class) +public interface GenderJsr330FieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java new file mode 100644 index 000000000..ac42d731a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -0,0 +1,109 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.jsr330.field; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test field injection for component model spring. + * + * @author Kevin Grüneberg + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330FieldMapper.class, + GenderJsr330FieldMapper.class, + FieldJsr330Config.class +}) +@IssueKey("571") +@RunWith(AnnotationProcessorTestRunner.class) +@ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) +@Configuration +public class Jsr330FieldMapperTest { + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private CustomerJsr330FieldMapper customerMapper; + private ConfigurableApplicationContext context; + + @Before + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @After + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @Test + public void shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @Test + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerJsr330FieldMapper.class ) + .content() + .contains( "@Inject" + lineSeparator() + " private GenderJsr330FieldMapper" ) + .doesNotContain( "public CustomerJsr330FieldMapperImpl(" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerDto.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerDto.java new file mode 100644 index 000000000..398b89089 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerDto.java @@ -0,0 +1,45 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.shared; + +/** + * @author Kevin Grüneberg + */ +public class CustomerDto { + + private GenderDto gender; + + private String name; + + public GenderDto getGender() { + return gender; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setGender(GenderDto gender) { + this.gender = gender; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerEntity.java new file mode 100644 index 000000000..862e7156a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/CustomerEntity.java @@ -0,0 +1,45 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.shared; + +/** + * @author Kevin Grüneberg + */ +public class CustomerEntity { + + private Gender gender; + + private String name; + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/Gender.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/Gender.java new file mode 100644 index 000000000..5d923ab92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/Gender.java @@ -0,0 +1,28 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.shared; + +/** + * @author Kevin Grüneberg + */ +public enum Gender { + + MALE, FEMALE + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/GenderDto.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/GenderDto.java new file mode 100644 index 000000000..567aac964 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/shared/GenderDto.java @@ -0,0 +1,28 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.shared; + +/** + * @author Kevin Grüneberg + */ +public enum GenderDto { + + M, F + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java new file mode 100644 index 000000000..33f941afe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ConstructorSpringConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java new file mode 100644 index 000000000..d5812bb8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Kevin Grüneberg + */ +@Mapper( componentModel = "spring", + uses = GenderSpringConstructorMapper.class, + injectionStrategy = InjectionStrategy.CONSTRUCTOR ) +public interface CustomerSpringConstructorMapper { + + @Mapping( source = "gender", target = "gender" ) + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/GenderSpringConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/GenderSpringConstructorMapper.java new file mode 100644 index 000000000..bc2667dee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/GenderSpringConstructorMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.constructor; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Kevin Grüneberg + */ +@Mapper(config = ConstructorSpringConfig.class) +public interface GenderSpringConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java new file mode 100644 index 000000000..1b0dba77e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.constructor; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test constructor injection for component model spring. + * + * @author Kevin Grüneberg + */ +@WithClasses( { + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerSpringConstructorMapper.class, + GenderSpringConstructorMapper.class, + ConstructorSpringConfig.class +} ) +@IssueKey( "571" ) +@RunWith(AnnotationProcessorTestRunner.class) +@ComponentScan(basePackageClasses = CustomerSpringConstructorMapper.class) +@Configuration +public class SpringConstructorMapperTest { + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerSpringConstructorMapper customerMapper; + private ConfigurableApplicationContext context; + + @Before + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @After + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @Test + public void shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @Test + public void shouldHaveConstructorInjection() { + generatedSource.forMapper( CustomerSpringConstructorMapper.class ) + .content() + .contains( "private final GenderSpringConstructorMapper" ) + .contains( "@Autowired" + lineSeparator() + + " public CustomerSpringConstructorMapperImpl(GenderSpringConstructorMapper" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java new file mode 100644 index 000000000..d0a685825 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Kevin Grüneberg + */ +@Mapper(componentModel = "spring", uses = GenderSpringFieldMapper.class, injectionStrategy = InjectionStrategy.FIELD) +public interface CustomerSpringFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java new file mode 100644 index 000000000..f712d1478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = "spring", injectionStrategy = InjectionStrategy.FIELD) +public interface FieldSpringConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/GenderSpringFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/GenderSpringFieldMapper.java new file mode 100644 index 000000000..655a640a8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/GenderSpringFieldMapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.field; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Kevin Grüneberg + */ +@Mapper(config = FieldSpringConfig.class) +public interface GenderSpringFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java new file mode 100644 index 000000000..37ac7ff98 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java @@ -0,0 +1,106 @@ +/** + * Copyright 2012-2017 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.injectionstrategy.spring.field; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test field injection for component model spring. + * + * @author Kevin Grüneberg + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerSpringFieldMapper.class, + GenderSpringFieldMapper.class, + FieldSpringConfig.class +}) +@IssueKey("571") +@RunWith(AnnotationProcessorTestRunner.class) +@ComponentScan(basePackageClasses = CustomerSpringFieldMapper.class) +@Configuration +public class SpringFieldMapperTest { + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerSpringFieldMapper customerMapper; + private ConfigurableApplicationContext context; + + @Before + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @After + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @Test + public void shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @Test + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerSpringFieldMapper.class ) + .content() + .contains( "@Autowired" + lineSeparator() + " private GenderSpringFieldMapper" ) + .doesNotContain( "public CustomerSpringFieldMapperImpl(" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java b/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java index e91c9bf90..be2ccb41d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java @@ -25,11 +25,13 @@ import java.util.List; import org.junit.Test; import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.InjectionStrategy; import org.mapstruct.MappingInheritanceStrategy; import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ReportingPolicy; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.InjectionStrategyPrism; import org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; @@ -71,6 +73,12 @@ public class EnumPrismsTest { namesOf( ReportingPolicyPrism.values() ) ); } + @Test + public void injectionStrategyPrismIsCorrect() { + assertThat( namesOf( InjectionStrategy.values() ) ).isEqualTo( + namesOf( InjectionStrategyPrism.values() ) ); + } + private static List namesOf(Enum[] values) { List names = new ArrayList( values.length );