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 -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<module name="FileLength"/>
|
||||
<module name="FileLength">
|
||||
<property name="max" value="2500"/>
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<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:
|
||||
|
||||
1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source
|
||||
parameters and constructing a new target bean.
|
||||
2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean.
|
||||
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.
|
||||
2. `@BeforeMapping` methods with a `@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.
|
||||
|
||||
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:* 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) {
|
||||
this.name = name;
|
||||
return this
|
||||
return this;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
@ -94,6 +94,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
private final Type returnTypeToConstruct;
|
||||
private final BuilderType returnTypeBuilder;
|
||||
private final MethodReference finalizerMethod;
|
||||
private final String finalizedResultName;
|
||||
private final List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType;
|
||||
private final List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType;
|
||||
|
||||
private final MappingReferences mappingReferences;
|
||||
|
||||
@ -368,8 +371,35 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
|
||||
MethodReference finalizeMethod = null;
|
||||
|
||||
List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType = new ArrayList<>();
|
||||
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType = new ArrayList<>();
|
||||
if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
|
||||
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(
|
||||
@ -383,12 +413,18 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
returnTypeBuilder,
|
||||
beforeMappingMethods,
|
||||
afterMappingMethods,
|
||||
beforeMappingReferencesWithFinalizedReturnType,
|
||||
afterMappingReferencesWithFinalizedReturnType,
|
||||
finalizeMethod,
|
||||
mappingReferences,
|
||||
subclasses
|
||||
);
|
||||
}
|
||||
|
||||
private void removeMappingReferencesWithoutSourceParameters(List<LifecycleCallbackMethodReference> references) {
|
||||
references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() );
|
||||
}
|
||||
|
||||
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
|
||||
return !isAbstractReturnTypeAllowed()
|
||||
&& canReturnTypeBeConstructed( returnTypeImpl );
|
||||
@ -706,7 +742,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
* Find a factory method for a return type or for a builder.
|
||||
* @param returnTypeImpl the return type implementation to construct
|
||||
* @param @selectionParameters
|
||||
* @return
|
||||
*/
|
||||
private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
|
||||
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
|
||||
@ -1380,7 +1415,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
|
||||
*/
|
||||
private void applyTargetThisMapping() {
|
||||
@ -1766,6 +1801,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
BuilderType returnTypeBuilder,
|
||||
List<LifecycleCallbackMethodReference> beforeMappingReferences,
|
||||
List<LifecycleCallbackMethodReference> afterMappingReferences,
|
||||
List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType,
|
||||
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
|
||||
MethodReference finalizerMethod,
|
||||
MappingReferences mappingReferences,
|
||||
List<SubclassMapping> subclassMappings) {
|
||||
@ -1783,9 +1820,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
this.propertyMappings = propertyMappings;
|
||||
this.returnTypeBuilder = returnTypeBuilder;
|
||||
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;
|
||||
|
||||
// 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.
|
||||
this.mappingsByParameter = new HashMap<>();
|
||||
this.constantMappings = new ArrayList<>( propertyMappings.size() );
|
||||
@ -1830,6 +1878,18 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
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) {
|
||||
// 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() );
|
||||
@ -1882,6 +1942,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
if ( returnTypeBuilder != null ) {
|
||||
types.add( returnTypeBuilder.getOwningType() );
|
||||
}
|
||||
for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithFinalizedReturnType ) {
|
||||
types.addAll( reference.getImportTypes() );
|
||||
}
|
||||
for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithFinalizedReturnType ) {
|
||||
types.addAll( reference.getImportTypes() );
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
@ -186,8 +186,8 @@ public abstract class MappingMethod extends ModelElement {
|
||||
return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")";
|
||||
}
|
||||
|
||||
private List<LifecycleCallbackMethodReference> filterMappingTarget(List<LifecycleCallbackMethodReference> methods,
|
||||
boolean mustHaveMappingTargetParameter) {
|
||||
protected static List<LifecycleCallbackMethodReference> filterMappingTarget(
|
||||
List<LifecycleCallbackMethodReference> methods, boolean mustHaveMappingTargetParameter) {
|
||||
if ( methods == null ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -21,6 +21,12 @@
|
||||
|
||||
</#if>
|
||||
</#list>
|
||||
<#list beforeMappingReferencesWithFinalizedReturnType as callback>
|
||||
<@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/>
|
||||
<#if !callback_has_next>
|
||||
|
||||
</#if>
|
||||
</#list>
|
||||
<#if !mapNullToDefault>
|
||||
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>;
|
||||
@ -129,7 +135,20 @@
|
||||
<#if returnType.name != "void">
|
||||
|
||||
<#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>
|
||||
return ${resultName};
|
||||
</#if>
|
||||
|
@ -43,12 +43,16 @@ public class BuilderLifecycleCallbacksTest {
|
||||
assertThat( context.getInvokedMethods() )
|
||||
.contains(
|
||||
"beforeWithoutParameters",
|
||||
"beforeWithTargetType",
|
||||
"beforeWithBuilderTargetType",
|
||||
"beforeWithBuilderTarget",
|
||||
"afterWithoutParameters",
|
||||
"afterWithBuilderTargetType",
|
||||
"afterWithBuilderTarget",
|
||||
"afterWithBuilderTargetReturningTarget"
|
||||
"afterWithBuilderTargetReturningTarget",
|
||||
"afterWithTargetType",
|
||||
"afterWithTarget",
|
||||
"afterWithTargetReturningTarget"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,15 @@ public class MappingContext {
|
||||
public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) {
|
||||
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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user