Expected behavior
When several @AfterMapping methods are applicable, they should effectively be chained by the mapper (cf. example).
Actual behavior
Mapper backs out of calling subsequent @AfterMapping methods when any returns non-null value.
Steps to reproduce the problem
import lombok.Data;
import lombok.With;
import org.junit.jupiter.api.Test;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* <pre>
* <org.mapstruct.version>1.6.3</org.mapstruct.version>
* <org.projectlombok.version>1.18.36</org.projectlombok.version>
* </pre>
*/
public class FooTest {
@Test
void test() {
Bar bar = new Bar();
bar.setMsg("foo");
Foo foo = Mappers.getMapper(FooTest.FooMapper.class).toFoo(bar);
assertTrue(foo.msg().contains("foo"), "foo missing");
assertTrue(foo.msg().contains("bar"), "bar missing");
assertTrue(foo.msg().contains("baz"), "baz missing");
}
@Mapper
interface FooMapper {
/**
* <blockquote cite="https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after">All before/after-mapping methods that can be applied to a mapping method will be used.</blockquote>
* Actual: Generated code does not call all @AfterMapping methods if any returns non-null value:
* <pre>
* FooTest.Foo foo1 = new FooTest.Foo( foo );
*
* FooTest.Foo target = addBar( foo1, bar );
* if ( target != null ) {
* return target;
* }
* FooTest.Foo target1 = addBaz( foo1, bar );
* if ( target1 != null ) {
* return target1;
* }
*
* return foo1;
* </pre>
* Expected: method calls are chained (order of calls not guaranteed):
* <pre>
* FooTest.Foo foo1 = new FooTest.Foo( foo );
*
* FooTest.Foo target = addBar( foo1, bar );
* if ( target != null ) {
* foo1 = target;
* }
* FooTest.Foo target1 = addBaz( foo1, bar );
* if ( target1 != null ) {
* foo1 = target1;
* }
*
* return foo1;
* </pre>
*/
Foo toFoo(Bar bar);
@AfterMapping
default Foo addBar(@MappingTarget Foo foo, Bar bar) {
// assume bar is used to calculate the new message
return foo.withMsg(foo.msg() + "bar");
}
@AfterMapping
default Foo addBaz(@MappingTarget Foo foo, Bar bar) {
// assume bar is used to caculate the new message
return foo.withMsg(foo.msg() + "baz");
}
}
record Foo(@With String msg) {
}
@Data
static class Bar {
private String msg;
}
}
MapStruct Version
1.6.3
Expected behavior
When several
@AfterMappingmethods are applicable, they should effectively be chained by the mapper (cf. example).Actual behavior
Mapper backs out of calling subsequent
@AfterMappingmethods when any returns non-null value.Steps to reproduce the problem
MapStruct Version
1.6.3