mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#1454 Support for lifecycle methods on type being built with builders
Add missing support for lifecycle methods with builders: * `@BeforeMapping` with `@TargetType` the type being build * `@AftereMapping` with `@TargetType` the type being build * `@AfterMapping` with `@MappingTarget` the type being build
This commit is contained in:
parent
7c90592d05
commit
6d205e5bc4
@ -29,7 +29,9 @@
|
|||||||
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
|
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
|
||||||
<module name="Translation"/>
|
<module name="Translation"/>
|
||||||
|
|
||||||
<module name="FileLength"/>
|
<module name="FileLength">
|
||||||
|
<property name="max" value="2500"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
<module name="LineLength">
|
<module name="LineLength">
|
||||||
<property name="max" value="120"/>
|
<property name="max" value="120"/>
|
||||||
|
@ -248,9 +248,8 @@ All before/after-mapping methods that *can* be applied to a mapping method *will
|
|||||||
|
|
||||||
The order of the method invocation is determined primarily by their variant:
|
The order of the method invocation is determined primarily by their variant:
|
||||||
|
|
||||||
1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source
|
1. `@BeforeMapping` methods without parameters, a `@MappingTarget` parameter or a `@TargetType` parameter are called before any null-checks on source parameters and constructing a new target bean.
|
||||||
parameters and constructing a new target bean.
|
2. `@BeforeMapping` methods with a `@MappingTarget` parameter are called after constructing a new target bean.
|
||||||
2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean.
|
|
||||||
3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement.
|
3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement.
|
||||||
|
|
||||||
Within those groups, the method invocations are ordered by their location of definition:
|
Within those groups, the method invocations are ordered by their location of definition:
|
||||||
@ -262,4 +261,11 @@ 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:* 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.
|
[NOTE]
|
||||||
|
====
|
||||||
|
Before/After-mapping methods can also be used with builders:
|
||||||
|
|
||||||
|
* `@BeforeMapping` methods with a `@MappingTarget` parameter of the real target will not be invoked because it is only available after the mapping was already performed.
|
||||||
|
* To be able to modify the object that is going to be built, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter. The `build` method is called when the `@AfterMapping` annotated method scope finishes.
|
||||||
|
* The `@AfterMapping` annotated method can also have the real target as `@TargetType` or `@MappingTarget`. It will be invoked after the real target was built (first the methods annotated with `@TargetType`, then the methods annotated with `@MappingTarget`)
|
||||||
|
====
|
@ -71,7 +71,7 @@ public class GolfPlayerDto {
|
|||||||
|
|
||||||
public GolfPlayerDto withName(String name) {
|
public GolfPlayerDto withName(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -94,6 +94,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
private final Type returnTypeToConstruct;
|
private final Type returnTypeToConstruct;
|
||||||
private final BuilderType returnTypeBuilder;
|
private final BuilderType returnTypeBuilder;
|
||||||
private final MethodReference finalizerMethod;
|
private final MethodReference finalizerMethod;
|
||||||
|
private final String finalizedResultName;
|
||||||
|
private final List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType;
|
||||||
|
private final List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType;
|
||||||
|
|
||||||
private final MappingReferences mappingReferences;
|
private final MappingReferences mappingReferences;
|
||||||
|
|
||||||
@ -368,8 +371,35 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
|
|
||||||
MethodReference finalizeMethod = null;
|
MethodReference finalizeMethod = null;
|
||||||
|
|
||||||
|
List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType = new ArrayList<>();
|
||||||
|
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType = new ArrayList<>();
|
||||||
if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
|
if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
|
||||||
finalizeMethod = getFinalizerMethod();
|
finalizeMethod = getFinalizerMethod();
|
||||||
|
|
||||||
|
Type actualReturnType = method.getReturnType();
|
||||||
|
|
||||||
|
beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget(
|
||||||
|
LifecycleMethodResolver.beforeMappingMethods(
|
||||||
|
method,
|
||||||
|
actualReturnType,
|
||||||
|
selectionParameters,
|
||||||
|
ctx,
|
||||||
|
existingVariableNames
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) );
|
||||||
|
|
||||||
|
afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods(
|
||||||
|
method,
|
||||||
|
actualReturnType,
|
||||||
|
selectionParameters,
|
||||||
|
ctx,
|
||||||
|
existingVariableNames
|
||||||
|
) );
|
||||||
|
|
||||||
|
// remove methods without parameters as they are already being invoked
|
||||||
|
removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType );
|
||||||
|
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BeanMappingMethod(
|
return new BeanMappingMethod(
|
||||||
@ -383,12 +413,18 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
returnTypeBuilder,
|
returnTypeBuilder,
|
||||||
beforeMappingMethods,
|
beforeMappingMethods,
|
||||||
afterMappingMethods,
|
afterMappingMethods,
|
||||||
|
beforeMappingReferencesWithFinalizedReturnType,
|
||||||
|
afterMappingReferencesWithFinalizedReturnType,
|
||||||
finalizeMethod,
|
finalizeMethod,
|
||||||
mappingReferences,
|
mappingReferences,
|
||||||
subclasses
|
subclasses
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeMappingReferencesWithoutSourceParameters(List<LifecycleCallbackMethodReference> references) {
|
||||||
|
references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() );
|
||||||
|
}
|
||||||
|
|
||||||
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
|
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
|
||||||
return !isAbstractReturnTypeAllowed()
|
return !isAbstractReturnTypeAllowed()
|
||||||
&& canReturnTypeBeConstructed( returnTypeImpl );
|
&& canReturnTypeBeConstructed( returnTypeImpl );
|
||||||
@ -706,7 +742,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
* Find a factory method for a return type or for a builder.
|
* Find a factory method for a return type or for a builder.
|
||||||
* @param returnTypeImpl the return type implementation to construct
|
* @param returnTypeImpl the return type implementation to construct
|
||||||
* @param @selectionParameters
|
* @param @selectionParameters
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
|
private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
|
||||||
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
|
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
|
||||||
@ -1380,7 +1415,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
* <p>
|
* <p>
|
||||||
* When a target property matches its name with the (nested) source property, it is added to the list if and
|
* When a target property matches its name with the (nested) source property, it is added to the list if and
|
||||||
* only if it is an unprocessed target property.
|
* only if it is an unprocessed target property.
|
||||||
*
|
* <p>
|
||||||
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
|
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
|
||||||
*/
|
*/
|
||||||
private void applyTargetThisMapping() {
|
private void applyTargetThisMapping() {
|
||||||
@ -1766,6 +1801,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
BuilderType returnTypeBuilder,
|
BuilderType returnTypeBuilder,
|
||||||
List<LifecycleCallbackMethodReference> beforeMappingReferences,
|
List<LifecycleCallbackMethodReference> beforeMappingReferences,
|
||||||
List<LifecycleCallbackMethodReference> afterMappingReferences,
|
List<LifecycleCallbackMethodReference> afterMappingReferences,
|
||||||
|
List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType,
|
||||||
|
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
|
||||||
MethodReference finalizerMethod,
|
MethodReference finalizerMethod,
|
||||||
MappingReferences mappingReferences,
|
MappingReferences mappingReferences,
|
||||||
List<SubclassMapping> subclassMappings) {
|
List<SubclassMapping> subclassMappings) {
|
||||||
@ -1783,9 +1820,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
this.propertyMappings = propertyMappings;
|
this.propertyMappings = propertyMappings;
|
||||||
this.returnTypeBuilder = returnTypeBuilder;
|
this.returnTypeBuilder = returnTypeBuilder;
|
||||||
this.finalizerMethod = finalizerMethod;
|
this.finalizerMethod = finalizerMethod;
|
||||||
|
if ( this.finalizerMethod != null ) {
|
||||||
|
this.finalizedResultName =
|
||||||
|
Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames );
|
||||||
|
existingVariableNames.add( this.finalizedResultName );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.finalizedResultName = null;
|
||||||
|
}
|
||||||
this.mappingReferences = mappingReferences;
|
this.mappingReferences = mappingReferences;
|
||||||
|
|
||||||
// intialize constant mappings as all mappings, but take out the ones that can be contributed to a
|
this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType;
|
||||||
|
this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType;
|
||||||
|
|
||||||
|
// initialize constant mappings as all mappings, but take out the ones that can be contributed to a
|
||||||
// parameter mapping.
|
// parameter mapping.
|
||||||
this.mappingsByParameter = new HashMap<>();
|
this.mappingsByParameter = new HashMap<>();
|
||||||
this.constantMappings = new ArrayList<>( propertyMappings.size() );
|
this.constantMappings = new ArrayList<>( propertyMappings.size() );
|
||||||
@ -1830,6 +1878,18 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
return subclassMappings;
|
return subclassMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFinalizedResultName() {
|
||||||
|
return finalizedResultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LifecycleCallbackMethodReference> getBeforeMappingReferencesWithFinalizedReturnType() {
|
||||||
|
return beforeMappingReferencesWithFinalizedReturnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LifecycleCallbackMethodReference> getAfterMappingReferencesWithFinalizedReturnType() {
|
||||||
|
return afterMappingReferencesWithFinalizedReturnType;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PropertyMapping> propertyMappingsByParameter(Parameter parameter) {
|
public List<PropertyMapping> propertyMappingsByParameter(Parameter parameter) {
|
||||||
// issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value
|
// issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value
|
||||||
return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() );
|
return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() );
|
||||||
@ -1882,6 +1942,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
if ( returnTypeBuilder != null ) {
|
if ( returnTypeBuilder != null ) {
|
||||||
types.add( returnTypeBuilder.getOwningType() );
|
types.add( returnTypeBuilder.getOwningType() );
|
||||||
}
|
}
|
||||||
|
for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithFinalizedReturnType ) {
|
||||||
|
types.addAll( reference.getImportTypes() );
|
||||||
|
}
|
||||||
|
for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithFinalizedReturnType ) {
|
||||||
|
types.addAll( reference.getImportTypes() );
|
||||||
|
}
|
||||||
|
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
@ -186,8 +186,8 @@ public abstract class MappingMethod extends ModelElement {
|
|||||||
return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")";
|
return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LifecycleCallbackMethodReference> filterMappingTarget(List<LifecycleCallbackMethodReference> methods,
|
protected static List<LifecycleCallbackMethodReference> filterMappingTarget(
|
||||||
boolean mustHaveMappingTargetParameter) {
|
List<LifecycleCallbackMethodReference> methods, boolean mustHaveMappingTargetParameter) {
|
||||||
if ( methods == null ) {
|
if ( methods == null ) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,12 @@
|
|||||||
|
|
||||||
</#if>
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
|
<#list beforeMappingReferencesWithFinalizedReturnType as callback>
|
||||||
|
<@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/>
|
||||||
|
<#if !callback_has_next>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
<#if !mapNullToDefault>
|
<#if !mapNullToDefault>
|
||||||
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
|
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
|
||||||
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /></#if><#else>null</#if></#if>;
|
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /></#if><#else>null</#if></#if>;
|
||||||
@ -129,7 +135,20 @@
|
|||||||
<#if returnType.name != "void">
|
<#if returnType.name != "void">
|
||||||
|
|
||||||
<#if finalizerMethod??>
|
<#if finalizerMethod??>
|
||||||
return ${resultName}.<@includeModel object=finalizerMethod />;
|
<#if (afterMappingReferencesWithFinalizedReturnType?size > 0)>
|
||||||
|
${returnType.name} ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />;
|
||||||
|
|
||||||
|
<#list afterMappingReferencesWithFinalizedReturnType as callback>
|
||||||
|
<#if callback_index = 0>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
<@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/>
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
return ${finalizedResultName};
|
||||||
|
<#else>
|
||||||
|
return ${resultName}.<@includeModel object=finalizerMethod />;
|
||||||
|
</#if>
|
||||||
<#else>
|
<#else>
|
||||||
return ${resultName};
|
return ${resultName};
|
||||||
</#if>
|
</#if>
|
||||||
|
@ -43,12 +43,16 @@ public class BuilderLifecycleCallbacksTest {
|
|||||||
assertThat( context.getInvokedMethods() )
|
assertThat( context.getInvokedMethods() )
|
||||||
.contains(
|
.contains(
|
||||||
"beforeWithoutParameters",
|
"beforeWithoutParameters",
|
||||||
|
"beforeWithTargetType",
|
||||||
"beforeWithBuilderTargetType",
|
"beforeWithBuilderTargetType",
|
||||||
"beforeWithBuilderTarget",
|
"beforeWithBuilderTarget",
|
||||||
"afterWithoutParameters",
|
"afterWithoutParameters",
|
||||||
"afterWithBuilderTargetType",
|
"afterWithBuilderTargetType",
|
||||||
"afterWithBuilderTarget",
|
"afterWithBuilderTarget",
|
||||||
"afterWithBuilderTargetReturningTarget"
|
"afterWithBuilderTargetReturningTarget",
|
||||||
|
"afterWithTargetType",
|
||||||
|
"afterWithTarget",
|
||||||
|
"afterWithTargetReturningTarget"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,15 @@ public class MappingContext {
|
|||||||
public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) {
|
public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) {
|
||||||
invokedMethods.add( "afterWithBuilderTargetReturningTarget" );
|
invokedMethods.add( "afterWithBuilderTargetReturningTarget" );
|
||||||
|
|
||||||
return orderBuilder.create();
|
// return null, so that @AfterMapping methods on the finalized object will be called in the tests
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterMapping
|
||||||
|
public Order afterWithTargetReturningTarget(@MappingTarget Order order) {
|
||||||
|
invokedMethods.add( "afterWithTargetReturningTarget" );
|
||||||
|
|
||||||
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getInvokedMethods() {
|
public List<String> getInvokedMethods() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user