Skip to content

Commit 72e6b1f

Browse files
authored
mapstruct#2636: defaultValue combined with qualified should not convert if not needed (mapstruct#2637)
1 parent 735a5be commit 72e6b1f

8 files changed

Lines changed: 259 additions & 0 deletions

File tree

core/src/main/java/org/mapstruct/Mapping.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@
218218
* </li>
219219
* </ol>
220220
* <p>
221+
* You can use {@link #qualifiedBy()} or {@link #qualifiedByName()} to force the use of a conversion method
222+
* even when one would not apply. (e.g. {@code String} to {@code String})
223+
* </p>
224+
* <p>
221225
* This attribute can not be used together with {@link #source()}, {@link #defaultValue()},
222226
* {@link #defaultExpression()} or {@link #expression()}.
223227
*
@@ -295,6 +299,8 @@
295299
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
296300
* mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
297301
* error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
302+
* <p>
303+
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
298304
*
299305
* @return the qualifiers
300306
* @see Qualifier
@@ -309,6 +315,8 @@
309315
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
310316
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
311317
* number of qualifiers as no custom annotation types are needed.
318+
* <p>
319+
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
312320
*
313321
* @return One or more qualifier name(s)
314322
* @see #qualifiedBy()

documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,3 +703,73 @@ public interface MovieMapper {
703703
====
704704
Although the used mechanism is the same, the user has to be a bit more careful. Refactoring the name of a defined qualifier in an IDE will neatly refactor all other occurrences as well. This is obviously not the case for changing a name.
705705
====
706+
707+
=== Combining qualifiers with defaults
708+
Please note that the `Mapping#defaultValue` is in essence a `String`, which needs to be converted to the `Mapping#target`. Providing a `Mapping#qualifiedByName` or `Mapping#qualifiedBy` will force MapStruct to use that method. If you want different behavior for the `Mapping#defaultValue`, then please provide an appropriate mapping method. This mapping method needs to transforms a `String` into the desired type of `Mapping#target` and also be annotated so that it can be found by the `Mapping#qualifiedByName` or `Mapping#qualifiedBy`.
709+
710+
.Mapper using defaultValue
711+
====
712+
[source, java, linenums]
713+
[subs="verbatim,attributes"]
714+
----
715+
@Mapper
716+
public interface MovieMapper {
717+
718+
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "DEFAULT" )
719+
GermanRelease toGerman( OriginalRelease movies );
720+
721+
@Named("CategoryToString")
722+
default String defaultValueForQualifier(Category cat) {
723+
// some mapping logic
724+
}
725+
}
726+
----
727+
====
728+
729+
In the above example in case that category is null, the method `CategoryToString( Enum.valueOf( Category.class, "DEFAULT" ) )` will be called and the result will be set to the category field.
730+
731+
.Mapper using defaultValue and default method.
732+
====
733+
[source, java, linenums]
734+
[subs="verbatim,attributes"]
735+
----
736+
@Mapper
737+
public interface MovieMapper {
738+
739+
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "Unknown" )
740+
GermanRelease toGerman( OriginalRelease movies );
741+
742+
@Named("CategoryToString")
743+
default String defaultValueForQualifier(Category cat) {
744+
// some mapping logic
745+
}
746+
747+
@Named("CategoryToString")
748+
default String defaultValueForQualifier(String value) {
749+
return value;
750+
}
751+
}
752+
----
753+
====
754+
In the above example in case that category is null, the method `defaultValueForQualifier( "Unknown" )` will be called and the result will be set to the category field.
755+
756+
If the above mentioned methods do not work there is the option to use `defaultExpression` to set the default value.
757+
758+
.Mapper using defaultExpression
759+
====
760+
[source, java, linenums]
761+
[subs="verbatim,attributes"]
762+
----
763+
@Mapper
764+
public interface MovieMapper {
765+
766+
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultExpression = "java(\"Unknown\")" )
767+
GermanRelease toGerman( OriginalRelease movies );
768+
769+
@Named("CategoryToString")
770+
default String defaultValueForQualifier(Category cat) {
771+
// some mapping logic
772+
}
773+
}
774+
----
775+
====
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
import java.util.UUID;
9+
10+
import org.mapstruct.Mapper;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.Named;
13+
14+
/**
15+
* @author Ben Zegveld
16+
*/
17+
@Mapper
18+
public interface DefaultExpressionUsageMapper {
19+
@Mapping( source = "folder.ancestor.id", target = "parent",
20+
defaultExpression = "java(\"#\")", qualifiedByName = "uuidToString" )
21+
DirectoryNode convert(Folder folder);
22+
23+
@Named( "uuidToString" )
24+
default String uuidToString(UUID id) {
25+
return id.toString();
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
import java.util.UUID;
9+
10+
import org.mapstruct.Mapper;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.Named;
13+
14+
/**
15+
* @author Ben Zegveld
16+
*/
17+
@Mapper
18+
public interface DefaultValueUsageMapper {
19+
@Mapping( source = "folder.ancestor.id", target = "parent",
20+
defaultValue = "00000000-0000-4000-0000-000000000000", qualifiedByName = "uuidToString" )
21+
DirectoryNode convert(Folder folder);
22+
23+
@Named( "uuidToString" )
24+
default String uuidToString(UUID id) {
25+
return id.toString();
26+
}
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
/**
9+
* @author Ben Zegveld
10+
*/
11+
class DirectoryNode {
12+
private String parent;
13+
14+
public void setParent(String parent) {
15+
this.parent = parent;
16+
}
17+
18+
public String getParent() {
19+
return parent;
20+
}
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
import java.util.UUID;
9+
10+
import org.mapstruct.Mapper;
11+
import org.mapstruct.Mapping;
12+
import org.mapstruct.Named;
13+
14+
/**
15+
* @author Ben Zegveld
16+
*/
17+
@Mapper
18+
public interface FaultyDefaultValueUsageMapper {
19+
@Mapping( source = "folder.ancestor.id", target = "parent", defaultValue = "#", qualifiedByName = "uuidToString" )
20+
DirectoryNode convert(Folder folder);
21+
22+
@Named( "uuidToString" )
23+
default String uuidToString(UUID id) {
24+
return id.toString();
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
import java.util.UUID;
9+
10+
/**
11+
* @author Ben Zegveld
12+
*/
13+
class Folder {
14+
private UUID id;
15+
private Folder ancestor;
16+
17+
Folder(UUID id, Folder ancestor) {
18+
this.id = id;
19+
this.ancestor = ancestor;
20+
}
21+
22+
public Folder getAncestor() {
23+
return ancestor;
24+
}
25+
26+
public UUID getId() {
27+
return id;
28+
}
29+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.selection.qualifier.defaults;
7+
8+
import java.util.UUID;
9+
import org.assertj.core.api.Assertions;
10+
11+
import org.mapstruct.ap.testutil.ProcessorTest;
12+
import org.mapstruct.ap.testutil.WithClasses;
13+
import org.mapstruct.factory.Mappers;
14+
15+
/**
16+
* @author Ben Zegveld
17+
*/
18+
@WithClasses( { DirectoryNode.class, Folder.class } )
19+
public class QualifierWithDefaultsTest {
20+
21+
@ProcessorTest
22+
@WithClasses( FaultyDefaultValueUsageMapper.class )
23+
void defaultValueHasInvalidValue() {
24+
Folder rootFolder = new Folder( UUID.randomUUID(), null );
25+
FaultyDefaultValueUsageMapper faultyMapper = Mappers.getMapper( FaultyDefaultValueUsageMapper.class );
26+
27+
Assertions
28+
.assertThatThrownBy( () -> faultyMapper.convert( rootFolder ) )
29+
.isInstanceOf( IllegalArgumentException.class ); // UUID.valueOf should throw this.
30+
}
31+
32+
@ProcessorTest
33+
@WithClasses( DefaultValueUsageMapper.class )
34+
void defaultValuehasUsableValue() {
35+
Folder rootFolder = new Folder( UUID.randomUUID(), null );
36+
37+
DirectoryNode node = Mappers.getMapper( DefaultValueUsageMapper.class ).convert( rootFolder );
38+
39+
Assertions.assertThat( node.getParent() ).isEqualTo( "00000000-0000-4000-0000-000000000000" );
40+
}
41+
42+
@ProcessorTest
43+
@WithClasses( DefaultExpressionUsageMapper.class )
44+
void defaultExpressionDoesNotGetConverted() {
45+
Folder rootFolder = new Folder( UUID.randomUUID(), null );
46+
47+
DirectoryNode node = Mappers.getMapper( DefaultExpressionUsageMapper.class ).convert( rootFolder );
48+
49+
Assertions.assertThat( node.getParent() ).isEqualTo( "#" );
50+
}
51+
}

0 commit comments

Comments
 (0)