Compare commits

...

25 Commits

Author SHA1 Message Date
Filip Hrisafov
6f79a53190 [maven-release-plugin] prepare release 1.3.1.Final 2019-09-29 17:54:46 +02:00
Sjaak Derksen
0c1c5b7f31 #1821 nullpointer due to @BeanMapping via inheritance (#1822) (#1932)
(cherry picked from commit ade4f4d7e2ab87b2e0f113221d3fc9a6729f78cc)
2019-09-29 17:46:53 +02:00
Filip Hrisafov
b32cf92519
#1828 Use update method when nested property of target is populated with multiple nested properties of source (#1930) 2019-09-29 16:54:04 +02:00
Sjaak Derksen
4dfb7d0ec9 #1801 Using constructor as builderCreationMethod in custom builder (#1905) 2019-09-28 19:37:12 +02:00
Sjaak Derksen
66055f024b #1756 better forged method error based on empty target bean properties (#1757) 2019-09-28 19:15:51 +02:00
Filip Hrisafov
fe22c9b1ce #1742 & #1661 refactoring and making builder optional
Applying changes done in 33710584d33856495992c7cab511b4a16643f53e
2019-09-28 19:15:51 +02:00
Filip Hrisafov
55363dfc1c #1742 Add test case 2019-09-28 19:15:51 +02:00
Filip Hrisafov
318b30ef23 #1799 Fluent setters starting with set should work properly 2019-09-22 18:22:53 +02:00
Filip Hrisafov
a71e7c0a0b Fix travis.yml on 1.3.x branch 2019-09-22 13:46:39 +02:00
Filip Hrisafov
edcf78a34d #1904 Do not include private methods when getting enclosed executable elements 2019-09-22 10:16:27 +02:00
Filip Hrisafov
ad38786117 #1790 Use mapperPrism.values.nullValuePropertyMappingStrategy when retrieving NullValuePropertyMappingStrategy 2019-09-22 10:12:31 +02:00
Sjaak Derksen
23a0420359 #1862 Update @MappingTarget documentation to take builders (#1864) 2019-09-22 10:11:08 +02:00
Jonathan Kraska
2f36a41735 #1826: fixed null pointer in nested property mapping when using presence checking (#1827) 2019-09-22 10:10:29 +02:00
Matt Drees
28b4e9ed45 Improve terms in qualifier docs (#1814)
This sentence is talking about `@Target`, not `@Retention`.
Also, let's use 'type' instead of 'class' to line up with `ElementType.TYPE`.
2019-09-22 10:09:26 +02:00
Filip Hrisafov
211c627c25 #1751 Fix handling of possible builder creation methods with generic signature
When a method has a generic signature and the builder type is generic then the method return type does not match the builder type.
Therefore check only for raw types.
Add extra check for void method since a void method can't be a builder creation method
2019-09-22 10:08:01 +02:00
Sjaak Derksen
69d0a2d557 #1819 documentation clarification on obtaining Mapper (#1820) 2019-09-22 10:06:07 +02:00
Filip Hrisafov
0530a80478 #1797 Use EnumSet.noneOf when creating a new instance of EnumSet 2019-09-22 10:05:33 +02:00
Sjaak Derksen
6af13ba220 #1784 NullValueMappingStrategy.RETURN_DEFAULT refers wrongly to primitive types (#1785) 2019-09-22 10:04:52 +02:00
Sjaak Derksen
359630aec1 #1772 unmapped source prop remaining when target and source entry diff (#1778) 2019-09-22 10:04:12 +02:00
Christian Bandowski
b2bb768f75 [#1457] Stricter matching for lifecycle methods / non-unique parameters (#1782)
In case a lifecycle method has multiple matching parameters (e. g. same type)
all parameter names must match exactly with the ones from the mapping method,
otherwise the lifecycle method will not be used and a warning will be shown.
2019-09-22 10:02:58 +02:00
power721
bd1f6e6f27 Fix typo and code error in documentation (#1779)
* fix typo in documentation "Using builders"

* fix generated code example in stream mapping
2019-09-22 10:00:32 +02:00
juliojgd
7164ef18c5 Fix typo in documentation (#1760)
It is "then" instead "than
2019-09-22 10:00:00 +02:00
Sjaak Derksen
546c56b116 #1142 update documentation (#1710)
* #1142 update documentation

* #1142 comment
2019-09-22 09:59:11 +02:00
Filip Hrisafov
9801163402 #1738: Use typeBound for the return type of the nested property mapping method and for the definition of the properties within the method 2019-09-22 09:58:06 +02:00
Filip Hrisafov
285cb219c8 Prepare for 1.3.1 bug fix release 2019-09-22 09:53:47 +02:00
134 changed files with 3539 additions and 609 deletions

View File

@ -1,4 +1,5 @@
language: java
dist: trusty
jdk:
- oraclejdk8
install: true
@ -11,13 +12,10 @@ deploy:
script: "test ${TRAVIS_TEST_RESULT} -eq 0 && mvn -s etc/travis-settings.xml -DskipTests=true deploy"
skip_cleanup: true
on:
repo: mapstruct/mapstruct
branch: master
sudo: required
cache:
directories:
- $HOME/.m2
addons:
apt:
packages:
- oracle-java8-installer

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -23,6 +23,7 @@ Gervais Blaise - https://github.com/gervaisb
Gunnar Morling - https://github.com/gunnarmorling
Ivo Smid - https://github.com/bedla
Jeff Smyth - https://github.com/smythie86
Jonathan Kraska - https://github.com/jakraska
Joshua Spoerri - https://github.com/spoerri
Kevin Grüneberg - https://github.com/kevcodez
Michael Pardo - https://github.com/pardom

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@ -104,6 +104,14 @@
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<parameter>
<excludes>
<!-- Exclude Builder from check see https://github.com/siom79/japicmp/issues/249 -->
<exclude>org.mapstruct.Builder</exclude>
</excludes>
</parameter>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -29,4 +29,12 @@ public @interface Builder {
* @return the method that needs to tbe invoked on the builder
*/
String buildMethod() default "build";
/**
* Toggling builders on / off. Builders are sometimes used solely for unit testing (fluent testdata)
* MapStruct will need to use the regular getters /setters in that case.
*
* @return when true, no builder patterns will be applied
*/
boolean disableBuilder() default false;
}

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -41,7 +41,7 @@ public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
return null;
}
return integers.stream().map( integer -> String.valueOf( integer ) )
return integers.map( integer -> String.valueOf( integer ) )
.collect( Collectors.toCollection( HashSet<String>::new ) );
}
@ -51,7 +51,7 @@ public List<CarDto> carsToCarDtos(Stream<Car> cars) {
return null;
}
return integers.stream().map( car -> carToCarDto( car ) )
return cars.map( car -> carToCarDto( car ) )
.collect( Collectors.toCollection( ArrayList<CarDto>::new ) );
}
----

View File

@ -568,7 +568,7 @@ public interface CarMapper {
The generated code of the `updateCarFromDto()` method will update the passed `Car` instance with the properties from the given `CarDto` object. There may be only one parameter marked as mapping target. Instead of `void` you may also set the method's return type to the type of the target parameter, which will cause the generated implementation to update the passed mapping target and return it as well. This allows for fluent invocations of mapping methods.
Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map.
For `CollectionMappingStrategy.ACCESSOR_ONLY` Collection- or map-typed properties of the target bean to be updated will be cleared and then populated with the values from the corresponding source collection or map. Otherwise, For `CollectionMappingStrategy.ADDER_PREFERRED` or `CollectionMappingStrategy.TARGET_IMMUTABLE` the target will not be cleared and the values will be populated immediately.
[[direct-field-mappings]]
=== Mappings with direct field access
@ -656,7 +656,7 @@ project on GitHub.
MapStruct also supports mapping of immutable types via builders.
When performing a mapping MapStruct checks if there is a builder for the type being mapped.
This is done via the `BuilderProvider` SPI.
If a Builder exists for a certain type, than that builder will be used for the mappings.
If a Builder exists for a certain type, then that builder will be used for the mappings.
The default implementation of the `BuilderProvider` assumes the following:
@ -677,7 +677,7 @@ To finish the mapping MapStruct generates code that will invoke the build method
[NOTE]
======
The <<object-factories>> are also considered for the builder type.
E.g. If an object factory exists for our `PersonBuilder` than this factory would be used instead of the builder creation method.
E.g. If an object factory exists for our `PersonBuilder` then this factory would be used instead of the builder creation method.
======
.Person with Builder example
@ -768,9 +768,9 @@ In case you want to disable using builders then you can use the `NoOpBuilderProv
== Retrieving a mapper
[[mappers-factory]]
=== The Mappers factory
=== The Mappers factory (no dependency injection)
Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return:
When not using a DI framework, Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return:
.Using the Mappers factory
====
@ -783,7 +783,7 @@ CarMapper mapper = Mappers.getMapper( CarMapper.class );
By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type:
.Declaring an instance of a mapper
.Declaring an instance of a mapper (interface)
====
[source, java, linenums]
[subs="verbatim,attributes"]
@ -799,6 +799,22 @@ public interface CarMapper {
----
====
.Declaring an instance of a mapper (abstract class)
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public abstract class CarMapper {
public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
}
----
====
This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances:
.Accessing a mapper
@ -811,12 +827,13 @@ CarDto dto = CarMapper.INSTANCE.carToCarDto( car );
----
====
Note that mappers generated by MapStruct are thread-safe and thus can safely be accessed from several threads at the same time.
Note that mappers generated by MapStruct are stateless and thread-safe and thus can safely be accessed from several threads at the same time.
[[using-dependency-injection]]
=== Using dependency injection
If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection as well. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <<configuration-options>>.
If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection and *not* via the `Mappers` class as described above. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <<configuration-options>>.
Currently there is support for CDI and Spring (the latter either via its custom annotations or using the JSR 330 annotations). See <<configuration-options>> for the allowed values of the `componentModel` attribute which are the same as for the `mapstruct.defaultComponentModel` processor option. In both cases the required annotations will be added to the generated mapper implementations classes in order to make the same subject to dependency injection. The following shows an example using CDI:
@ -1312,7 +1329,7 @@ public @interface GermanToEnglish {
----
====
Please take note of the retention `TitleTranslator` on class level, `EnglishToGerman`, `GermanToEnglish` on method level!
Please take note of the target `TitleTranslator` on type level, `EnglishToGerman`, `GermanToEnglish` on method level!
Then, using the qualifiers, the mapping could look like this:
@ -2124,7 +2141,6 @@ MapStruct offers control over the object to create when the source argument of t
However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MappingConfig`, the mapping result can be altered to return empty *default* values. This means for:
* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present.
* *Primitives*: the default values for primitives will be returned, e.g. `false` for `boolean` or `0` for `int`.
* *Iterables / Arrays*: an empty iterable will be returned.
* *Maps*: an empty map will be returned.
@ -2695,6 +2711,7 @@ Within those groups, the method invocations are ordered by their location of def
*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation.
*Important:* when using a builder, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter so that the method is able to modify the object going to be build. The `build` method is called when the `@AfterMapping` annotated method scope finishes. MapStruct will not call the `@AfterMapping` annotated method if the real target is used as `@MappingTarget` annotated parameter.
[[using-spi]]
== Using the MapStruct SPI

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -51,6 +51,7 @@ public class FullFeatureCompilationTest {
// SPI not working correctly here.. (not picked up)
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" );
switch ( processorType ) {
case ORACLE_JAVA_9:

View File

@ -11,7 +11,7 @@
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<packaging>pom</packaging>
<name>MapStruct Parent</name>
@ -58,7 +58,7 @@
<connection>scm:git:git://github.com/mapstruct/mapstruct.git</connection>
<developerConnection>scm:git:git@github.com:mapstruct/mapstruct.git</developerConnection>
<url>https://github.com/mapstruct/mapstruct/</url>
<tag>HEAD</tag>
<tag>1.3.1.Final</tag>
</scm>
<distributionManagement>

View File

@ -13,7 +13,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>parent/pom.xml</relativePath>
</parent>
@ -54,7 +54,7 @@
<connection>scm:git:git://github.com/mapstruct/mapstruct.git</connection>
<developerConnection>scm:git:git@github.com:mapstruct/mapstruct.git</developerConnection>
<url>https://github.com/mapstruct/mapstruct/</url>
<tag>HEAD</tag>
<tag>1.3.1.Final</tag>
</scm>
<profiles>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.3.1.Final</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

View File

@ -7,6 +7,7 @@ package org.mapstruct.ap.internal.model;
import javax.lang.model.element.AnnotationMirror;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
import org.mapstruct.ap.internal.model.common.SourceRHS;
import org.mapstruct.ap.internal.model.common.Type;
@ -73,7 +74,7 @@ class AbstractBaseBuilder<B extends AbstractBaseBuilder<B>> {
*
* @return See above
*/
Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) {
Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) {
if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) {
return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) );
@ -93,6 +94,7 @@ class AbstractBaseBuilder<B extends AbstractBaseBuilder<B>> {
else {
forgedMappingMethod = new BeanMappingMethod.Builder()
.forgedMethod( forgedMethod )
.returnTypeBuilder( builderType )
.mappingContext( ctx )
.build();
}

View File

@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.model;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.SourceRHS;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.BeanMapping;
import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.ForgedMethodHistory;
import org.mapstruct.ap.internal.util.Strings;
@ -63,7 +64,11 @@ public abstract class AbstractMappingMethodBuilder<B extends AbstractMappingMeth
true
);
return createForgedAssignment( sourceRHS, forgedMethod );
return createForgedAssignment(
sourceRHS,
ctx.getTypeFactory().builderTypeFor( targetType, BeanMapping.builderPrismFor( method ) ),
forgedMethod
);
}
private String getName(Type sourceType, Type targetType) {

View File

@ -5,8 +5,6 @@
*/
package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
@ -20,13 +18,13 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.lang.model.type.DeclaredType;
import javax.tools.Diagnostic;
import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
@ -50,7 +48,11 @@ import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_NOT_ASSIGNABLE;
import static org.mapstruct.ap.internal.util.Message.GENERAL_ABSTRACT_RETURN_TYPE;
/**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally
@ -63,20 +65,23 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final List<PropertyMapping> constantMappings;
private final Type resultType;
private final Type returnTypeToConstruct;
private final BuilderType returnTypeBuilder;
private final MethodReference finalizerMethod;
public static class Builder {
private MappingBuilderContext ctx;
private Method method;
/* returnType to construct can have a builder */
private BuilderType returnTypeBuilder;
private Map<String, Accessor> unprocessedTargetProperties;
private Map<String, Accessor> unprocessedSourceProperties;
private Set<String> targetProperties;
private final List<PropertyMapping> propertyMappings = new ArrayList<>();
private final Set<Parameter> unprocessedSourceParameters = new HashSet<>();
private NullValueMappingStrategyPrism nullValueMappingStrategy;
private SelectionParameters selectionParameters;
private final Set<String> existingVariableNames = new HashSet<>();
private Map<String, List<Mapping>> methodMappings;
private SingleMappingByTargetPropertyNameFunction singleMapping;
@ -87,27 +92,69 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return this;
}
public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) {
this.returnTypeBuilder = returnTypeBuilder;
return this;
}
public Builder sourceMethod(SourceMethod sourceMethod) {
singleMapping = new SourceMethodSingleMapping( sourceMethod );
return setupMethodWithMapping( sourceMethod );
this.method = sourceMethod;
return this;
}
public Builder forgedMethod(Method method) {
singleMapping = new EmptySingleMapping();
return setupMethodWithMapping( method );
this.method = method;
return this;
}
private Builder setupMethodWithMapping(Method sourceMethod) {
this.method = sourceMethod;
this.methodMappings = sourceMethod.getMappingOptions().getMappings();
CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy();
Type mappingType = method.getResultType();
if ( !method.isUpdateMethod() ) {
mappingType = mappingType.getEffectiveType();
public BeanMappingMethod build() {
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null;
/* the return type that needs to be constructed (new or factorized), so for instance: */
/* 1) the return type of a non-update method */
/* 2) or the implementation type that needs to be used when the return type is abstract */
/* 3) or the builder whenever the return type is immutable */
Type returnTypeToConstruct = null;
/* factory or builder method to construct the returnTypeToConstruct */
MethodReference factoryMethod = null;
// determine which return type to construct
if ( !method.getReturnType().isVoid() ) {
Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters );
if ( returnTypeImpl != null ) {
factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
}
else if (isBuilderRequired() ) {
returnTypeImpl = returnTypeBuilder.getBuilder();
factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
}
else if ( !method.isUpdateMethod() ) {
returnTypeImpl = method.getReturnType();
factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters );
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl;
}
}
}
Map<String, Accessor> accessors = mappingType
.getPropertyWriteAccessors( cms );
/* the type that needs to be used in the mapping process as target */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
this.methodMappings = this.method.getMappingOptions().getMappings();
CollectionMappingStrategyPrism cms = this.method.getMapperConfiguration().getCollectionMappingStrategy();
// determine accessors
Map<String, Accessor> accessors = resultTypeToMap.getPropertyWriteAccessors( cms );
this.targetProperties = accessors.keySet();
this.unprocessedTargetProperties = new LinkedHashMap<>( accessors );
@ -125,17 +172,14 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
existingVariableNames.addAll( method.getParameterNames() );
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
// get bean mapping (when specified as annotation )
if ( beanMapping != null ) {
for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) {
unprocessedSourceProperties.remove( ignoreUnmapped );
}
}
return this;
}
public BeanMappingMethod build() {
// map properties with mapping
boolean mappingErrorOccured = handleDefinedMappings();
if ( mappingErrorOccured ) {
@ -158,87 +202,30 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
reportErrorForUnmappedTargetPropertiesIfRequired();
reportErrorForUnmappedSourcePropertiesIfRequired();
// get bean mapping (when specified as annotation )
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() );
// mapNullToDefault
NullValueMappingStrategyPrism nullValueMappingStrategy =
beanMapping != null ? beanMapping.getNullValueMappingStrategy() : null;
boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy );
// selectionParameters
SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null;
// check if there's a factory method for the result type
MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) {
factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod(
method,
method.getResultType(),
selectionParameters,
ctx
);
}
// if there's no factory method, try the resultType in the @BeanMapping
Type resultType = null;
if ( factoryMethod == null ) {
if ( selectionParameters != null && selectionParameters.getResultType() != null ) {
resultType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ).getEffectiveType();
if ( resultType.isAbstract() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
beanMappingPrism.mirror,
Message.BEANMAPPING_ABSTRACT,
resultType,
method.getResultType()
);
}
else if ( !resultType.isAssignableTo( method.getResultType() ) ) {
ctx.getMessager().printMessage(
method.getExecutable(),
beanMappingPrism.mirror,
Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType()
);
}
else if ( !resultType.hasEmptyAccessibleContructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
beanMappingPrism.mirror,
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
resultType
);
}
}
else if ( !method.isUpdateMethod() && method.getReturnType().getEffectiveType().isAbstract() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_ABSTRACT_RETURN_TYPE,
method.getReturnType().getEffectiveType()
);
}
else if ( !method.isUpdateMethod() &&
!method.getReturnType().getEffectiveType().hasEmptyAccessibleContructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
method.getReturnType().getEffectiveType()
);
}
}
// sort
sortPropertyMappingsByDependencies();
// before / after mappings
List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods(
method,
resultTypeToMap,
selectionParameters,
ctx,
existingVariableNames
);
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames );
LifecycleMethodResolver.afterMappingMethods(
method,
resultTypeToMap,
selectionParameters,
ctx,
existingVariableNames
);
if (factoryMethod != null && method instanceof ForgedMethod ) {
( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() );
@ -246,7 +233,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MethodReference finalizeMethod = null;
if ( shouldCallFinalizerMethod( resultType == null ? method.getResultType() : resultType ) ) {
if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
finalizeMethod = getFinalizerMethod();
}
@ -256,32 +243,41 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
propertyMappings,
factoryMethod,
mapNullToDefault,
resultType,
returnTypeToConstruct,
returnTypeBuilder,
beforeMappingMethods,
afterMappingMethods,
finalizeMethod
);
}
private boolean shouldCallFinalizerMethod(Type resultType) {
Type returnType = method.getReturnType();
if ( returnType.isVoid() ) {
/**
* @return builder is required when there is a returnTypeBuilder and the mapping method is not update method.
* However, builder is also required when there is a returnTypeBuilder, the mapping target is the builder and
* builder is not assignable to the return type (so without building).
*/
private boolean isBuilderRequired() {
return returnTypeBuilder != null
&& ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() );
}
private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) {
if ( returnTypeToConstruct == null ) {
return false;
}
Type mappingType = method.isUpdateMethod() ? resultType : resultType.getEffectiveType();
if ( mappingType.isAssignableTo( returnType ) ) {
else if ( returnTypeToConstruct.isAssignableTo( method.getReturnType() ) ) {
// If the mapping type can be assigned to the return type then we
// don't need a finalizer method
return false;
}
return returnType.getBuilderType() != null;
return returnTypeBuilder != null;
}
private MethodReference getFinalizerMethod() {
return BuilderFinisherMethodResolver.getBuilderFinisherMethod(
method,
method.getReturnType().getBuilderType(),
returnTypeBuilder,
ctx
);
}
@ -372,6 +368,88 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
private Type getReturnTypeToConstructFromSelectionParameters(SelectionParameters selectionParams) {
if ( selectionParams != null && selectionParams.getResultType() != null ) {
return ctx.getTypeFactory().getType( selectionParams.getResultType() );
}
return null;
}
private boolean canResultTypeFromBeanMappingBeConstructed(Type resultType) {
boolean error = true;
if ( resultType.isAbstract() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror,
BEANMAPPING_ABSTRACT,
resultType,
method.getResultType()
);
error = false;
}
else if ( !resultType.isAssignableTo( method.getResultType() ) ) {
ctx.getMessager().printMessage(
method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror,
BEANMAPPING_NOT_ASSIGNABLE,
resultType,
method.getResultType()
);
error = false;
}
else if ( !resultType.hasEmptyAccessibleContructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror,
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
resultType
);
error = false;
}
return error;
}
private boolean canReturnTypeBeConstructed(Type returnType) {
boolean error = true;
if ( returnType.isAbstract() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
GENERAL_ABSTRACT_RETURN_TYPE,
returnType
);
error = false;
}
else if ( !returnType.hasEmptyAccessibleContructor() ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
returnType
);
error = false;
}
return error;
}
/**
* Find a factory method for a return type or for a builder.
* @param returnTypeImpl the return type implementation to construct
* @param selectionParameters
* @return
*/
private MethodReference getFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
MethodReference factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method,
returnTypeImpl,
selectionParameters,
ctx
);
if ( factoryMethod == null && returnTypeBuilder != null ) {
factoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( method, returnTypeBuilder );
}
return factoryMethod;
}
/**
* Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
* inverse mapping method.
@ -396,23 +474,30 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
for ( Mapping mapping : entry.getValue() ) {
TargetReference targetReference = mapping.getTargetReference();
if ( targetReference.isValid() ) {
if ( !handledTargets.contains( first( targetReference.getPropertyEntries() ).getFullName() ) ) {
String target = first( targetReference.getPropertyEntries() ).getFullName();
if ( !handledTargets.contains( target ) ) {
if ( handleDefinedMapping( mapping, handledTargets ) ) {
errorOccurred = true;
}
}
if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) {
List<PropertyEntry> sourceEntries = mapping.getSourceReference().getPropertyEntries();
if ( !sourceEntries.isEmpty() ) {
String source = first( sourceEntries ).getFullName();
unprocessedSourceProperties.remove( source );
}
}
}
else {
errorOccurred = true;
}
}
}
// remove the remaining name based properties
for ( String handledTarget : handledTargets ) {
// In order to avoid: "Unknown property foo in return type" in case of duplicate
// target mappings
unprocessedTargetProperties.remove( handledTarget );
unprocessedDefinedTargets.remove( handledTarget );
unprocessedSourceProperties.remove( handledTarget );
}
return errorOccurred;
@ -497,7 +582,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.nullValuePropertyMappingStrategy( mapping.getNullValuePropertyMappingStrategy() )
.build();
handledTargets.add( propertyName );
unprocessedSourceProperties.remove( mapping.getSourceName() );
unprocessedSourceParameters.remove( sourceRef.getParameter() );
}
else {
@ -584,7 +668,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
Accessor sourceReadAccessor =
sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName );
ExecutableElementAccessor sourcePresenceChecker =
Accessor sourcePresenceChecker =
sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName );
if ( sourceReadAccessor != null ) {
@ -749,7 +833,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
ForgedMethodHistory history = forgedMethod.getHistory();
ctx.getMessager().printMessage(
this.method.getExecutable(),
Message.PROPERTYMAPPING_MAPPING_NOT_FOUND,
Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND,
history.createSourcePropertyErrorMessage(),
history.getTargetType(),
history.createTargetPropertyName(),
@ -834,7 +918,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
List<PropertyMapping> propertyMappings,
MethodReference factoryMethod,
boolean mapNullToDefault,
Type resultType,
Type returnTypeToConstruct,
BuilderType returnTypeBuilder,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences,
MethodReference finalizerMethod) {
@ -848,6 +933,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
this.propertyMappings = propertyMappings;
this.returnTypeBuilder = returnTypeBuilder;
this.finalizerMethod = finalizerMethod;
// intialize constant mappings as all mappings, but take out the ones that can be contributed to a
@ -864,11 +950,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
}
this.resultType = resultType;
}
public List<PropertyMapping> getPropertyMappings() {
return propertyMappings;
this.returnTypeToConstruct = returnTypeToConstruct;
}
public List<PropertyMapping> getConstantMappings() {
@ -880,14 +962,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return mappingsByParameter.get( parameter.getName() );
}
@Override
public Type getResultType() {
if ( resultType == null ) {
return super.getResultType();
}
else {
return resultType;
}
public Type getReturnTypeToConstruct() {
return returnTypeToConstruct;
}
public MethodReference getFinalizerMethod() {
@ -902,12 +978,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
types.addAll( propertyMapping.getImportTypes() );
}
if ( !isExistingInstanceMapping() ) {
types.addAll( getResultType().getEffectiveType().getImportTypes() );
if ( returnTypeToConstruct != null ) {
types.addAll( returnTypeToConstruct.getImportTypes() );
}
if ( getResultType().getBuilderType() != null ) {
types.add( getResultType().getBuilderType().getOwningType() );
if ( returnTypeBuilder != null ) {
types.add( returnTypeBuilder.getOwningType() );
}
return types;

View File

@ -12,7 +12,6 @@ import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.source.BeanMapping;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
@ -36,7 +35,7 @@ public class BuilderFinisherMethodResolver {
return null;
}
BuilderPrism builderMapping = builderMappingPrism( method, ctx );
BuilderPrism builderMapping = BeanMapping.builderPrismFor( method );
if ( builderMapping == null && buildMethods.size() == 1 ) {
return MethodReference.forMethodCall( first( buildMethods ).getSimpleName().toString() );
}
@ -77,12 +76,4 @@ public class BuilderFinisherMethodResolver {
return null;
}
private static BuilderPrism builderMappingPrism(Method method, MappingBuilderContext ctx) {
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
if ( beanMapping != null && beanMapping.getBuilder() != null ) {
return beanMapping.getBuilder();
}
return MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ).getBuilderPrism();
}
}

View File

@ -20,6 +20,7 @@ import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT;
import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL;
@ -57,7 +58,7 @@ public class CollectionAssignmentBuilder {
private Accessor targetReadAccessor;
private Type targetType;
private String targetPropertyName;
private PropertyMapping.TargetWriteAccessorType targetAccessorType;
private AccessorType targetAccessorType;
private Assignment assignment;
private SourceRHS sourceRHS;
private NullValueCheckStrategyPrism nvcs;
@ -88,7 +89,7 @@ public class CollectionAssignmentBuilder {
return this;
}
public CollectionAssignmentBuilder targetAccessorType(PropertyMapping.TargetWriteAccessorType targetAccessorType) {
public CollectionAssignmentBuilder targetAccessorType(AccessorType targetAccessorType) {
this.targetAccessorType = targetAccessorType;
return this;
}
@ -129,8 +130,7 @@ public class CollectionAssignmentBuilder {
CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy();
boolean targetImmutable = cms == CollectionMappingStrategyPrism.TARGET_IMMUTABLE || targetReadAccessor == null;
if ( targetAccessorType == PropertyMapping.TargetWriteAccessorType.SETTER ||
targetAccessorType == PropertyMapping.TargetWriteAccessorType.FIELD ) {
if ( targetAccessorType == AccessorType.SETTER || targetAccessorType == AccessorType.FIELD ) {
if ( result.isCallingUpdateMethod() && !targetImmutable ) {
@ -149,7 +149,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
factoryMethod,
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ),
targetAccessorType == AccessorType.FIELD,
targetType,
true,
nvpms == SET_TO_NULL && !targetType.isPrimitive(),
@ -165,7 +165,7 @@ public class CollectionAssignmentBuilder {
nvcs,
nvpms,
ctx.getTypeFactory(),
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType )
targetAccessorType == AccessorType.FIELD
);
}
else if ( result.getType() == Assignment.AssignmentType.DIRECT ||
@ -176,7 +176,7 @@ public class CollectionAssignmentBuilder {
method.getThrownTypes(),
targetType,
ctx.getTypeFactory(),
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType )
targetAccessorType == AccessorType.FIELD
);
}
else {
@ -185,7 +185,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
targetType,
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType )
targetAccessorType == AccessorType.FIELD
);
}
}
@ -203,7 +203,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
targetType,
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType )
targetAccessorType == AccessorType.FIELD
);
}

View File

@ -127,7 +127,7 @@ public abstract class ContainerMappingMethodBuilder<B extends ContainerMappingMe
MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) {
factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, method.getResultType(), null, ctx );
factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, null, ctx );
}
Set<String> existingVariables = new HashSet<>( method.getParameterNames() );

View File

@ -10,7 +10,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.internal.model.common.Accessibility;

View File

@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
* Model element that can be used to create a type of {@link Iterable} or {@link java.util.Map}. If an implementation
* type is used and the target type has a constructor with {@code int} as parameter and the source parameter is of
@ -69,6 +71,20 @@ public class IterableCreation extends ModelElement {
if ( factoryMethod == null && resultType.getImplementationType() != null ) {
types.addAll( resultType.getImplementationType().getImportTypes() );
}
if ( isEnumSet() ) {
types.add( getEnumSetElementType() );
// The result type itself is an EnumSet
types.add( resultType );
}
return types;
}
public Type getEnumSetElementType() {
return first( getResultType().determineTypeArguments( Iterable.class ) );
}
public boolean isEnumSet() {
return "java.util.EnumSet".equals( resultType.getFullyQualifiedName() );
}
}

View File

@ -30,6 +30,48 @@ public final class LifecycleMethodResolver {
private LifecycleMethodResolver() {
}
/**
* @param method the method to obtain the beforeMapping methods for
* @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract
* @param selectionParameters method selectionParameters
* @param ctx the builder context
* @param existingVariableNames the existing variable names in the mapping method
* @return all applicable {@code @BeforeMapping} methods for the given method
*/
public static List<LifecycleCallbackMethodReference> beforeMappingMethods(Method method,
Type alternativeTarget,
SelectionParameters selectionParameters,
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( method,
alternativeTarget,
selectionParameters,
filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
existingVariableNames );
}
/**
* @param method the method to obtain the afterMapping methods for
* @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract
* @param selectionParameters method selectionParameters
* @param ctx the builder context
* @param existingVariableNames list of already used variable names
* @return all applicable {@code @AfterMapping} methods for the given method
*/
public static List<LifecycleCallbackMethodReference> afterMappingMethods(Method method,
Type alternativeTarget,
SelectionParameters selectionParameters,
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( method,
alternativeTarget,
selectionParameters,
filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
existingVariableNames );
}
/**
* @param method the method to obtain the beforeMapping methods for
* @param selectionParameters method selectionParameters
@ -42,6 +84,7 @@ public final class LifecycleMethodResolver {
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( method,
method.getResultType(),
selectionParameters,
filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
@ -60,6 +103,7 @@ public final class LifecycleMethodResolver {
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( method,
method.getResultType(),
selectionParameters,
filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
@ -87,17 +131,11 @@ public final class LifecycleMethodResolver {
}
private static List<LifecycleCallbackMethodReference> collectLifecycleCallbackMethods(
Method method, SelectionParameters selectionParameters, List<SourceMethod> callbackMethods,
Method method, Type targetType, SelectionParameters selectionParameters, List<SourceMethod> callbackMethods,
MappingBuilderContext ctx, Set<String> existingVariableNames) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory() );
Type targetType = method.getResultType();
if ( !method.isUpdateMethod() ) {
targetType = targetType.getEffectiveType();
}
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
List<SelectedMethod<SourceMethod>> matchingMethods = selectors.getMatchingMethods(
method,

View File

@ -181,10 +181,9 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) {
factoryMethod = ObjectFactoryMethodResolver
.getFactoryMethod( method, method.getResultType(), null, ctx );
.getFactoryMethod( method, null, ctx );
}
keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false );
valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes(), valueTargetType, false );

View File

@ -155,7 +155,7 @@ public class MethodReference extends ModelElement implements Assignment {
@Override
public String getSourceReference() {
return assignment.getSourceReference();
return assignment != null ? assignment.getSourceReference() : null;
}
@Override

View File

@ -160,6 +160,16 @@ public class NestedTargetPropertyMappingHolder {
groupedByTP.singleTargetReferences.get( targetProperty )
);
// We need an update method in the when one of the following is satisfied:
// 1) Multiple source parameters for the target reference
// 2) Multiple source references for the target reference
// The reason for this is that multiple sources have effect on the target.
// See Issue1828Test for more info.
boolean forceUpdateMethod =
multipleSourceParametersForTP || groupedSourceReferences.groupedBySourceReferences.size() > 1;
boolean forceUpdateMethodOrNonNestedReferencesPresent =
forceUpdateMethod || !groupedSourceReferences.nonNested.isEmpty();
// For all the groupedBySourceReferences we need to create property mappings
// from the Mappings and not restrict on the defined mappings (allow to forge name based mapping)
// if we have composite methods i.e. more then 2 parameters then we have to force a creation
@ -168,8 +178,6 @@ public class NestedTargetPropertyMappingHolder {
.groupedBySourceReferences
.entrySet() ) {
PropertyEntry sourceEntry = entryBySP.getKey();
boolean forceUpdateMethodOrNonNestedReferencesPresent =
multipleSourceParametersForTP || !groupedSourceReferences.nonNested.isEmpty();
// If there are multiple source parameters that are mapped to the target reference
// then we restrict the mapping only to the defined mappings. And we create MappingOptions
// for forged methods (which means that any unmapped target properties are ignored)

View File

@ -5,11 +5,8 @@
*/
package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@ -26,6 +23,8 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
*
* @author Sjaak Derksen
@ -39,7 +38,6 @@ public class ObjectFactoryMethodResolver {
* returns a no arg factory method
*
* @param method target mapping method
* @param targetType return type to match
* @param selectionParameters parameters used in the selection process
* @param ctx
*
@ -47,30 +45,49 @@ public class ObjectFactoryMethodResolver {
*
*/
public static MethodReference getFactoryMethod( Method method,
Type targetType,
SelectionParameters selectionParameters,
MappingBuilderContext ctx) {
return getFactoryMethod( method, method.getResultType(), selectionParameters, ctx );
}
/**
* returns a no arg factory method
*
* @param method target mapping method
* @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract
* @param selectionParameters parameters used in the selection process
* @param ctx
*
* @return a method reference to the factory method, or null if no suitable, or ambiguous method found
*
*/
public static MethodReference getFactoryMethod( Method method,
Type alternativeTarget,
SelectionParameters selectionParameters,
MappingBuilderContext ctx) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory() );
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() );
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
selectors.getMatchingMethods(
method,
getAllAvailableMethods( method, ctx.getSourceModel() ),
java.util.Collections.<Type> emptyList(),
targetType.getEffectiveType(),
alternativeTarget,
SelectionCriteria.forFactoryMethods( selectionParameters ) );
if (matchingFactoryMethods.isEmpty()) {
return findBuilderFactoryMethod( targetType );
return null;
}
if ( matchingFactoryMethods.size() > 1 ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
targetType.getEffectiveType(),
alternativeTarget,
Strings.join( matchingFactoryMethods, ", " ) );
return null;
@ -99,8 +116,7 @@ public class ObjectFactoryMethodResolver {
}
}
private static MethodReference findBuilderFactoryMethod(Type targetType) {
BuilderType builder = targetType.getBuilderType();
public static MethodReference getBuilderFactoryMethod(Method method, BuilderType builder) {
if ( builder == null ) {
return null;
}
@ -111,7 +127,7 @@ public class ObjectFactoryMethodResolver {
return null;
}
if ( !builder.getBuildingType().isAssignableTo( targetType ) ) {
if ( !builder.getBuildingType().isAssignableTo( method.getReturnType() ) ) {
//TODO print error message
return null;
}

View File

@ -12,7 +12,6 @@ import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import org.mapstruct.ap.internal.model.assignment.AdderWrapper;
import org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper;
@ -22,6 +21,7 @@ import org.mapstruct.ap.internal.model.assignment.SetterWrapper;
import org.mapstruct.ap.internal.model.assignment.StreamAdderWrapper;
import org.mapstruct.ap.internal.model.assignment.UpdateWrapper;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Parameter;
@ -36,17 +36,17 @@ import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
import org.mapstruct.ap.internal.model.source.PropertyEntry;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceReference;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism;
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.NativeTypes;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.ValueProvider;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT;
@ -72,38 +72,13 @@ public class PropertyMapping extends ModelElement {
private final List<String> dependsOn;
private final Assignment defaultValueAssignment;
public enum TargetWriteAccessorType {
FIELD,
GETTER,
SETTER,
ADDER;
public static TargetWriteAccessorType of(AccessorNamingUtils accessorNaming, Accessor accessor) {
if ( accessorNaming.isSetterMethod( accessor ) ) {
return TargetWriteAccessorType.SETTER;
}
else if ( accessorNaming.isAdderMethod( accessor ) ) {
return TargetWriteAccessorType.ADDER;
}
else if ( accessorNaming.isGetterMethod( accessor ) ) {
return TargetWriteAccessorType.GETTER;
}
else {
return TargetWriteAccessorType.FIELD;
}
}
public static boolean isFieldAssignment(TargetWriteAccessorType accessorType) {
return accessorType == FIELD;
}
}
@SuppressWarnings("unchecked")
private static class MappingBuilderBase<T extends MappingBuilderBase<T>> extends AbstractBaseBuilder<T> {
protected Accessor targetWriteAccessor;
protected TargetWriteAccessorType targetWriteAccessorType;
protected AccessorType targetWriteAccessorType;
protected Type targetType;
protected BuilderType targetBuilderType;
protected Accessor targetReadAccessor;
protected String targetPropertyName;
protected String sourcePropertyName;
@ -124,7 +99,8 @@ public class PropertyMapping extends ModelElement {
this.targetReadAccessor = targetProp.getReadAccessor();
this.targetWriteAccessor = targetProp.getWriteAccessor();
this.targetType = targetProp.getType();
this.targetWriteAccessorType = TargetWriteAccessorType.of( ctx.getAccessorNaming(), targetWriteAccessor );
this.targetBuilderType = targetProp.getBuilderType();
this.targetWriteAccessorType = targetWriteAccessor.getAccessorType();
return (T) this;
}
@ -135,8 +111,10 @@ public class PropertyMapping extends ModelElement {
public T targetWriteAccessor(Accessor targetWriteAccessor) {
this.targetWriteAccessor = targetWriteAccessor;
this.targetWriteAccessorType = TargetWriteAccessorType.of( ctx.getAccessorNaming(), targetWriteAccessor );
this.targetType = determineTargetType();
this.targetType = ctx.getTypeFactory().getType( targetWriteAccessor.getAccessedType() );
BuilderPrism builderPrism = BeanMapping.builderPrismFor( method );
this.targetBuilderType = ctx.getTypeFactory().builderTypeFor( this.targetType, builderPrism );
this.targetWriteAccessorType = targetWriteAccessor.getAccessorType();
return (T) this;
}
@ -146,28 +124,6 @@ public class PropertyMapping extends ModelElement {
return (T) this;
}
private Type determineTargetType() {
// This is a bean mapping method, so we know the result is a declared type
Type mappingType = method.getResultType();
if ( !method.isUpdateMethod() ) {
mappingType = mappingType.getEffectiveType();
}
DeclaredType resultType = (DeclaredType) mappingType.getTypeMirror();
switch ( targetWriteAccessorType ) {
case ADDER:
case SETTER:
return ctx.getTypeFactory()
.getSingleParameter( resultType, targetWriteAccessor )
.getType();
case GETTER:
case FIELD:
default:
return ctx.getTypeFactory()
.getReturnType( resultType, targetWriteAccessor );
}
}
public T targetPropertyName(String targetPropertyName) {
this.targetPropertyName = targetPropertyName;
return (T) this;
@ -189,7 +145,7 @@ public class PropertyMapping extends ModelElement {
}
protected boolean isFieldAssignment() {
return targetWriteAccessorType == TargetWriteAccessorType.FIELD;
return targetWriteAccessorType == AccessorType.FIELD;
}
}
@ -296,11 +252,11 @@ public class PropertyMapping extends ModelElement {
// handle source
this.rightHandSide = getSourceRHS( sourceReference );
rightHandSide.setUseElementAsSourceTypeForMatching(
targetWriteAccessorType == TargetWriteAccessorType.ADDER );
targetWriteAccessorType == AccessorType.ADDER );
// all the tricky cases will be excluded for the time being.
boolean preferUpdateMethods;
if ( targetWriteAccessorType == TargetWriteAccessorType.ADDER ) {
if ( targetWriteAccessorType == AccessorType.ADDER ) {
preferUpdateMethods = false;
}
else {
@ -433,13 +389,12 @@ public class PropertyMapping extends ModelElement {
return null;
}
private Assignment assignToPlain(Type targetType, TargetWriteAccessorType targetAccessorType,
private Assignment assignToPlain(Type targetType, AccessorType targetAccessorType,
Assignment rightHandSide) {
Assignment result;
if ( targetAccessorType == TargetWriteAccessorType.SETTER ||
targetAccessorType == TargetWriteAccessorType.FIELD ) {
if ( targetAccessorType == AccessorType.SETTER || targetAccessorType == AccessorType.FIELD ) {
result = assignToPlainViaSetter( targetType, rightHandSide );
}
else {
@ -517,7 +472,7 @@ public class PropertyMapping extends ModelElement {
return result;
}
private Assignment assignToCollection(Type targetType, TargetWriteAccessorType targetAccessorType,
private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType,
Assignment rhs) {
return new CollectionAssignmentBuilder()
.mappingBuilderContext( ctx )
@ -763,7 +718,7 @@ public class PropertyMapping extends ModelElement {
forgeMethodWithMappingOptions,
forgedNamedBased
);
return createForgedAssignment( sourceRHS, forgedMethod );
return createForgedAssignment( sourceRHS, targetBuilderType, forgedMethod );
}
private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS) {
@ -876,8 +831,8 @@ public class PropertyMapping extends ModelElement {
if ( assignment != null ) {
if ( ctx.getAccessorNaming().isSetterMethod( targetWriteAccessor ) ||
Executables.isFieldAccessor( targetWriteAccessor ) ) {
if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER ||
targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) {
// target accessor is setter, so decorate assignment as setter
if ( assignment.isCallingUpdateMethod() ) {
@ -992,8 +947,8 @@ public class PropertyMapping extends ModelElement {
public PropertyMapping build() {
Assignment assignment = new SourceRHS( javaExpression, null, existingVariableNames, "" );
if ( ctx.getAccessorNaming().isSetterMethod( targetWriteAccessor ) ||
Executables.isFieldAccessor( targetWriteAccessor ) ) {
if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER ||
targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) {
// setter, so wrap in setter
assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() );
}

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.internal.model.common;
import java.util.Collection;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
@ -82,13 +83,6 @@ public class BuilderType {
return buildMethods;
}
public BuilderInfo asBuilderInfo() {
return new BuilderInfo.Builder()
.builderCreationMethod( this.builderCreationMethod )
.buildMethod( this.buildMethods )
.build();
}
public static BuilderType create(BuilderInfo builderInfo, Type typeToBuild, TypeFactory typeFactory,
Types typeUtils) {
if ( builderInfo == null ) {
@ -109,6 +103,11 @@ public class BuilderType {
owner = typeFactory.getType( builderCreationOwner );
}
// When the builderCreationMethod is constructor, its return type is Void. In this case the
// builder type should be the owner type.
if (builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR) {
builder = owner;
}
return new BuilderType(
builder,

View File

@ -34,12 +34,12 @@ import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.Fields;
import org.mapstruct.ap.internal.util.Filters;
import org.mapstruct.ap.internal.util.JavaStreamConstants;
import org.mapstruct.ap.internal.util.Nouns;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
import org.mapstruct.ap.spi.BuilderInfo;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import static org.mapstruct.ap.internal.util.Collections.first;
import org.mapstruct.ap.internal.util.NativeTypes;
@ -67,7 +67,6 @@ public class Type extends ModelElement implements Comparable<Type> {
private final ImplementationType implementationType;
private final Type componentType;
private final BuilderType builderType;
private final String packageName;
private final String name;
@ -89,9 +88,11 @@ public class Type extends ModelElement implements Comparable<Type> {
private Boolean isToBeImported;
private Map<String, Accessor> readAccessors = null;
private Map<String, ExecutableElementAccessor> presenceCheckers = null;
private Map<String, Accessor> presenceCheckers = null;
private List<ExecutableElement> allMethods = null;
private List<VariableElement> allFields = null;
private List<Accessor> allAccessors = null;
private List<Accessor> setters = null;
private List<Accessor> adders = null;
private List<Accessor> alternativeTargetAccessors = null;
@ -100,12 +101,13 @@ public class Type extends ModelElement implements Comparable<Type> {
private Boolean hasEmptyAccessibleContructor;
private final Filters filters;
//CHECKSTYLE:OFF
public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory,
AccessorNamingUtils accessorNaming,
TypeMirror typeMirror, TypeElement typeElement,
List<Type> typeParameters, ImplementationType implementationType, Type componentType,
BuilderInfo builderInfo,
String packageName, String name, String qualifiedName,
boolean isInterface, boolean isEnumType, boolean isIterableType,
boolean isCollectionType, boolean isMapType, boolean isStreamType,
@ -157,7 +159,7 @@ public class Type extends ModelElement implements Comparable<Type> {
this.isToBeImported = isToBeImported;
this.toBeImportedTypes = toBeImportedTypes;
this.notToBeImportedTypes = notToBeImportedTypes;
this.builderType = BuilderType.create( builderInfo, this, this.typeFactory, this.typeUtils );
this.filters = new Filters( accessorNaming, typeUtils, typeMirror );
}
//CHECKSTYLE:ON
@ -200,18 +202,6 @@ public class Type extends ModelElement implements Comparable<Type> {
return componentType;
}
public BuilderType getBuilderType() {
return builderType;
}
/**
* The effective type that should be used when searching for getters / setters, creating new types etc
* @return the effective type for mappings
*/
public Type getEffectiveType() {
return builderType != null ? builderType.getBuilder() : this;
}
public boolean isPrimitive() {
return typeMirror.getKind().isPrimitive();
}
@ -419,7 +409,6 @@ public class Type extends ModelElement implements Comparable<Type> {
typeParameters,
implementationType,
componentType,
builderType == null ? null : builderType.asBuilderInfo(),
packageName,
name,
qualifiedName,
@ -462,7 +451,6 @@ public class Type extends ModelElement implements Comparable<Type> {
bounds,
implementationType,
componentType,
builderType == null ? null : builderType.asBuilderInfo(),
packageName,
name,
qualifiedName,
@ -505,26 +493,26 @@ public class Type extends ModelElement implements Comparable<Type> {
*/
public Map<String, Accessor> getPropertyReadAccessors() {
if ( readAccessors == null ) {
List<Accessor> getterList = Filters.getterMethodsIn( accessorNaming, getAllAccessors() );
List<Accessor> getterList = filters.getterMethodsIn( getAllMethods() );
Map<String, Accessor> modifiableGetters = new LinkedHashMap<>();
for ( Accessor getter : getterList ) {
String propertyName = accessorNaming.getPropertyName( getter );
String propertyName = getPropertyName( getter );
if ( modifiableGetters.containsKey( propertyName ) ) {
// In the DefaultAccessorNamingStrategy, this can only be the case for Booleans: isFoo() and
// getFoo(); The latter is preferred.
if ( !getter.getSimpleName().toString().startsWith( "is" ) ) {
modifiableGetters.put( accessorNaming.getPropertyName( getter ), getter );
modifiableGetters.put( getPropertyName( getter ), getter );
}
}
else {
modifiableGetters.put( accessorNaming.getPropertyName( getter ), getter );
modifiableGetters.put( getPropertyName( getter ), getter );
}
}
List<Accessor> fieldsList = Filters.fieldsIn( getAllAccessors() );
List<Accessor> fieldsList = filters.fieldsIn( getAllFields() );
for ( Accessor field : fieldsList ) {
String propertyName = accessorNaming.getPropertyName( field );
String propertyName = getPropertyName( field );
if ( !modifiableGetters.containsKey( propertyName ) ) {
// If there was no getter or is method for booleans, then resort to the field.
// If a field was already added do not add it again.
@ -541,15 +529,12 @@ public class Type extends ModelElement implements Comparable<Type> {
*
* @return an unmodifiable map of all presence checkers, indexed by property name
*/
public Map<String, ExecutableElementAccessor> getPropertyPresenceCheckers() {
public Map<String, Accessor> getPropertyPresenceCheckers() {
if ( presenceCheckers == null ) {
List<ExecutableElementAccessor> checkerList = Filters.presenceCheckMethodsIn(
accessorNaming,
getAllAccessors()
);
Map<String, ExecutableElementAccessor> modifiableCheckers = new LinkedHashMap<>();
for ( ExecutableElementAccessor checker : checkerList ) {
modifiableCheckers.put( accessorNaming.getPropertyName( checker ), checker );
List<Accessor> checkerList = filters.presenceCheckMethodsIn( getAllMethods() );
Map<String, Accessor> modifiableCheckers = new LinkedHashMap<>();
for ( Accessor checker : checkerList ) {
modifiableCheckers.put( getPropertyName( checker ), checker );
}
presenceCheckers = Collections.unmodifiableMap( modifiableCheckers );
}
@ -577,7 +562,7 @@ public class Type extends ModelElement implements Comparable<Type> {
Map<String, Accessor> result = new LinkedHashMap<>();
for ( Accessor candidate : candidates ) {
String targetPropertyName = accessorNaming.getPropertyName( candidate );
String targetPropertyName = getPropertyName( candidate );
Accessor readAccessor = getPropertyReadAccessors().get( targetPropertyName );
@ -593,12 +578,12 @@ public class Type extends ModelElement implements Comparable<Type> {
// first check if there's a setter method.
Accessor adderMethod = null;
if ( accessorNaming.isSetterMethod( candidate )
if ( candidate.getAccessorType() == AccessorType.SETTER
// ok, the current accessor is a setter. So now the strategy determines what to use
&& cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) {
adderMethod = getAdderForType( targetType, targetPropertyName );
}
else if ( accessorNaming.isGetterMethod( candidate ) ) {
else if ( candidate.getAccessorType() == AccessorType.GETTER ) {
// the current accessor is a getter (no setter available). But still, an add method is according
// to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter.
adderMethod = getAdderForType( targetType, targetPropertyName );
@ -608,7 +593,7 @@ public class Type extends ModelElement implements Comparable<Type> {
candidate = adderMethod;
}
}
else if ( Executables.isFieldAccessor( candidate ) && ( Executables.isFinal( candidate ) ||
else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) ||
result.containsKey( targetPropertyName ) ) ) {
// if the candidate is a field and a mapping already exists, then use that one, skip it.
continue;
@ -636,18 +621,36 @@ public class Type extends ModelElement implements Comparable<Type> {
if ( parameter != null ) {
return parameter.getType();
}
else if ( accessorNaming.isGetterMethod( candidate ) || Executables.isFieldAccessor( candidate ) ) {
else if ( candidate.getAccessorType() == AccessorType.GETTER ||
candidate.getAccessorType() == AccessorType.FIELD ) {
return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate );
}
return null;
}
private List<Accessor> getAllAccessors() {
if ( allAccessors == null ) {
allAccessors = Executables.getAllEnclosedAccessors( elementUtils, typeElement );
private List<ExecutableElement> getAllMethods() {
if ( allMethods == null ) {
allMethods = Executables.getAllEnclosedExecutableElements( elementUtils, typeElement );
}
return allAccessors;
return allMethods;
}
private List<VariableElement> getAllFields() {
if ( allFields == null ) {
allFields = Fields.getAllEnclosedFields( elementUtils, typeElement );
}
return allFields;
}
private String getPropertyName(Accessor accessor ) {
if ( accessor.getAccessorType() == AccessorType.FIELD ) {
return accessorNaming.getPropertyName( (VariableElement) accessor.getElement() );
}
else {
return accessorNaming.getPropertyName( (ExecutableElement) accessor.getElement() );
}
}
/**
@ -709,19 +712,14 @@ public class Type extends ModelElement implements Comparable<Type> {
* @return accessor candidates
*/
private List<Accessor> getAccessorCandidates(Type property, Class<?> superclass) {
TypeMirror typeArg = first( property.determineTypeArguments( superclass ) ).getTypeBound()
.getTypeMirror();
TypeMirror typeArg = first( property.determineTypeArguments( superclass ) ).getTypeBound().getTypeMirror();
// now, look for a method that
// 1) starts with add,
// 2) and has typeArg as one and only arg
List<Accessor> adderList = getAdders();
List<Accessor> candidateList = new ArrayList<>();
for ( Accessor adder : adderList ) {
ExecutableElement executable = adder.getExecutable();
if ( executable == null ) {
// it should not be null, but to be safe
continue;
}
ExecutableElement executable = (ExecutableElement) adder.getElement();
VariableElement arg = executable.getParameters().get( 0 );
if ( typeUtils.isSameType( boxed( arg.asType() ), boxed( typeArg ) ) ) {
candidateList.add( adder );
@ -746,7 +744,7 @@ public class Type extends ModelElement implements Comparable<Type> {
*/
private List<Accessor> getSetters() {
if ( setters == null ) {
setters = Collections.unmodifiableList( Filters.setterMethodsIn( accessorNaming, getAllAccessors() ) );
setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) );
}
return setters;
}
@ -761,7 +759,7 @@ public class Type extends ModelElement implements Comparable<Type> {
*/
private List<Accessor> getAdders() {
if ( adders == null ) {
adders = Collections.unmodifiableList( Filters.adderMethodsIn( accessorNaming, getAllAccessors() ) );
adders = Collections.unmodifiableList( filters.adderMethodsIn( getAllMethods() ) );
}
return adders;
}
@ -781,10 +779,9 @@ public class Type extends ModelElement implements Comparable<Type> {
List<Accessor> result = new ArrayList<>();
List<Accessor> setterMethods = getSetters();
List<Accessor> readAccessors =
new ArrayList<>( getPropertyReadAccessors().values() );
List<Accessor> readAccessors = new ArrayList<>( getPropertyReadAccessors().values() );
// All the fields are also alternative accessors
readAccessors.addAll( Filters.fieldsIn( getAllAccessors() ) );
readAccessors.addAll( filters.fieldsIn( getAllFields() ) );
// there could be a read accessor (field or method) for a list/map that is not present as setter.
// an accessor could substitute the setter in that case and act as setter.
@ -794,7 +791,7 @@ public class Type extends ModelElement implements Comparable<Type> {
!correspondingSetterMethodExists( readAccessor, setterMethods ) ) {
result.add( readAccessor );
}
else if ( Executables.isFieldAccessor( readAccessor ) &&
else if ( readAccessor.getAccessorType() == AccessorType.FIELD &&
!correspondingSetterMethodExists( readAccessor, setterMethods ) ) {
result.add( readAccessor );
}
@ -807,10 +804,10 @@ public class Type extends ModelElement implements Comparable<Type> {
private boolean correspondingSetterMethodExists(Accessor getterMethod,
List<Accessor> setterMethods) {
String getterPropertyName = accessorNaming.getPropertyName( getterMethod );
String getterPropertyName = getPropertyName( getterMethod );
for ( Accessor setterMethod : setterMethods ) {
String setterPropertyName = accessorNaming.getPropertyName( setterMethod );
String setterPropertyName = getPropertyName( setterMethod );
if ( getterPropertyName.equals( setterPropertyName ) ) {
return true;
}

View File

@ -39,6 +39,7 @@ import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Collections;
import org.mapstruct.ap.internal.util.Extractor;
@ -49,6 +50,7 @@ import org.mapstruct.ap.internal.util.NativeTypes;
import org.mapstruct.ap.internal.util.RoundContext;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
import org.mapstruct.ap.spi.BuilderInfo;
import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException;
@ -186,7 +188,6 @@ public class TypeFactory {
}
ImplementationType implementationType = getImplementationType( mirror );
BuilderInfo builderInfo = findBuilder( mirror );
boolean isIterableType = typeUtils.isSubtype( mirror, iterableType );
boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType );
@ -280,7 +281,6 @@ public class TypeFactory {
getTypeParameters( mirror, false ),
implementationType,
componentType,
builderInfo,
packageName,
name,
qualifiedName,
@ -354,10 +354,10 @@ public class TypeFactory {
}
public Parameter getSingleParameter(DeclaredType includingType, Accessor method) {
ExecutableElement executable = method.getExecutable();
if ( executable == null ) {
if ( method.getAccessorType() == AccessorType.FIELD ) {
return null;
}
ExecutableElement executable = (ExecutableElement) method.getElement();
List<? extends VariableElement> parameters = executable.getParameters();
if ( parameters.size() != 1 ) {
@ -369,7 +369,7 @@ public class TypeFactory {
}
public List<Parameter> getParameters(DeclaredType includingType, Accessor accessor) {
ExecutableElement method = accessor.getExecutable();
ExecutableElement method = (ExecutableElement) accessor.getElement();
TypeMirror methodType = getMethodType( includingType, accessor.getElement() );
if ( method == null || methodType.getKind() != TypeKind.EXECUTABLE ) {
return new ArrayList<>();
@ -427,10 +427,10 @@ public class TypeFactory {
}
public List<Type> getThrownTypes(Accessor accessor) {
if (accessor.getExecutable() == null) {
if (accessor.getAccessorType() == AccessorType.FIELD) {
return new ArrayList<>();
}
return extractTypes( accessor.getExecutable().getThrownTypes() );
return extractTypes( ( (ExecutableElement) accessor.getElement() ).getThrownTypes() );
}
private List<Type> extractTypes(List<? extends TypeMirror> typeMirrors) {
@ -502,7 +502,6 @@ public class TypeFactory {
getTypeParameters( mirror, true ),
null,
null,
null,
implementationType.getPackageName(),
implementationType.getName(),
implementationType.getFullyQualifiedName(),
@ -523,13 +522,17 @@ public class TypeFactory {
return null;
}
private BuilderInfo findBuilder(TypeMirror type) {
private BuilderInfo findBuilder(TypeMirror type, BuilderPrism builderPrism, boolean report) {
if ( builderPrism != null && builderPrism.disableBuilder() ) {
return null;
}
try {
return roundContext.getAnnotationProcessorContext()
.getBuilderProvider()
.findBuilderInfo( type );
}
catch ( MoreThanOneBuilderCreationMethodException ex ) {
if ( report ) {
messager.printMessage(
typeUtils.asElement( type ),
Message.BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD,
@ -537,6 +540,7 @@ public class TypeFactory {
Strings.join( ex.getBuilderInfo(), ", ", BUILDER_INFO_CREATION_METHOD_EXTRACTOR )
);
}
}
return null;
}
@ -663,4 +667,21 @@ public class TypeFactory {
return true;
}
public BuilderType builderTypeFor( Type type, BuilderPrism builderPrism ) {
if ( type != null ) {
BuilderInfo builderInfo = findBuilder( type.getTypeMirror(), builderPrism, true );
return BuilderType.create( builderInfo, type, this, this.typeUtils );
}
return null;
}
public Type effectiveResultTypeFor( Type type, BuilderPrism builderPrism ) {
if ( type != null ) {
BuilderInfo builderInfo = findBuilder( type.getTypeMirror(), builderPrism, false );
BuilderType builderType = BuilderType.create( builderInfo, type, this, this.typeUtils );
return builderType != null ? builderType.getBuilder() : type;
}
return type;
}
}

View File

@ -44,7 +44,7 @@ public class BeanMapping {
*/
public static BeanMapping forInheritance( BeanMapping map ) {
return new BeanMapping(
map.selectionParameters,
SelectionParameters.forInheritance( map.selectionParameters ),
map.nullValueMappingStrategy,
map.nullValuePropertyMappingStrategy,
map.nullValueCheckStrategy,
@ -177,4 +177,21 @@ public class BeanMapping {
public BuilderPrism getBuilder() {
return builder;
}
/**
* derives the builder prism given the options and configuration
* @param method containing mandatory configuration and the mapping options (optionally containing a beanmapping)
* @return a BuilderPrism
*/
public static BuilderPrism builderPrismFor(Method method) {
BuilderPrism beanMappingBuilderPrism;
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
if ( beanMapping != null ) {
beanMappingBuilderPrism = beanMapping.builder;
}
else {
beanMappingBuilderPrism = null;
}
return method.getMapperConfiguration().getBuilderPrism( beanMappingBuilderPrism );
}
}

View File

@ -291,7 +291,10 @@ public class MappingOptions {
CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy();
Type writeType = method.getResultType();
if ( !method.isUpdateMethod() ) {
writeType = writeType.getEffectiveType();
writeType = typeFactory.effectiveResultTypeFor(
writeType,
BeanMapping.builderPrismFor( method )
);
}
Map<String, Accessor> writeAccessors = writeType.getPropertyWriteAccessors( cms );
List<String> mappedPropertyNames = new ArrayList<>();

View File

@ -129,7 +129,6 @@ public interface Method {
*/
Type getResultType();
/**
*
* @return the names of the parameters of this mapping method
@ -187,4 +186,13 @@ public interface Method {
* @return the mapping options for this method
*/
MappingOptions getMappingOptions();
/**
*
* @return true when @MappingTarget annotated parameter is the same type as the return type. The method has
* to be an update method in order for this to be true.
*/
default boolean isMappingTargetAssignableToReturnType() {
return isUpdateMethod() ? getResultType().isAssignableTo( getReturnType() ) : false;
}
}

View File

@ -7,10 +7,10 @@ package org.mapstruct.ap.internal.model.source;
import java.util.Arrays;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
/**
@ -22,8 +22,9 @@ public class PropertyEntry {
private final String[] fullName;
private final Accessor readAccessor;
private final Accessor writeAccessor;
private final ExecutableElementAccessor presenceChecker;
private final Accessor presenceChecker;
private final Type type;
private final BuilderType builderType;
/**
* Constructor used to create {@link TargetReference} property entries from a mapping
@ -34,12 +35,13 @@ public class PropertyEntry {
* @param type
*/
private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor writeAccessor,
ExecutableElementAccessor presenceChecker, Type type) {
Accessor presenceChecker, Type type, BuilderType builderType) {
this.fullName = fullName;
this.readAccessor = readAccessor;
this.writeAccessor = writeAccessor;
this.presenceChecker = presenceChecker;
this.type = type;
this.builderType = builderType;
}
/**
@ -49,11 +51,12 @@ public class PropertyEntry {
* @param readAccessor its read accessor
* @param writeAccessor its write accessor
* @param type type of the property
* @param builderType the builder for the type
* @return the property entry for given parameters.
*/
public static PropertyEntry forTargetReference(String[] fullName, Accessor readAccessor,
Accessor writeAccessor, Type type) {
return new PropertyEntry( fullName, readAccessor, writeAccessor, null, type );
Accessor writeAccessor, Type type, BuilderType builderType) {
return new PropertyEntry( fullName, readAccessor, writeAccessor, null, type, builderType );
}
/**
@ -66,8 +69,8 @@ public class PropertyEntry {
* @return the property entry for given parameters.
*/
public static PropertyEntry forSourceReference(String name, Accessor readAccessor,
ExecutableElementAccessor presenceChecker, Type type) {
return new PropertyEntry( new String[]{name}, readAccessor, null, presenceChecker, type );
Accessor presenceChecker, Type type) {
return new PropertyEntry( new String[]{name}, readAccessor, null, presenceChecker, type, null );
}
public String getName() {
@ -82,7 +85,7 @@ public class PropertyEntry {
return writeAccessor;
}
public ExecutableElementAccessor getPresenceChecker() {
public Accessor getPresenceChecker() {
return presenceChecker;
}
@ -90,6 +93,10 @@ public class PropertyEntry {
return type;
}
public BuilderType getBuilderType() {
return builderType;
}
public String getFullName() {
return Strings.join( Arrays.asList( fullName ), "." );
}
@ -97,7 +104,7 @@ public class PropertyEntry {
public PropertyEntry pop() {
if ( fullName.length > 1 ) {
String[] newFullName = Arrays.copyOfRange( fullName, 1, fullName.length );
return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type );
return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type, builderType );
}
else {
return null;

View File

@ -26,6 +26,23 @@ public class SelectionParameters {
private final Types typeUtils;
private final SourceRHS sourceRHS;
/**
* Returns new selection parameters
*
* ResultType is not inherited.
*
* @param selectionParameters
* @return
*/
public static SelectionParameters forInheritance(SelectionParameters selectionParameters) {
return new SelectionParameters(
selectionParameters.qualifiers,
selectionParameters.qualifyingNames,
null,
selectionParameters.typeUtils
);
}
public SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames, TypeMirror resultType,
Types typeUtils) {
this( qualifiers, qualifyingNames, resultType, typeUtils, null );

View File

@ -19,6 +19,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
@ -50,6 +51,7 @@ public class SourceMethod implements Method {
private final Parameter targetTypeParameter;
private final boolean isObjectFactory;
private final Type returnType;
private final BuilderType builderType;
private final Accessibility accessibility;
private final List<Type> exceptionTypes;
private final MapperConfiguration config;
@ -80,6 +82,7 @@ public class SourceMethod implements Method {
private ExecutableElement executable;
private List<Parameter> parameters;
private Type returnType = null;
private BuilderType builderType = null;
private List<Type> exceptionTypes;
private Map<String, List<Mapping>> mappings;
private IterableMapping iterableMapping = null;
@ -207,6 +210,7 @@ public class SourceMethod implements Method {
this.executable = builder.executable;
this.parameters = builder.parameters;
this.returnType = builder.returnType;
this.builderType = builder.builderType;
this.exceptionTypes = builder.exceptionTypes;
this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() );

View File

@ -24,7 +24,6 @@ import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
/**
* This class describes the source side of a property mapping.
@ -209,7 +208,7 @@ public class SourceReference {
for ( String entryName : entryNames ) {
boolean matchFound = false;
Map<String, Accessor> sourceReadAccessors = newType.getPropertyReadAccessors();
Map<String, ExecutableElementAccessor> sourcePresenceCheckers = newType.getPropertyPresenceCheckers();
Map<String, Accessor> sourcePresenceCheckers = newType.getPropertyPresenceCheckers();
for ( Map.Entry<String, Accessor> getter : sourceReadAccessors.entrySet() ) {
if ( getter.getKey().equals( entryName ) ) {
@ -245,7 +244,7 @@ public class SourceReference {
private String name;
private Accessor readAccessor;
private ExecutableElementAccessor presenceChecker;
private Accessor presenceChecker;
private Type type;
private Parameter sourceParameter;
@ -259,7 +258,7 @@ public class SourceReference {
return this;
}
public BuilderFromProperty presenceChecker(ExecutableElementAccessor presenceChecker) {
public BuilderFromProperty presenceChecker(Accessor presenceChecker) {
this.presenceChecker = presenceChecker;
return this;
}

View File

@ -9,20 +9,21 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.DeclaredType;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
/**
* This class describes the target side of a property mapping.
@ -146,8 +147,7 @@ public class TargetReference {
Parameter parameter = method.getMappingTargetParameter();
boolean foundEntryMatch;
Type resultType = method.getResultType();
resultType = typeBasedOnMethod( resultType );
Type resultType = typeBasedOnMethod( method.getResultType() );
// there can be 4 situations
// 1. Return type
@ -198,8 +198,8 @@ public class TargetReference {
break;
}
if ( isLast || ( accessorNaming.isSetterMethod( targetWriteAccessor )
|| Executables.isFieldAccessor( targetWriteAccessor ) ) ) {
if ( isLast || ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER
|| targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) ) {
// only intermediate nested properties when they are a true setter or field accessor
// the last may be other readAccessor (setter / getter / adder).
@ -207,8 +207,28 @@ public class TargetReference {
// check if an entry alread exists, otherwise create
String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 );
PropertyEntry propertyEntry = PropertyEntry.forTargetReference( fullName, targetReadAccessor,
targetWriteAccessor, nextType );
BuilderType builderType;
PropertyEntry propertyEntry = null;
if ( method.isUpdateMethod() ) {
propertyEntry = PropertyEntry.forTargetReference(
fullName,
targetReadAccessor,
targetWriteAccessor,
nextType,
null
);
}
else {
BuilderPrism builderPrism = BeanMapping.builderPrismFor( method );
builderType = typeFactory.builderTypeFor( nextType, builderPrism );
propertyEntry = PropertyEntry.forTargetReference(
fullName,
targetReadAccessor,
targetWriteAccessor,
nextType,
builderType
);
}
targetEntries.add( propertyEntry );
}
@ -228,8 +248,7 @@ public class TargetReference {
private Type findNextType(Type initial, Accessor targetWriteAccessor, Accessor targetReadAccessor) {
Type nextType;
Accessor toUse = targetWriteAccessor != null ? targetWriteAccessor : targetReadAccessor;
if ( accessorNaming.isGetterMethod( toUse ) ||
Executables.isFieldAccessor( toUse ) ) {
if ( toUse.getAccessorType() == AccessorType.GETTER || toUse.getAccessorType() == AccessorType.FIELD ) {
nextType = typeFactory.getReturnType(
(DeclaredType) typeBasedOnMethod( initial ).getTypeMirror(),
toUse
@ -268,7 +287,8 @@ public class TargetReference {
return type;
}
else {
return type.getEffectiveType();
BuilderPrism builderPrism = BeanMapping.builderPrismFor( method );
return typeFactory.effectiveResultTypeFor( type, builderPrism );
}
}

View File

@ -8,13 +8,13 @@ package org.mapstruct.ap.internal.model.source.selector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.util.FormattingMessager;
/**
* Applies all known {@link MethodSelector}s in order.
@ -25,10 +25,11 @@ public class MethodSelectors {
private final List<MethodSelector> selectors;
public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory) {
public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory,
FormattingMessager messager) {
selectors = Arrays.asList(
new MethodFamilySelector(),
new TypeSelector( typeFactory ),
new TypeSelector( typeFactory, messager ),
new QualifierSelector( typeUtils, elementUtils ),
new TargetTypeSelector( typeUtils, elementUtils ),
new XmlElementDeclSelector( typeUtils ),

View File

@ -5,10 +5,9 @@
*/
package org.mapstruct.ap.internal.model.source.selector;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
@ -17,6 +16,10 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.MethodMatcher;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
* Selects those methods from the given input set which match the given source and target types (via
@ -27,9 +30,11 @@ import org.mapstruct.ap.internal.model.source.MethodMatcher;
public class TypeSelector implements MethodSelector {
private TypeFactory typeFactory;
private FormattingMessager messager;
public TypeSelector(TypeFactory typeFactory) {
public TypeSelector(TypeFactory typeFactory, FormattingMessager messager) {
this.typeFactory = typeFactory;
this.messager = messager;
}
@Override
@ -63,7 +68,7 @@ public class TypeSelector implements MethodSelector {
if ( parameterBindingPermutations != null ) {
SelectedMethod<T> matchingMethod =
getFirstMatchingParameterBinding( targetType, method, parameterBindingPermutations );
getMatchingParameterBinding( targetType, mappingMethod, method, parameterBindingPermutations );
if ( matchingMethod != null ) {
result.add( matchingMethod );
@ -77,7 +82,6 @@ public class TypeSelector implements MethodSelector {
SourceRHS sourceRHS) {
List<ParameterBinding> availableParams = new ArrayList<>( method.getParameters().size() + 3 );
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
if ( sourceRHS != null ) {
availableParams.addAll( ParameterBinding.fromParameters( method.getContextParameters() ) );
availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) );
@ -86,6 +90,8 @@ public class TypeSelector implements MethodSelector {
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
}
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
return availableParams;
}
@ -94,8 +100,6 @@ public class TypeSelector implements MethodSelector {
List<ParameterBinding> availableParams = new ArrayList<>( sourceTypes.size() + 2 );
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
for ( Type sourceType : sourceTypes ) {
availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) );
}
@ -106,25 +110,125 @@ public class TypeSelector implements MethodSelector {
}
}
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
return availableParams;
}
/**
* Adds default parameter bindings for the mapping-target and target-type if not already available.
*
* @param availableParams Already available params, new entries will be added to this list
* @param targetType Target type
*/
private void addMappingTargetAndTargetTypeBindings(List<ParameterBinding> availableParams, Type targetType) {
boolean mappingTargetAvailable = false;
boolean targetTypeAvailable = false;
// search available parameter bindings if mapping-target and/or target-type is available
for ( ParameterBinding pb : availableParams ) {
if ( pb.isMappingTarget() ) {
mappingTargetAvailable = true;
}
else if ( pb.isTargetType() ) {
targetTypeAvailable = true;
}
}
if ( !mappingTargetAvailable ) {
availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) );
}
if ( !targetTypeAvailable ) {
availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
}
private <T extends Method> SelectedMethod<T> getFirstMatchingParameterBinding(Type targetType,
SelectedMethod<T> method, List<List<ParameterBinding>> parameterAssignmentVariants) {
for ( List<ParameterBinding> parameterAssignments : parameterAssignmentVariants ) {
if ( method.getMethod().matches( extractTypes( parameterAssignments ), targetType ) ) {
method.setParameterBindings( parameterAssignments );
return method;
}
}
private <T extends Method> SelectedMethod<T> getMatchingParameterBinding(Type targetType,
Method mappingMethod, SelectedMethod<T> selectedMethodInfo,
List<List<ParameterBinding>> parameterAssignmentVariants) {
List<List<ParameterBinding>> matchingParameterAssignmentVariants = new ArrayList<>(
parameterAssignmentVariants
);
Method selectedMethod = selectedMethodInfo.getMethod();
// remove all assignment variants that doesn't match the types from the method
matchingParameterAssignmentVariants.removeIf( parameterAssignments ->
!selectedMethod.matches( extractTypes( parameterAssignments ), targetType )
);
if ( matchingParameterAssignmentVariants.isEmpty() ) {
// no matching variants found
return null;
}
else if ( matchingParameterAssignmentVariants.size() == 1 ) {
// we found exactly one set of variants, use this
selectedMethodInfo.setParameterBindings( first( matchingParameterAssignmentVariants ) );
return selectedMethodInfo;
}
// more than one variant matches, try to find one where also the parameter names are matching
// -> remove all variants where the binding var-name doesn't match the var-name of the parameter
List<Parameter> methodParameters = selectedMethod.getParameters();
matchingParameterAssignmentVariants.removeIf( parameterBindings ->
parameterBindingNotMatchesParameterVariableNames(
parameterBindings,
methodParameters
)
);
if ( matchingParameterAssignmentVariants.isEmpty() ) {
// we had some matching assignments before, but when checking the parameter names we can't find an
// appropriate one, in this case the user must chose identical parameter names for the mapping and lifecycle
// method
messager.printMessage(
selectedMethod.getExecutable(),
Message.LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS,
mappingMethod
);
return null;
}
// there should never be more then one assignment left after checking the parameter names as it is not possible
// to use the same parameter name more then once
// -> we can use the first variant that is left (that should also be the only one)
selectedMethodInfo.setParameterBindings( first( matchingParameterAssignmentVariants ) );
return selectedMethodInfo;
}
/**
* Checks if the given parameter-bindings have the same variable name than the given parameters.<br>
* The first entry in the parameter-bindings belongs to the first entry in the parameters and so on.<br>
*
* @param parameterBindings List of parameter bindings
* @param parameters List of parameters, must have the same size than the {@code parameterBindings} list
*
* @return {@code true} as soon as there is a parameter with a different variable name than the binding
*/
private boolean parameterBindingNotMatchesParameterVariableNames(List<ParameterBinding> parameterBindings,
List<Parameter> parameters) {
if ( parameterBindings.size() != parameters.size() ) {
return true;
}
int i = 0;
for ( ParameterBinding parameterBinding : parameterBindings ) {
Parameter parameter = parameters.get( i++ );
// if the parameterBinding contains a parameter name we must ensure that this matches the name from the
// method parameter -> remove all variants where this is not the case
if ( parameterBinding.getVariableName() != null &&
!parameter.getName().equals( parameterBinding.getVariableName() ) ) {
return true;
}
}
return false;
}
/**
* @param availableParams parameter bindings available in the scope of the method call
@ -200,12 +304,8 @@ public class TypeSelector implements MethodSelector {
}
private static List<Type> extractTypes(List<ParameterBinding> parameters) {
List<Type> result = new ArrayList<>( parameters.size() );
for ( ParameterBinding param : parameters ) {
result.add( param.getType() );
}
return result;
return parameters.stream()
.map( ParameterBinding::getType )
.collect( Collectors.toList() );
}
}

View File

@ -41,11 +41,13 @@ import org.mapstruct.ap.internal.model.ValueMappingMethod;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.source.BeanMapping;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.DecoratedWithPrism;
import org.mapstruct.ap.internal.prism.InheritConfigurationPrism;
import org.mapstruct.ap.internal.prism.InheritInverseConfigurationPrism;
@ -365,11 +367,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
}
else {
BuilderPrism builderPrism = BeanMapping.builderPrismFor( method );
BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder();
BeanMappingMethod beanMappingMethod = builder
.mappingContext( mappingContext )
.sourceMethod( method )
.returnTypeBuilder( typeFactory.builderTypeFor( method.getReturnType(), builderPrism ) )
.build();
if ( beanMappingMethod != null ) {

View File

@ -381,8 +381,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private boolean checkParameterAndReturnType(ExecutableElement method, List<Parameter> sourceParameters,
Parameter targetParameter, List<Parameter> contextParameters,
Type resultType, Type returnType,
boolean containsTargetTypeParameter) {
Type resultType, Type returnType, boolean containsTargetTypeParameter) {
if ( sourceParameters.isEmpty() ) {
messager.printMessage( method, Message.RETRIEVAL_NO_INPUT_ARGS );
return false;
@ -401,7 +400,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
if ( returnType.getTypeMirror().getKind() != TypeKind.VOID &&
!resultType.isAssignableTo( returnType ) &&
!resultType.isAssignableTo( returnType.getEffectiveType() )) {
!resultType.isAssignableTo( typeFactory.effectiveResultTypeFor( returnType, null ) )) {
messager.printMessage( method, Message.RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE );
return false;
}

View File

@ -91,7 +91,7 @@ public class MappingResolverImpl implements MappingResolver {
this.conversions = new Conversions( elementUtils, typeFactory );
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, typeFactory );
this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, typeFactory, messager );
}
@Override

View File

@ -7,6 +7,7 @@ package org.mapstruct.ap.internal.util;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@ -14,7 +15,7 @@ import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.MethodType;
@ -33,46 +34,41 @@ public final class AccessorNamingUtils {
this.accessorNamingStrategy = accessorNamingStrategy;
}
public boolean isGetterMethod(Accessor method) {
ExecutableElement executable = method.getExecutable();
return executable != null && isPublicNotStatic( method ) &&
public boolean isGetterMethod(ExecutableElement executable) {
return executable != null && isPublicNotStatic( executable ) &&
executable.getParameters().isEmpty() &&
accessorNamingStrategy.getMethodType( executable ) == MethodType.GETTER;
}
public boolean isPresenceCheckMethod(Accessor method) {
if ( !( method instanceof ExecutableElementAccessor ) ) {
return false;
}
ExecutableElement executable = method.getExecutable();
public boolean isPresenceCheckMethod(ExecutableElement executable) {
return executable != null
&& isPublicNotStatic( method )
&& isPublicNotStatic( executable )
&& executable.getParameters().isEmpty()
&& ( executable.getReturnType().getKind() == TypeKind.BOOLEAN ||
"java.lang.Boolean".equals( getQualifiedName( executable.getReturnType() ) ) )
&& accessorNamingStrategy.getMethodType( executable ) == MethodType.PRESENCE_CHECKER;
}
public boolean isSetterMethod(Accessor method) {
ExecutableElement executable = method.getExecutable();
public boolean isSetterMethod(ExecutableElement executable) {
return executable != null
&& isPublicNotStatic( method )
&& isPublicNotStatic( executable )
&& executable.getParameters().size() == 1
&& accessorNamingStrategy.getMethodType( executable ) == MethodType.SETTER;
}
public boolean isAdderMethod(Accessor method) {
ExecutableElement executable = method.getExecutable();
public boolean isAdderMethod(ExecutableElement executable) {
return executable != null
&& isPublicNotStatic( method )
&& isPublicNotStatic( executable )
&& executable.getParameters().size() == 1
&& accessorNamingStrategy.getMethodType( executable ) == MethodType.ADDER;
}
public String getPropertyName(Accessor accessor) {
ExecutableElement executable = accessor.getExecutable();
return executable != null ? accessorNamingStrategy.getPropertyName( executable ) :
accessor.getSimpleName().toString();
public String getPropertyName(ExecutableElement executable) {
return accessorNamingStrategy.getPropertyName( executable );
}
public String getPropertyName(VariableElement variable) {
return variable.getSimpleName().toString();
}
/**
@ -82,8 +78,12 @@ public final class AccessorNamingUtils {
* {@code addChild(Child v)}, the element name would be 'Child'.
*/
public String getElementNameForAdder(Accessor adderMethod) {
ExecutableElement executable = adderMethod.getExecutable();
return executable != null ? accessorNamingStrategy.getElementName( executable ) : null;
if ( adderMethod.getAccessorType() == AccessorType.ADDER ) {
return accessorNamingStrategy.getElementName( (ExecutableElement) adderMethod.getElement() );
}
else {
return null;
}
}
private static String getQualifiedName(TypeMirror type) {

View File

@ -5,7 +5,6 @@
*/
package org.mapstruct.ap.internal.util;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
@ -18,7 +17,6 @@ import java.util.ListIterator;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@ -27,8 +25,6 @@ import javax.lang.model.util.Elements;
import org.mapstruct.ap.internal.prism.AfterMappingPrism;
import org.mapstruct.ap.internal.prism.BeforeMappingPrism;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
import org.mapstruct.ap.internal.util.accessor.VariableElementAccessor;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
/**
@ -54,28 +50,15 @@ public class Executables {
private Executables() {
}
/**
* An {@link Accessor} is a field accessor, if it doesn't have an executable element, is public and it is not
* static.
*
* @param accessor the accessor to ber checked
*
* @return {@code true} if the {@code accessor} is for a {@code public} non {@code static} field.
*/
public static boolean isFieldAccessor(Accessor accessor) {
ExecutableElement executable = accessor.getExecutable();
return executable == null && isPublic( accessor ) && isNotStatic( accessor );
}
static boolean isPublicNotStatic(Accessor accessor) {
static boolean isPublicNotStatic(ExecutableElement accessor) {
return isPublic( accessor ) && isNotStatic( accessor );
}
static boolean isPublic(Accessor method) {
static boolean isPublic(ExecutableElement method) {
return method.getModifiers().contains( Modifier.PUBLIC );
}
private static boolean isNotStatic(Accessor accessor) {
private static boolean isNotStatic(ExecutableElement accessor) {
return !accessor.getModifiers().contains( Modifier.STATIC );
}
@ -103,8 +86,8 @@ public class Executables {
/**
* Finds all executable elements within the given type element, including executable elements defined in super
* classes and implemented interfaces. Methods defined in {@link java.lang.Object} are ignored, as well as
* implementations of {@link java.lang.Object#equals(Object)}.
* classes and implemented interfaces. Methods defined in {@link java.lang.Object},
* implementations of {@link java.lang.Object#equals(Object)} and private methods are ignored
*
* @param elementUtils element helper
* @param element the element to inspect
@ -112,34 +95,14 @@ public class Executables {
* @return the executable elements usable in the type
*/
public static List<ExecutableElement> getAllEnclosedExecutableElements(Elements elementUtils, TypeElement element) {
List<ExecutableElement> executables = new ArrayList<>();
for ( Accessor accessor : getAllEnclosedAccessors( elementUtils, element ) ) {
if ( accessor.getExecutable() != null ) {
executables.add( accessor.getExecutable() );
}
}
return executables;
}
/**
* Finds all executable elements/variable elements within the given type element, including executable/variable
* elements defined in super classes and implemented interfaces and including the fields in the . Methods defined
* in {@link java.lang.Object} are ignored, as well as implementations of {@link java.lang.Object#equals(Object)}.
*
* @param elementUtils element helper
* @param element the element to inspect
*
* @return the executable elements usable in the type
*/
public static List<Accessor> getAllEnclosedAccessors(Elements elementUtils, TypeElement element) {
List<Accessor> enclosedElements = new ArrayList<>();
List<ExecutableElement> enclosedElements = new ArrayList<>();
element = replaceTypeElementIfNecessary( elementUtils, element );
addEnclosedElementsInHierarchy( elementUtils, enclosedElements, element, element );
return enclosedElements;
}
private static void addEnclosedElementsInHierarchy(Elements elementUtils, List<Accessor> alreadyAdded,
private static void addEnclosedElementsInHierarchy(Elements elementUtils, List<ExecutableElement> alreadyAdded,
TypeElement element, TypeElement parentType) {
if ( element != parentType ) { // otherwise the element was already checked for replacement
element = replaceTypeElementIfNecessary( elementUtils, element );
@ -150,7 +113,6 @@ public class Executables {
}
addNotYetOverridden( elementUtils, alreadyAdded, methodsIn( element.getEnclosedElements() ), parentType );
addFields( alreadyAdded, fieldsIn( element.getEnclosedElements() ) );
if ( hasNonObjectSuperclass( element ) ) {
addEnclosedElementsInHierarchy(
@ -178,28 +140,19 @@ public class Executables {
* @param methodsToAdd methods to add to alreadyAdded, if they are not yet overridden by an element in the list
* @param parentType the type for with elements are collected
*/
private static void addNotYetOverridden(Elements elementUtils, List<Accessor> alreadyCollected,
private static void addNotYetOverridden(Elements elementUtils, List<ExecutableElement> alreadyCollected,
List<ExecutableElement> methodsToAdd, TypeElement parentType) {
List<Accessor> safeToAdd = new ArrayList<>( methodsToAdd.size() );
List<ExecutableElement> safeToAdd = new ArrayList<>( methodsToAdd.size() );
for ( ExecutableElement toAdd : methodsToAdd ) {
if ( isNotObjectEquals( toAdd )
if ( isNotPrivate( toAdd ) && isNotObjectEquals( toAdd )
&& wasNotYetOverridden( elementUtils, alreadyCollected, toAdd, parentType ) ) {
safeToAdd.add( new ExecutableElementAccessor( toAdd ) );
safeToAdd.add( toAdd );
}
}
alreadyCollected.addAll( 0, safeToAdd );
}
private static void addFields(List<Accessor> alreadyCollected, List<VariableElement> variablesToAdd) {
List<Accessor> safeToAdd = new ArrayList<>( variablesToAdd.size() );
for ( VariableElement toAdd : variablesToAdd ) {
safeToAdd.add( new VariableElementAccessor( toAdd ) );
}
alreadyCollected.addAll( 0, safeToAdd );
}
/**
* @param executable the executable to check
@ -217,6 +170,15 @@ public class Executables {
return true;
}
/**
* @param executable the executable to check
*
* @return {@code true}, iff the executable does not have a private modifier
*/
private static boolean isNotPrivate(ExecutableElement executable) {
return !executable.getModifiers().contains( Modifier.PRIVATE );
}
/**
* @param elementUtils the elementUtils
* @param alreadyCollected the list of already collected methods of one type hierarchy (order is from sub-types to
@ -225,10 +187,10 @@ public class Executables {
* @param parentType the type for which elements are collected
* @return {@code true}, iff the given executable was not yet overridden by a method in the given list.
*/
private static boolean wasNotYetOverridden(Elements elementUtils, List<Accessor> alreadyCollected,
private static boolean wasNotYetOverridden(Elements elementUtils, List<ExecutableElement> alreadyCollected,
ExecutableElement executable, TypeElement parentType) {
for ( ListIterator<Accessor> it = alreadyCollected.listIterator(); it.hasNext(); ) {
ExecutableElement executableInSubtype = it.next().getExecutable();
for ( ListIterator<ExecutableElement> it = alreadyCollected.listIterator(); it.hasNext(); ) {
ExecutableElement executableInSubtype = it.next();
if ( executableInSubtype == null ) {
continue;
}

View File

@ -0,0 +1,106 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.util;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
/**
* Provides functionality around {@link VariableElement}s.
*
* @author Sjaak Derksen
*/
public class Fields {
private Fields() {
}
public static boolean isFieldAccessor(VariableElement method) {
return isPublic( method ) && isNotStatic( method );
}
static boolean isPublic(VariableElement method) {
return method.getModifiers().contains( Modifier.PUBLIC );
}
private static boolean isNotStatic(VariableElement method) {
return !method.getModifiers().contains( Modifier.STATIC );
}
/**
* Finds all variable elements within the given type element, including variable
* elements defined in super classes and implemented interfaces and including the fields in the .
*
* @param elementUtils element helper
* @param element the element to inspect
*
* @return the executable elements usable in the type
*/
public static List<VariableElement> getAllEnclosedFields(Elements elementUtils, TypeElement element) {
List<VariableElement> enclosedElements = new ArrayList<>();
element = replaceTypeElementIfNecessary( elementUtils, element );
addEnclosedElementsInHierarchy( elementUtils, enclosedElements, element, element );
return enclosedElements;
}
private static void addEnclosedElementsInHierarchy(Elements elementUtils, List<VariableElement> alreadyAdded,
TypeElement element, TypeElement parentType) {
if ( element != parentType ) { // otherwise the element was already checked for replacement
element = replaceTypeElementIfNecessary( elementUtils, element );
}
if ( element.asType().getKind() == TypeKind.ERROR ) {
throw new TypeHierarchyErroneousException( element );
}
addFields( alreadyAdded, fieldsIn( element.getEnclosedElements() ) );
if ( hasNonObjectSuperclass( element ) ) {
addEnclosedElementsInHierarchy(
elementUtils,
alreadyAdded,
asTypeElement( element.getSuperclass() ),
parentType
);
}
}
private static void addFields(List<VariableElement> alreadyCollected, List<VariableElement> variablesToAdd) {
List<VariableElement> safeToAdd = new ArrayList<>( variablesToAdd.size() );
for ( VariableElement toAdd : variablesToAdd ) {
safeToAdd.add( toAdd );
}
alreadyCollected.addAll( 0, safeToAdd );
}
private static TypeElement asTypeElement(TypeMirror mirror) {
return (TypeElement) ( (DeclaredType) mirror ).asElement();
}
private static boolean hasNonObjectSuperclass(TypeElement element) {
if ( element.getSuperclass().getKind() == TypeKind.ERROR ) {
throw new TypeHierarchyErroneousException( element );
}
return element.getSuperclass().getKind() == TypeKind.DECLARED
&& !asTypeElement( element.getSuperclass() ).getQualifiedName().toString().equals( "java.lang.Object" );
}
}

View File

@ -9,9 +9,22 @@ import java.util.LinkedList;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
import org.mapstruct.ap.internal.util.accessor.VariableElementAccessor;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.accessor.AccessorType.ADDER;
import static org.mapstruct.ap.internal.util.accessor.AccessorType.GETTER;
import static org.mapstruct.ap.internal.util.accessor.AccessorType.PRESENCE_CHECKER;
import static org.mapstruct.ap.internal.util.accessor.AccessorType.SETTER;
/**
* Filter methods for working with {@link Element} collections.
@ -20,66 +33,88 @@ import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
*/
public class Filters {
private Filters() {
private final AccessorNamingUtils accessorNaming;
private final Types typeUtils;
private final TypeMirror typeMirror;
public Filters(AccessorNamingUtils accessorNaming, Types typeUtils, TypeMirror typeMirror) {
this.accessorNaming = accessorNaming;
this.typeUtils = typeUtils;
this.typeMirror = typeMirror;
}
public static List<Accessor> getterMethodsIn(AccessorNamingUtils accessorNaming, List<Accessor> elements) {
public List<Accessor> getterMethodsIn(List<ExecutableElement> elements) {
List<Accessor> getterMethods = new LinkedList<>();
for ( Accessor method : elements ) {
for ( ExecutableElement method : elements ) {
if ( accessorNaming.isGetterMethod( method ) ) {
getterMethods.add( method );
getterMethods.add( new ExecutableElementAccessor( method, getReturnType( method ), GETTER ) );
}
}
return getterMethods;
}
public static List<Accessor> fieldsIn(List<Accessor> accessors) {
public List<Accessor> fieldsIn(List<VariableElement> accessors) {
List<Accessor> fieldAccessors = new LinkedList<>();
for ( Accessor accessor : accessors ) {
if ( Executables.isFieldAccessor( accessor ) ) {
fieldAccessors.add( accessor );
for ( VariableElement accessor : accessors ) {
if ( Fields.isFieldAccessor( accessor ) ) {
fieldAccessors.add( new VariableElementAccessor( accessor ) );
}
}
return fieldAccessors;
}
public static List<ExecutableElementAccessor> presenceCheckMethodsIn(AccessorNamingUtils accessorNaming,
List<Accessor> elements) {
List<ExecutableElementAccessor> presenceCheckMethods = new LinkedList<>();
public List<Accessor> presenceCheckMethodsIn(List<ExecutableElement> elements) {
List<Accessor> presenceCheckMethods = new LinkedList<>();
for ( Accessor method : elements ) {
for ( ExecutableElement method : elements ) {
if ( accessorNaming.isPresenceCheckMethod( method ) ) {
presenceCheckMethods.add( (ExecutableElementAccessor) method );
presenceCheckMethods.add( new ExecutableElementAccessor(
method,
getReturnType( method ),
PRESENCE_CHECKER
) );
}
}
return presenceCheckMethods;
}
public static List<Accessor> setterMethodsIn(AccessorNamingUtils accessorNaming, List<Accessor> elements) {
public List<Accessor> setterMethodsIn(List<ExecutableElement> elements) {
List<Accessor> setterMethods = new LinkedList<>();
for ( Accessor method : elements ) {
for ( ExecutableElement method : elements ) {
if ( accessorNaming.isSetterMethod( method ) ) {
setterMethods.add( method );
setterMethods.add( new ExecutableElementAccessor( method, getFirstParameter( method ), SETTER ) );
}
}
return setterMethods;
}
public static List<Accessor> adderMethodsIn(AccessorNamingUtils accessorNaming, List<Accessor> elements) {
public List<Accessor> adderMethodsIn(List<ExecutableElement> elements) {
List<Accessor> adderMethods = new LinkedList<>();
for ( Accessor method : elements ) {
for ( ExecutableElement method : elements ) {
if ( accessorNaming.isAdderMethod( method ) ) {
adderMethods.add( method );
adderMethods.add( new ExecutableElementAccessor( method, getFirstParameter( method ), ADDER ) );
}
}
return adderMethods;
}
private TypeMirror getReturnType(ExecutableElement executableElement) {
return getWithinContext( executableElement ).getReturnType();
}
private TypeMirror getFirstParameter(ExecutableElement executableElement) {
return first( getWithinContext( executableElement ).getParameterTypes() );
}
private ExecutableType getWithinContext( ExecutableElement executableElement ) {
return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement );
}
}

View File

@ -186,7 +186,7 @@ public class MapperConfiguration {
else if ( beanPrism != null ) {
return beanPrism;
}
else if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) {
else if ( mapperConfigPrism != null && mapperPrism.values.nullValuePropertyMappingStrategy() == null ) {
return NullValuePropertyMappingStrategyPrism.valueOf(
mapperConfigPrism.nullValuePropertyMappingStrategy()
);
@ -265,8 +265,11 @@ public class MapperConfiguration {
return mapperPrism.disableSubMappingMethodsGeneration(); // fall back to default defined in the annotation
}
public BuilderPrism getBuilderPrism() {
if ( mapperPrism.values.builder() != null ) {
public BuilderPrism getBuilderPrism(BuilderPrism beanMappingBuilderPrism) {
if ( beanMappingBuilderPrism != null ) {
return beanMappingBuilderPrism;
}
else if ( mapperPrism.values.builder() != null ) {
return mapperPrism.builder();
}
else if ( mapperConfigPrism != null && mapperConfigPrism.values.builder() != null ) {

View File

@ -32,7 +32,8 @@ public enum Message {
BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ),
PROPERTYMAPPING_MAPPING_NOT_FOUND( "Can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND( "Can't map %s to %s. Consider to implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND( "No target bean properties found: can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND( "No target bean properties found: can't map %s to %s. Consider to implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_DUPLICATE_TARGETS( "Target property \"%s\" must not be mapped more than once." ),
PROPERTYMAPPING_EMPTY_TARGET( "Target must not be empty in @Mapping." ),
PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED( "Source and constant are both defined in @Mapping, either define a source or a constant." ),
@ -81,6 +82,8 @@ public enum Message {
ENUMMAPPING_UNMAPPED_SOURCES( "The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via adding additional mappings: %s." ),
ENUMMAPPING_DEPRECATED( "Mapping of Enums via @Mapping is going to be removed in future versions of MapStruct. Please use @ValueMapping instead!", Diagnostic.Kind.WARNING ),
LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING),
DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ),
DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ),

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.internal.util;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType;
/**
* This a wrapper class which provides the value that needs to be used in the models.
@ -45,7 +46,7 @@ public class ValueProvider {
return null;
}
String value = accessor.getSimpleName().toString();
if ( accessor.getExecutable() != null ) {
if ( accessor.getAccessorType() != AccessorType.FIELD ) {
value += "()";
}
return new ValueProvider( value );

View File

@ -38,4 +38,5 @@ abstract class AbstractAccessor<T extends Element> implements Accessor {
public T getElement() {
return element;
}
}

View File

@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.util.accessor;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
@ -23,7 +24,7 @@ public interface Accessor {
* This returns the type that this accessor gives as a return.
*
* e.g. The {@link ExecutableElement#getReturnType()} if this is a method accessor,
* or {@link javax.lang.model.element.VariableElement#asType()} for field accessors.
* or {@link VariableElement#asType()} for field accessors.
*
* @return the type that the accessor gives as a return
*/
@ -39,13 +40,15 @@ public interface Accessor {
*/
Set<Modifier> getModifiers();
/**
* @return the {@link ExecutableElement}, or {@code null} if the accessor does not have one
*/
ExecutableElement getExecutable();
/**
* @return the underlying {@link Element}
*/
Element getElement();
/**
* The accessor type
*
* @return
*/
AccessorType getAccessorType();
}

View File

@ -0,0 +1,15 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.util.accessor;
public enum AccessorType {
FIELD,
GETTER,
SETTER,
ADDER,
PRESENCE_CHECKER;
}

View File

@ -15,17 +15,28 @@ import javax.lang.model.type.TypeMirror;
*/
public class ExecutableElementAccessor extends AbstractAccessor<ExecutableElement> {
public ExecutableElementAccessor(ExecutableElement element) {
private final TypeMirror accessedType;
private final AccessorType accessorType;
public ExecutableElementAccessor(ExecutableElement element, TypeMirror accessedType,
AccessorType accessorType) {
super( element );
this.accessedType = accessedType;
this.accessorType = accessorType;
}
@Override
public TypeMirror getAccessedType() {
return element.getReturnType();
return accessedType;
}
@Override
public ExecutableElement getExecutable() {
return element;
public String toString() {
return element.toString();
}
@Override
public AccessorType getAccessorType() {
return accessorType;
}
}

View File

@ -5,7 +5,6 @@
*/
package org.mapstruct.ap.internal.util.accessor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
@ -26,7 +25,12 @@ public class VariableElementAccessor extends AbstractAccessor<VariableElement> {
}
@Override
public ExecutableElement getExecutable() {
return null;
public String toString() {
return element.toString();
}
@Override
public AccessorType getAccessorType() {
return AccessorType.FIELD;
}
}

View File

@ -57,7 +57,8 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
}
/**
* Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it starts
* Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it
* has no parameters, starts
* with 'get' and the return type is any type other than {@code void}, OR the getter starts with 'is' and the type
* returned is a primitive or the wrapper for {@code boolean}. NOTE: the latter does strictly not comply to the bean
* convention. The remainder of the name is supposed to reflect the property name.
@ -69,6 +70,10 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
* @return {@code true} when the method is a getter.
*/
public boolean isGetterMethod(ExecutableElement method) {
if ( !method.getParameters().isEmpty() ) {
// If the method has parameters it can't be a getter
return false;
}
String methodName = method.getSimpleName().toString();
boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 &&
@ -166,12 +171,23 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
if ( methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) {
if ( isFluentSetter( getterOrSetterMethod ) ) {
// If this is a fluent setter that starts with set and the 4th character is an uppercase one
// then we treat it as a Java Bean style method (we get the property starting from the 4th character).
// Otherwise we treat it as a fluent setter
// For example, for the following methods:
// * public Builder setSettlementDate(String settlementDate)
// * public Builder settlementDate(String settlementDate)
// We are going to extract the same property name settlementDate
if ( methodName.startsWith( "set" )
&& methodName.length() > 3
&& Character.isUpperCase( methodName.charAt( 3 ) ) ) {
return IntrospectorUtils.decapitalize( methodName.substring( 3 ) );
}
else if ( isFluentSetter( getterOrSetterMethod ) ) {
else {
return methodName;
}
}
return IntrospectorUtils.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
}

View File

@ -204,7 +204,14 @@ public class DefaultBuilderProvider implements BuilderProvider {
return method.getParameters().isEmpty()
&& method.getModifiers().contains( Modifier.PUBLIC )
&& method.getModifiers().contains( Modifier.STATIC )
&& !typeUtils.isSameType( method.getReturnType(), typeElement.asType() );
&& method.getReturnType().getKind() != TypeKind.VOID
// Only compare raw elements
// Reason: if the method is a generic method (<T> Holder<T> build()) and the type element is (Holder<T>)
// then the return type of the method does not match the type of the type element
&& !typeUtils.isSameType(
typeUtils.erasure( method.getReturnType() ),
typeUtils.erasure( typeElement.asType() )
);
}
/**

View File

@ -10,7 +10,7 @@
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#assign targetType = resultType />
<#if !existingInstanceMapping>
<#assign targetType = resultType.effectiveType />
<#assign targetType = returnTypeToConstruct />
</#if>
<#list beforeMappingReferencesWithoutMappingTarget as callback>
<@includeModel object=callback targetBeanName=resultName targetType=targetType/>
@ -25,7 +25,7 @@
</#if>
<#if !existingInstanceMapping>
<@includeModel object=resultType.effectiveType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType.effectiveType/><#else>new <@includeModel object=resultType.effectiveType/>()</#if>;
<@includeModel object=returnTypeToConstruct/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=returnTypeToConstruct/><#else>new <@includeModel object=returnTypeToConstruct/>()</#if>;
</#if>
<#list beforeMappingReferencesWithMappingTarget as callback>

View File

@ -9,6 +9,8 @@
<@compress single_line=true>
<#if factoryMethod??>
<@includeModel object=factoryMethod targetType=resultType/>
<#elseif enumSet>
EnumSet.noneOf( <@includeModel object=enumSetElementType raw=true/>.class )
<#else>
new
<#if resultType.implementationType??>

View File

@ -6,17 +6,17 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.NestedPropertyMappingMethod" -->
<#lt>private <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
if ( ${sourceParameter.name} == null ) {
return ${returnType.null};
}
<#list propertyEntries as entry>
<#if entry.presenceCheckerName?? >
if ( !<@localVarName index=entry_index/>.${entry.presenceCheckerName}() ) {
if ( <#if entry_index != 0><@localVarName index=entry_index/> == null || </#if>!<@localVarName index=entry_index/>.${entry.presenceCheckerName}() ) {
return ${returnType.null};
}
</#if>
<@includeModel object=entry.type/> ${entry.name} = <@localVarName index=entry_index/>.${entry.accessorName};
<@includeModel object=entry.type.typeBound/> ${entry.name} = <@localVarName index=entry_index/>.${entry.accessorName};
<#if !entry.presenceCheckerName?? >
<#if !entry.type.primitive>
if ( ${entry.name} == null ) {

View File

@ -167,7 +167,6 @@ public class DateFormatValidatorFactoryTest {
null,
null,
null,
null,
fullQualifiedName,
false,
false,

View File

@ -115,7 +115,6 @@ public class DefaultConversionContextTest {
null,
null,
null,
null,
fullQualifiedName,
false,
false,

View File

@ -0,0 +1,47 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class BookMapper {
public static final BookMapper INSTANCE = Mappers.getMapper( BookMapper.class );
public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName);
@AfterMapping
protected void fillAuthor(@MappingTarget TargetBook targetBook, String authorFirstName, String authorLastName) {
targetBook.setAuthorFirstName( authorFirstName );
targetBook.setAuthorLastName( authorLastName );
}
@AfterMapping
protected void withoutAuthorNames(@MappingTarget TargetBook targetBook) {
targetBook.setAfterMappingWithoutAuthorName( true );
}
@AfterMapping
protected void withOnlyFirstName(@MappingTarget TargetBook targetBook, String authorFirstName) {
targetBook.setAfterMappingWithOnlyFirstName( authorFirstName );
}
@AfterMapping
protected void withOnlyLastName(@MappingTarget TargetBook targetBook, String authorLastName) {
targetBook.setAfterMappingWithOnlyLastName( authorLastName );
}
@AfterMapping
protected void withDifferentVariableName(@MappingTarget TargetBook targetBook, String author) {
targetBook.setAfterMappingWithDifferentVariableName( true );
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class DifferentOrderingBookMapper {
public static final DifferentOrderingBookMapper INSTANCE = Mappers.getMapper( DifferentOrderingBookMapper.class );
public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName);
@AfterMapping
protected void fillAuthor(String authorLastName, String authorFirstName, @MappingTarget TargetBook targetBook) {
targetBook.setAuthorLastName( authorLastName );
targetBook.setAuthorFirstName( authorFirstName );
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ErroneousBookMapper {
public static final ErroneousBookMapper INSTANCE = Mappers.getMapper( ErroneousBookMapper.class );
public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName);
@AfterMapping
protected void fillAuthor(@MappingTarget TargetBook targetBook, String authorFirstN, String authorLastN) {
targetBook.setAuthorFirstName( authorFirstN );
targetBook.setAuthorLastName( authorLastN );
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
@WithClasses({
SourceBook.class,
TargetBook.class
})
@RunWith(AnnotationProcessorTestRunner.class)
@IssueKey("1457")
public class Issue1457Test {
private SourceBook sourceBook;
private String authorFirstName;
private String authorLastName;
@Before
public void setup() {
sourceBook = new SourceBook();
sourceBook.setIsbn( "3453146972" );
sourceBook.setTitle( "Per Anhalter durch die Galaxis" );
authorFirstName = "Douglas";
authorLastName = "Adams";
}
@Test
@WithClasses({
BookMapper.class
})
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = @Diagnostic(
messageRegExp =
"Lifecycle method has multiple matching parameters \\(e\\. g\\. same type\\), in this case " +
"please ensure to name the parameters in the lifecycle and mapping method identical\\. This " +
"lifecycle method will not be used for the mapping method '.*\\.TargetBook mapBook\\(" +
".*\\.SourceBook sourceBook, .*\\.String authorFirstName, .*\\.String authorLastName\\)'\\.",
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 43
)
)
public void testMapperWithMatchingParameterNames() {
TargetBook targetBook = BookMapper.INSTANCE.mapBook( sourceBook, authorFirstName, authorLastName );
assertTargetBookMatchesSourceBook( targetBook );
assertThat( targetBook.isAfterMappingWithoutAuthorName() ).isTrue();
assertThat( targetBook.getAfterMappingWithOnlyFirstName() ).isEqualTo( authorFirstName );
assertThat( targetBook.getAfterMappingWithOnlyLastName() ).isEqualTo( authorLastName );
assertThat( targetBook.isAfterMappingWithDifferentVariableName() ).isFalse();
}
@Test
@WithClasses({
DifferentOrderingBookMapper.class
})
public void testMapperWithMatchingParameterNamesAndDifferentOrdering() {
TargetBook targetBook = DifferentOrderingBookMapper.INSTANCE.mapBook(
sourceBook,
authorFirstName,
authorLastName
);
assertTargetBookMatchesSourceBook( targetBook );
}
@Test
@WithClasses({
ObjectFactoryBookMapper.class
})
public void testMapperWithObjectFactory() {
TargetBook targetBook = ObjectFactoryBookMapper.INSTANCE.mapBook(
sourceBook,
authorFirstName,
authorLastName
);
assertTargetBookMatchesSourceBook( targetBook );
}
private void assertTargetBookMatchesSourceBook(TargetBook targetBook) {
assertThat( sourceBook.getIsbn() ).isEqualTo( targetBook.getIsbn() );
assertThat( sourceBook.getTitle() ).isEqualTo( targetBook.getTitle() );
assertThat( authorFirstName ).isEqualTo( targetBook.getAuthorFirstName() );
assertThat( authorLastName ).isEqualTo( targetBook.getAuthorLastName() );
}
@Test
@WithClasses({
ErroneousBookMapper.class
})
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = @Diagnostic(
messageRegExp =
"Lifecycle method has multiple matching parameters \\(e\\. g\\. same type\\), in this case " +
"please ensure to name the parameters in the lifecycle and mapping method identical\\. This " +
"lifecycle method will not be used for the mapping method '.*\\.TargetBook mapBook\\(" +
".*\\.SourceBook sourceBook, .*\\.String authorFirstName, .*\\.String authorLastName\\)'\\.",
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 22
)
)
public void testMapperWithoutMatchingParameterNames() {
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
import org.mapstruct.Mapper;
import org.mapstruct.ObjectFactory;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ObjectFactoryBookMapper {
public static final ObjectFactoryBookMapper INSTANCE = Mappers.getMapper( ObjectFactoryBookMapper.class );
public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName);
@ObjectFactory
protected TargetBook createTargetBook(String authorFirstName, String authorLastName) {
TargetBook targetBook = new TargetBook();
targetBook.setAuthorFirstName( authorFirstName );
targetBook.setAuthorLastName( authorLastName );
return targetBook;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
public class SourceBook {
private String isbn;
private String title;
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1457;
public class TargetBook {
private String isbn;
private String title;
private String authorFirstName;
private String authorLastName;
private boolean afterMappingWithoutAuthorName;
private String afterMappingWithOnlyFirstName;
private String afterMappingWithOnlyLastName;
private boolean afterMappingWithDifferentVariableName;
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthorFirstName() {
return authorFirstName;
}
public void setAuthorFirstName(String authorFirstName) {
this.authorFirstName = authorFirstName;
}
public String getAuthorLastName() {
return authorLastName;
}
public void setAuthorLastName(String authorLastName) {
this.authorLastName = authorLastName;
}
public boolean isAfterMappingWithoutAuthorName() {
return afterMappingWithoutAuthorName;
}
public void setAfterMappingWithoutAuthorName(boolean afterMappingWithoutAuthorName) {
this.afterMappingWithoutAuthorName = afterMappingWithoutAuthorName;
}
public String getAfterMappingWithOnlyFirstName() {
return afterMappingWithOnlyFirstName;
}
public void setAfterMappingWithOnlyFirstName(String afterMappingWithOnlyFirstName) {
this.afterMappingWithOnlyFirstName = afterMappingWithOnlyFirstName;
}
public String getAfterMappingWithOnlyLastName() {
return afterMappingWithOnlyLastName;
}
public void setAfterMappingWithOnlyLastName(String afterMappingWithOnlyLastName) {
this.afterMappingWithOnlyLastName = afterMappingWithOnlyLastName;
}
public boolean isAfterMappingWithDifferentVariableName() {
return afterMappingWithDifferentVariableName;
}
public void setAfterMappingWithDifferentVariableName(boolean afterMappingWithDifferentVariableName) {
this.afterMappingWithDifferentVariableName = afterMappingWithDifferentVariableName;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1738;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue1738Mapper {
Issue1738Mapper INSTANCE = Mappers.getMapper( Issue1738Mapper.class );
@Mapping(target = "value", source = "nested.value")
Target map(Source source);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1738;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@RunWith(AnnotationProcessorTestRunner.class)
@IssueKey("1738")
@WithClasses({
Issue1738Mapper.class,
Source.class,
Target.class
})
public class Issue1738Test {
@Test
public void shouldGenerateCorrectSourceNestedMapping() {
Source source = new Source();
Source.Nested<Number> nested = new Source.Nested<>();
source.setNested( nested );
nested.setValue( 100L );
Target target = Issue1738Mapper.INSTANCE.map( source );
assertThat( target.getValue() ).isEqualTo( 100L );
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1738;
/**
* @author Filip Hrisafov
*/
public class Source {
private Nested<? extends Number> nested;
public Nested<? extends Number> getNested() {
return nested;
}
public void setNested(Nested<? extends Number> nested) {
this.nested = nested;
}
public static class Nested<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1738;
/**
* @author Filip Hrisafov
*/
public class Target {
private Number value;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue1742Mapper {
void update(@MappingTarget Target target, Source source);
}

View File

@ -0,0 +1,32 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
/**
* @author Filip Hrisafov
*/
@IssueKey("1742")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses( {
Issue1742Mapper.class,
NestedSource.class,
NestedTarget.class,
Source.class,
Target.class,
} )
public class Issue1742Test {
@Test
public void shouldCompile() {
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
/**
* @author Filip Hrisafov
*/
public class NestedSource {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
/**
* @author Filip Hrisafov
*/
public class NestedTarget {
private String value;
public NestedTarget() {
}
public NestedTarget(Builder builder) {
this.value = getValue();
}
public static Builder builder() {
return new Builder();
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public static class Builder {
private String value;
public Builder value(String value) {
this.value = value;
return this;
}
public NestedTarget create() {
return new NestedTarget(this);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
/**
* @author Filip Hrisafov
*/
public class Source {
private NestedSource nested;
public NestedSource getNested() {
return nested;
}
public void setNested(NestedSource nested) {
this.nested = nested;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1742;
/**
* @author Filip Hrisafov
*/
public class Target {
private NestedTarget nested;
public NestedTarget getNested() {
return nested;
}
public void setNested(NestedTarget nested) {
this.nested = nested;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1751;
/**
* @author Filip Hrisafov
*/
public class Holder<T> {
private final T value;
public Holder(T value) {
this.value = value;
}
public T getValue() {
return value;
}
// If empty is considered as a builder creation method, this method would be the build method and would
// lead to a stackoverflow
@SuppressWarnings("unused")
public Holder<T> duplicate() {
return new Holder<>( value );
}
// This method should not be considered as builder creation method
@SuppressWarnings("unused")
public static <V> Holder<V> empty() {
return new Holder<>( null );
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1751;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue1751Mapper {
Issue1751Mapper INSTANCE = Mappers.getMapper( Issue1751Mapper.class );
Target map(Source source);
default Holder<Target> mapToHolder(Source source) {
return new Holder<>( this.map( source ) );
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1751;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@IssueKey("1772")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Holder.class,
Issue1751Mapper.class,
Source.class,
Target.class
})
public class Issue1751Test {
@Test
public void name() {
Source source = new Source();
source.setValue( "some value" );
Holder<Target> targetHolder = Issue1751Mapper.INSTANCE.mapToHolder( source );
assertThat( targetHolder.getValue() )
.extracting( Target::getValue )
.isEqualTo( "some value" );
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1751;
/**
* @author Filip Hrisafov
*/
public class Source {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1751;
/**
* @author Filip Hrisafov
*/
public class Target {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1772;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
public interface Issue1772Mapper {
Issue1772Mapper INSTANCE = Mappers.getMapper( Issue1772Mapper.class );
@Mapping(target = "nestedTarget.doubleNestedTarget", source = "nestedSource.doublyNestedSourceField" )
Target map(Source source);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1772;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Sjaak Derksen
*/
@IssueKey("1772")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Issue1772Mapper.class,
Source.class,
Target.class,
})
public class Issue1772Test {
@Test
public void shouldCorrectlyMarkSourceAsUsed() {
Source source = new Source();
source.setNestedSource( new Source.NestedSource() );
source.getNestedSource().setDoublyNestedSourceField( 5d );
Target target = Issue1772Mapper.INSTANCE.map( source );
assertThat( target.getNestedTarget().getDoubleNestedTarget() ).isEqualTo( 5d );
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1772;
/**
* @author Sjaak Derksen
*/
public class Source {
private NestedSource nestedSource;
public NestedSource getNestedSource() {
return nestedSource;
}
public void setNestedSource(NestedSource nestedSource) {
this.nestedSource = nestedSource;
}
public static class NestedSource {
private double doublyNestedSourceField;
public double getDoublyNestedSourceField() {
return doublyNestedSourceField;
}
public void setDoublyNestedSourceField(double doublyNestedSourceField) {
this.doublyNestedSourceField = doublyNestedSourceField;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1772;
/**
* @author Sjaak Derksen
*/
public class Target {
private NestedTarget nestedTarget;
public NestedTarget getNestedTarget() {
return nestedTarget;
}
public void setNestedTarget(NestedTarget nestedTarget) {
this.nestedTarget = nestedTarget;
}
public static class NestedTarget {
private double doubleNestedTarget;
public double getDoubleNestedTarget() {
return doubleNestedTarget;
}
public void setDoubleNestedTarget(double doubleNestedTarget) {
this.doubleNestedTarget = doubleNestedTarget;
}
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1790;
import org.mapstruct.MapperConfig;
import org.mapstruct.NullValueCheckStrategy;
/**
* @author Filip Hrisafov
*/
@MapperConfig(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface Issue1790Config {
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1790;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(config = Issue1790Config.class, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface Issue1790Mapper {
Issue1790Mapper INSTANCE = Mappers.getMapper( Issue1790Mapper.class );
void toExistingCar(@MappingTarget Target target, Source source);
}

View File

@ -0,0 +1,40 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1790;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@IssueKey("1790")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Issue1790Config.class,
Issue1790Mapper.class,
Source.class,
Target.class,
})
public class Issue1790Test {
@Test
public void shouldProperlyApplyNullValuePropertyMappingStrategyWhenInheriting() {
Target target = new Target();
target.setName( "My name is set" );
Source source = new Source();
Issue1790Mapper.INSTANCE.toExistingCar( target, source );
assertThat( target.getName() ).isEqualTo( "My name is set" );
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1790;
/**
* @author Filip Hrisafov
*/
public class Source {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1790;
/**
* @author Filip Hrisafov
*/
public class Target {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1797;
import java.util.EnumSet;
/**
* @author Filip Hrisafov
*/
public class Customer {
public enum Type {
ONE, TWO
}
private final EnumSet<Type> types;
public Customer(EnumSet<Type> types) {
this.types = types;
}
public EnumSet<Type> getTypes() {
return types;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1797;
import java.util.EnumSet;
/**
* @author Filip Hrisafov
*/
public class CustomerDto {
public enum Type {
ONE, TWO
}
private EnumSet<Type> types;
public EnumSet<Type> getTypes() {
return types;
}
public void setTypes(EnumSet<Type> types) {
this.types = types;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1797;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue1797Mapper {
Issue1797Mapper INSTANCE = Mappers.getMapper( Issue1797Mapper.class );
CustomerDto map(Customer customer);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1797;
import java.util.EnumSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@IssueKey("1797")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
Customer.class,
CustomerDto.class,
Issue1797Mapper.class
})
public class Issue1797Test {
@Test
public void shouldCorrectlyMapEnumSetToEnumSet() {
Customer customer = new Customer( EnumSet.of( Customer.Type.ONE ) );
CustomerDto customerDto = Issue1797Mapper.INSTANCE.map( customer );
assertThat( customerDto.getTypes() ).containsExactly( CustomerDto.Type.ONE );
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.bugs._1799;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue1799Mapper {
Issue1799Mapper INSTANCE = Mappers.getMapper( Issue1799Mapper.class );
Target map(Source source);
}

Some files were not shown because too many files have changed in this diff Show More