Compare commits

...

581 Commits

Author SHA1 Message Date
Yang Tang
e4bc1cdf1e
#3884 Ensure NullValuePropertyMappingStrategy.SET_TO_DEFAULT initializes empty collection/map when target is null
Signed-off-by: TangYang <tangyang9464@163.com>
2025-06-15 08:29:45 +02:00
Filip Hrisafov
c90c93630e #3886: Records do not have property write accessors (apart from the record components) 2025-06-14 23:40:02 +02:00
Filip Hrisafov
f4d1818171 Fix issue key in Issue3807Test 2025-06-14 21:33:26 +02:00
dependabot[bot]
d68819a233 Bump org.springframework:spring-context from 6.2.2 to 6.2.7 in /parent
Bumps [org.springframework:spring-context](https://github.com/spring-projects/spring-framework) from 6.2.2 to 6.2.7.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.2...v6.2.7)

---
updated-dependencies:
- dependency-name: org.springframework:spring-context
  dependency-version: 6.2.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-14 17:02:12 +02:00
Filip Hrisafov
46ce011e4b
Refactor options and add an enum (#3877) 2025-06-01 07:52:18 +02:00
Filip Hrisafov
9847eaf195
#3876: Move Windows and Mac OS builds outside of the main workflow 2025-05-31 18:14:13 +02:00
Yang Tang
ce84c81de2
#3659: Support @AnnotatedWith on decorators
Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-31 17:52:05 +02:00
Yang Tang
bff88297e3
#3807: Properly recognize the type of public generic fields
Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-31 13:29:39 +02:00
Yang Tang
8fc97f5f62
#3806: Properly apply NullValuePropertyMappingStrategy.IGNORE for collections / maps without setters
Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-31 11:13:50 +02:00
Yang Tang
5464c3cff8
#3711: Support generic @Context
Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-31 11:10:24 +02:00
Aleksey Ivashin
6b6600c370
#1958: Add support for ignoring multiple target properties at once 2025-05-25 17:05:18 +02:00
Yang Tang
0badba7003
#3849: Resolve duplicate invocation of overloaded lifecycle methods with inheritance
Add compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector` to disable the deduplication if needed.

Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-25 16:35:38 +02:00
Yang Tang
3a5c70224d
#3809 Fix conditional mapping with @TargetPropertyName failing for nested update mappings
Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-25 15:40:43 +02:00
Şamil Can
42c87d1da9
#3848: Mark String to number as lossy conversion 2025-05-25 15:22:47 +02:00
Dennis Melzer
05f27e96e2
#3852 Initialize Optionals with empty instead of null 2025-05-25 14:58:23 +02:00
Yang Tang
6e6fd01a2e
#3821: Add support for custom exception for subclass exhaustive strategy for @SubclassMapping
---------

Signed-off-by: TangYang <tangyang9464@163.com>
2025-05-17 18:40:51 +02:00
roelmang
fce73aee6a #3729 Support for using inner class Builder without using static factory method 2025-05-11 21:58:47 +02:00
Filip Hrisafov
2fb5776350 Add release notes for next version 2025-05-11 20:07:53 +02:00
zral
602e29083f
#1140 Add warning when target has no properties 2025-05-11 19:59:59 +02:00
Cause Chung
2c84d04463 #3240 Add Support for Java21 SequencedSet and SequencedMap 2025-05-11 17:33:35 +02:00
dependabot[bot]
668eeb5de1 Bump com.google.protobuf:protobuf-java from 3.21.7 to 3.25.5 in /parent
Bumps [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.7 to 3.25.5.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.7...v3.25.5)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-java
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-11 15:57:52 +02:00
fml2
d5f825193c
#3815: chore(docs): Improved wording about @Condition usage 2025-05-10 14:12:20 +02:00
Zegveld
39551242d7
#3786: Improve error message when mapping non-iterable to array 2025-01-24 14:41:35 +01:00
Filip Hrisafov
c08ba4ca7e
Update setup-java and checkout actions to v4 (#3804) 2025-01-22 08:51:49 +01:00
Filip Hrisafov
57d4f88a48
Java EA GitHub Actions improvements (#3803)
* Use Java 17 for building MapStruct (still Java 8 compatible)
* Upgrade to Spring 6 for tests
* Adjust excludes and min tests version for some integration tests
2025-01-20 07:56:19 +01:00
Filip Hrisafov
e0a7d3d0e6 Use latest Maven Wrapper 2025-01-19 15:05:59 +01:00
Tran Ngoc Nhan
8f96291911
Fix documentation typo and code polish (#3787) 2025-01-18 13:04:42 +01:00
Daniel Hammer
4812d2b030
Align README with v1.6.3 release (#3784) 2024-12-07 14:57:13 +01:00
jinhyogyeom
f98a742f98
Standardize Class Names to PascalCase in tests (#3773) 2024-12-01 11:22:51 +01:00
Tran Ngoc Nhan
084cf3abc1
Fix javadoc typos (#3780) 2024-11-29 09:33:41 +01:00
dudxor4587
f3d2b2e65b
Delete unnecessary conditions and modify return statement (#3772) 2024-11-21 23:14:16 +01:00
Roman Obolonskyi
737af6b50a
#3628 Add support for locale parameter for numberFormat and dateFormat 2024-11-17 16:46:59 +01:00
Minji Kim
bee983cd3c
Fix typos in comments (#3769) 2024-11-15 22:13:36 +01:00
hsjni0110
8de18e5a65
fix typos in method and variable names (#3766) 2024-11-13 21:35:20 +01:00
cussle
0df3f6af95
docs & refactor: fix typos and improve readability in multiple classes (#3767)
- AnnotatedConstructor: Fixed a variable name typo (noArgConstructorToInBecluded → noArgConstructorToBeIncluded).
- AbstractBaseBuilder: Improved Javadoc by fixing typos and clarifying wording.
- SourceRhsSelector: Corrected a typo in the class-level Javadoc.
- InheritanceSelector: Enhanced readability by fixing typos and refining comments.
2024-11-13 21:22:57 +01:00
GitHub Action
5bf2b152af Next version 1.7.0-SNAPSHOT 2024-11-09 11:40:01 +00:00
GitHub Action
b4e25e49de Releasing version 1.6.3 2024-11-09 11:31:12 +00:00
Filip Hrisafov
772fae4c77 Prepare release notes for 1.6.3 2024-11-09 12:20:14 +01:00
Filip Hrisafov
efdf435770 #3751 Improve readme to include support for Java 16+ records 2024-11-09 12:17:29 +01:00
Filip Hrisafov
c2bd847599 #3732 Do not generate obsolete imports for LocalDateTime <-> LocalDate conversion 2024-11-09 12:17:13 +01:00
Filip Hrisafov
21fdaa0f82 #3747 Do not generate redundant if condition with constructor mapping and RETURN_DEFAULT null value mapping strategy 2024-11-03 13:46:48 +01:00
Srimathi-S
32f1fea7b5
#3370 Prevent stack overflow error for Immutables with custom builder 2024-11-03 12:52:52 +01:00
Filip Hrisafov
26c5bcd923
Update readme with 1.6.2 2024-09-27 09:15:17 +02:00
GitHub Action
4e0d73db1d Next version 1.7.0-SNAPSHOT 2024-09-16 08:06:43 +00:00
GitHub Action
212607b447 Releasing version 1.6.2 2024-09-16 07:55:31 +00:00
Filip Hrisafov
4fd22d6b26 Prepare release notes for 1.6.2 2024-09-16 09:54:30 +02:00
Filip Hrisafov
a3b4139070 #3717 Fix ClassCastException when getting thrown types for a record accessor 2024-09-16 09:52:57 +02:00
GitHub Action
c74e62a94c Next version 1.7.0-SNAPSHOT 2024-09-15 16:01:43 +00:00
GitHub Action
10d69878a1 Releasing version 1.6.1 2024-09-15 15:52:17 +00:00
Filip Hrisafov
c36f9ae5d1 Prepare release notes for 1.6.1 2024-09-15 17:11:24 +02:00
Filip Hrisafov
3011dd77d7 #3678 before / after mapping for type using builder should only be kept if they are using the actual type in @TargetType or @MappingTarget 2024-09-15 16:45:21 +02:00
Filip Hrisafov
4c1df35ba6 #3703 Use include model instead of manually writing the type name for return type for afterMappingReferencesWithFinalizedReturnType 2024-09-15 16:45:08 +02:00
Filip Hrisafov
2686e852b6
#3661 Use correct type for the Record component read accessors 2024-09-14 01:17:45 +02:00
Filip Hrisafov
12c9c6c1f0 Use email variable for GitHub Bot git email 2024-09-06 16:35:37 +03:00
Filip Hrisafov
796dd94674 Update next release changelog with latest changes 2024-09-02 15:33:08 +02:00
Filip Hrisafov
5232df2707 Try to stabilise MapMappingTest and CarMapperTest 2024-09-02 15:18:37 +02:00
Obolrom
4d9894ba25
#3113 Use LinkedHashSet, LinkedHashSet new factory methods for java >= 19 2024-09-02 10:26:48 +02:00
김기서
23f4802374
Fix method name typo (#3691) 2024-09-02 09:05:01 +02:00
Obolrom
1e89d7497b
Fix method name typo (#3622) 2024-09-02 08:44:17 +02:00
hduelme
c6010c917a Fix typo in readme Maven plugin config 2024-08-31 16:34:54 +02:00
Filip Hrisafov
58dcb9d813
Update latest version and remove some obsolete badges 2024-08-28 11:56:21 +02:00
Filip Hrisafov
c89b616f8c
#3668 Do not apply implicit mappings when using SubclassExhaustiveStrategy#RUNTIME_EXCEPTION and return type is abstract 2024-08-24 12:22:37 +02:00
Filip Hrisafov
6c8a2e184b #3667, #3673 MappingReference should custom MappingOption equality instead of the default only target name based one 2024-08-24 11:29:28 +02:00
Filip Hrisafov
60cd0a4420
#3670 Fix regression when using InheritInverseConfiguration with nested target properties and reversing target = "." 2024-08-24 11:27:52 +02:00
Stefan Simon
b452d7f2c8
#3652 Inverse Inheritance should be possible for ignore-mappings without source 2024-08-18 17:46:35 +02:00
GitHub Action
96d0698417 Next version 1.7.0-SNAPSHOT 2024-08-12 21:08:07 +00:00
GitHub Action
38ec5c5335 Releasing version 1.6.0 2024-08-12 20:59:31 +00:00
thunderhook
81ca739040 #3638 Remove deprecation note of enum mapping via @Mapping 2024-07-25 10:40:12 +02:00
Filip Hrisafov
0f24633d04 Fix update website script to be able to run Linux 2024-07-20 18:05:08 +02:00
GitHub Action
6365a606c1 Next version 1.6.0-SNAPSHOT 2024-07-20 15:45:11 +00:00
GitHub Action
6ef64ea3aa Releasing version 1.6.0.RC1 2024-07-20 15:36:11 +00:00
Filip Hrisafov
bbb9bb403c Fix typo in changelog 2024-07-20 17:21:29 +02:00
Filip Hrisafov
5ce9c537e9 Add release notes 2024-07-20 16:29:17 +02:00
Filip Hrisafov
e2edb1a086 #3504 Add example classes for the passing target type documentation 2024-07-20 16:26:38 +02:00
Stefan Simon
3047760fd0
#3591 Fix duplicate method generation with recursive auto mapping 2024-07-20 16:19:59 +02:00
Obolrom
df49ce5ff9
#3609 Pass bean mapping ignored unmapped source properties to subclass forged methods
Co-authored-by: thunderhook <8238759+thunderhook@users.noreply.github.com>
2024-07-20 14:06:49 +02:00
Filip Hrisafov
66f4288842
#3601 Always use SourceParameterCondition when checking source parameter
This is a breaking change, with this change whenever a source parameter is used as a source for a target property the condition has to apply to source parameters and not properties
2024-07-20 13:53:39 +02:00
thunderhook
52877d36c2 #3634 fix typo in experimental note 2024-07-20 13:06:00 +02:00
thunderhook
eef3bdfca4 #3639 fix documentation link 2024-07-20 13:05:28 +02:00
hduelme
8fa2f40944
Enforce whitespaces around the for colon with CheckStyle (#3642) 2024-07-15 23:18:32 +02:00
Connor McGowan
037da5a1e1
#3635 Fix documentation of unmappedSourcePolicy default (#3637) 2024-07-07 21:19:38 +02:00
Filip Hrisafov
69371708ee
#3574 Respect only explicit mappings but fail on unmapped source fields
* #3574 Respect only explicit mappings but fail on unmapped source fields

This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties.
Source properties are anyway ignored, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy
2024-07-06 10:31:32 +02:00
Filip Hrisafov
babb9dedd9 #3602 Doing a release should reset NEXT_RELEASE_CHANGELOG.md 2024-06-30 14:47:27 +02:00
Filip Hrisafov
baa02bf377 #3602 Fix path for update-website.sh scrip in release workflow 2024-05-11 09:32:41 +02:00
GitHub Action
8a679b325d Next version 1.6.0-SNAPSHOT 2024-05-11 07:10:47 +00:00
GitHub Action
21a8b88a0f Releasing version 1.6.0.Beta2 2024-05-11 07:01:58 +00:00
Filip Hrisafov
8e53b4181f #3602 Fix setup-java action for release workflow 2024-05-11 08:47:39 +02:00
Filip Hrisafov
9a5e6b1892
#3602 Automate release with JReleaser
Add JReleaser for automating the release and add a step for automating the publishing of the website
2024-05-11 08:27:20 +02:00
Filip Hrisafov
b33942a010 #3561 Add test case 2024-05-01 08:15:44 +02:00
Filip Hrisafov
0a2a0aa526
#2610 Add support for conditions on source parameters + fix incorrect use of source parameter in presence check method (#3543)
The new `@SourceParameterCondition` is also going to cover the problems in #3270 and #3459.
The changes in the `MethodFamilySelector` are also fixing #3561
2024-04-29 08:05:52 +02:00
Filip Hrisafov
0a935c67a7 #3565 Presence check methods should not be considered as valid mapping candidates 2024-04-28 20:22:53 +02:00
Zegveld
5fbd36c443
#3577 Improve Mapping#ignoreByDefault documentation 2024-04-28 20:20:11 +02:00
hduelme
8e66445fe9
#3564 Correct issue key for Issue3485Test 2024-04-02 12:00:41 +02:00
Oliver Erhart
e815e3cb1e
#3524 Provide tests with Lombok style super builders 2024-03-10 09:15:37 +01:00
hduelme
2c12e75bfc
#3485 Exception for Mapping to target = "." without source 2024-03-03 18:59:32 +01:00
Filip Hrisafov
c374b5267f
#3360 Do not report unmapped source and target properties when result type is abstract due to runtime exception subclass exhaustive strategy (#3526) 2024-02-11 19:53:38 +01:00
Zegveld
bb1cd63485
#2788 Improve unmapped source properties message for forged methods 2024-02-11 13:29:34 +01:00
Filip Hrisafov
ca1fd0d85d
#3331 Do not handle defined mappings if the result type is abstract due to runtime exception subclass exhaustive strategy (#3487) 2024-02-11 12:51:19 +01:00
Oliver Erhart
8191c850e0
#3323 Support access to the source property name 2024-02-11 10:42:23 +01:00
hduelme
0a43bc088f
Add missing generic type to Javadoc Builder (#3499) 2024-01-28 18:07:57 +01:00
hduelme
90a3ce0b46
Use primitive types in NativeTypes (#3501) 2024-01-28 18:06:42 +01:00
Chanyut Yuvacharuskul
6830258f77
Fix typo in chapter-10-advanced-mapping-options.asciidoc (#3500) 2024-01-28 17:51:57 +01:00
hduelme
6322138028
Add missing generic diamond operator to MappingOptions (#3498) 2024-01-28 17:50:33 +01:00
Filip Hrisafov
60f162ca88
#3463 DefaultBuilderProvider should be able to handle methods in parent interfaces 2024-01-28 17:47:39 +01:00
Filip Hrisafov
6cb126cd7c
#3462 Stream getters should not be treated as alternative setter 2023-12-31 09:34:34 +01:00
Oliver Erhart
7e6fee8714
#1064 Provide a switch to turn off CheckStyle on generated test sources 2023-12-27 13:40:04 +01:00
Filip Hrisafov
6d99f7b8f3
#3473 Add Java 21 and EA to build matrix
Fix tests not running green on Java 21
Update Spring to run correctly on Java 21
2023-12-18 07:35:26 +01:00
mosesonline
fa857e9ff4
bump some lib versions (#3460) 2023-12-10 15:26:47 +01:00
Ravil Galeyev
930f5709b6
#3400 Remove unnecessary casts to long and double 2023-11-29 22:25:22 +01:00
Muhammad Usama
2bb2aefed8
#3413 Using Mapping#expression and Mapping#conditionaQualifiedBy(Name) should lead to compile error 2023-11-24 06:34:15 +01:00
wandi34
2af291ce2f
Fix Typo Mappper in SubclassMapping Doc 2023-11-11 21:48:52 +01:00
Filip Hrisafov
04deac2b3a [maven-release-plugin] prepare for next development iteration 2023-11-04 23:06:17 +01:00
Filip Hrisafov
0ac0c42dbc [maven-release-plugin] prepare release 1.6.0.Beta1 2023-11-04 23:06:16 +01:00
Oliver Erhart
79f01e2de0
Change master to main branch and fix CI status badge (#3423) 2023-11-04 21:35:40 +01:00
Oliver Erhart
b77d321ffb
Added recent contributors (including myself) 2023-11-01 00:01:45 +01:00
Xiu Hong Kooi
5d39314bd2
#3376 support mapping from iterables to collection 2023-10-31 23:55:11 +01:00
Filip Hrisafov
c59eca2a77
#3361 Inheriting mappings should only be applied if the target has been redefined 2023-09-30 21:52:07 +02:00
dependabot[bot]
97c389d58b Bump org.codehaus.plexus:plexus-utils from 3.0.20 to 3.0.24 in /parent
Bumps [org.codehaus.plexus:plexus-utils](https://github.com/codehaus-plexus/plexus-utils) from 3.0.20 to 3.0.24.
- [Release notes](https://github.com/codehaus-plexus/plexus-utils/releases)
- [Commits](https://github.com/codehaus-plexus/plexus-utils/compare/plexus-utils-3.0.20...plexus-utils-3.0.24)

---
updated-dependencies:
- dependency-name: org.codehaus.plexus:plexus-utils
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-30 11:11:47 +02:00
Nikolas Charalambidis
032ee4d77a #3374 Lombok compatibility documentation 2023-09-17 09:44:31 +02:00
GVladi
f61a3acec3 #3089 Improve support for Map attributes for Immutables
Co-Authored-By: thunderhook <8238759+thunderhook@users.noreply.github.com>
2023-09-16 10:56:52 +02:00
Filip Hrisafov
ea997f83ce
#2340 Add FUNDING.yml 2023-08-19 10:09:36 +02:00
Ben Zegveld
8cc2bdd092 #3163: Strip wild card when checking for type assignability 2023-08-13 09:26:53 +02:00
Zegveld
721288140a
Feature/2663 (#3007)
#2663 Fix for 2-step mapping with generics.

---------

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
2023-08-04 10:14:53 +02:00
Filip Hrisafov
812faeef51 Use presence checks for checking source parameter presence
Instead of explicitly doing a null check use a PresenceCheck mechanism for
* BeanMappingMethod
* ContainerMappingMethod
* MapMappingMethod
2023-08-01 15:41:19 +02:00
Anton Erofeev
b2dc64136d
#3292 Simplify expressions, remove redundant expressions 2023-08-01 14:17:50 +02:00
Venkatesh Prasad Kannan
279ab22482
#3309 Add BeanMapping#unmappedSourcePolicy 2023-08-01 09:48:20 +02:00
Filip Hrisafov
28d827a724 Add test case for subclass mapping and bean mapping ignore by default 2023-07-30 10:39:10 +02:00
Lucas Resch
0460c373c0
#3229: Implement InjectionStrategy.SETTER 2023-07-30 10:38:24 +02:00
Roberto Oliveira
230e84efd1 #3340 Update tarLongFileMode to use POSIX 2023-07-29 09:13:55 +02:00
Filip Hrisafov
4abf2d4202 #3317 Do not generate source parameter if check for only primitives 2023-07-09 15:01:06 +02:00
Filip Hrisafov
53c73324ff #3310 Make sure that adders work properly when they are in a generic class 2023-07-09 15:00:57 +02:00
Zegveld
04434af17a
#3296: Skip default and static methods when determining prototype methods 2023-07-08 18:03:56 +02:00
dependabot[bot]
1b1325da6d Bump guava from 29.0-jre to 32.0.0-jre in /parent
Bumps [guava](https://github.com/google/guava) from 29.0-jre to 32.0.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 12:37:01 +02:00
Filip Hrisafov
86919c637f
#3144 Map to Bean should only be possible for single source mappings and if explicitly used in multi source mappings 2023-05-28 09:55:40 +02:00
Filip Hrisafov
d075d9a5b6 Upgrade Freemarker to 2.3.32 2023-05-27 15:55:15 +02:00
Filip Hrisafov
62d1bd3490
#3280 Refactor method selection and use a context to be able to more easily access information 2023-05-27 15:04:34 +02:00
Filip Hrisafov
c2eed45df1 #3126 Apply target this references in the BeanMappingMethod 2023-05-27 11:17:01 +02:00
Oliver Erhart
51f4e7eba9
#3231 Prefer record constructor annotated with @Default 2023-05-24 06:04:13 +02:00
José Carlos Campanero Ortiz
84c443df9c
#3245 Remove redundant null checks in nested properties 2023-05-24 05:44:36 +02:00
Oliver Erhart
6d205e5bc4
#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
2023-05-21 22:49:41 +02:00
paparadva
7c90592d05 #2863 Add validation of String type to @TargetPropertyName 2023-05-21 22:21:34 +02:00
Filip Hrisafov
efaa67aadf #3104 Update methods with NullValuePropertyMappingStrategy.IGNORE should use SetterWrapperForCollectionsAndMapsWithNullCheck 2023-05-20 17:04:59 +02:00
Filip Hrisafov
a89c34f00c #3238 Compile error instead of null pointer exception for invalid ignore with target this 2023-05-20 17:04:22 +02:00
Jason Bodnar
d0e4c48228
#3172 Add mapping between Locale and String 2023-05-08 22:23:03 +02:00
Zegveld
bc5a877121
#3054: Allow abstract return type when all directly sealed subtypes are covered by subclass mappings
Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
2023-05-01 11:54:24 +02:00
dependabot[bot]
be94569791 Bump protobuf-java from 3.21.2 to 3.21.7 in /parent
Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.2 to 3.21.7.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.2...v3.21.7)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-java
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 11:32:21 +02:00
MengxingYuan
f3dac94701
#2781 Remove unmapped source properties when source parameter is directly mapped 2023-05-01 10:28:51 +02:00
Etien Rožnik
4843123e6e
#3165 Support adders for array / iterable to collection 2023-05-01 09:42:58 +02:00
José Carlos Campanero Ortiz
a8df94cc20
#2987 Support for defining Javadoc in the generated mapper implementation 2023-05-01 09:22:59 +02:00
todzhang's cloudsdocker
970984785d
Update one typo in JavaDoc (#2938) 2023-05-01 09:12:23 +02:00
Bragolgirith
d3b4a168b7
#3199 Add support for implicit conversion between java.time.LocalDate and java.time.LocalDateTime 2023-05-01 09:11:05 +02:00
ro0sterjam
931591a385
#3071 Support defining custom processor options by custom SPI 2023-04-30 17:02:39 +02:00
Zegveld
2f78d3f4e2
#3125: Allow subclassmapping inheritance for methods with identical signature
Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
2023-04-30 16:33:00 +02:00
Filip Hrisafov
1ab5db6a27
Update latest release version to 1.5.5.Final 2023-04-23 22:20:05 +02:00
Filip Hrisafov
86a668661a #3159 Do null value check for collections with default expression 2023-04-23 18:30:08 +02:00
Filip Hrisafov
1bc3436c5c #3239 Mapping composition is no longer experimental 2023-04-22 22:31:03 +02:00
Filip Hrisafov
979b35a2f4 #3153 Do not use compress directive to strip whitespaces for value mapping switch method
When using the compress directive it is going to strip whitespaces from the templates as well (i.e. what the user defined in `ValueMapping#source` and / or `ValueMapping#target
2023-04-22 19:32:02 +02:00
Filip Hrisafov
e69843f46e #3158 BeanMapping#ignoreByDefault should work properly for constructor properties 2023-04-22 19:31:30 +02:00
Filip Hrisafov
c6ea69eaf9 #3186 Do not use conversions in 2-step mapping when they are disabled 2023-04-22 19:31:01 +02:00
Filip Hrisafov
d10d48ccff #3248 BeanMapping#ignoreUnmappedSourceProperties should be inherited for @InheritConfiguration 2023-04-22 19:21:28 +02:00
Claudio Nave
b1034e6703 #3202 Improve line location report for invalid qualifier for SubclassMapping 2023-04-22 18:16:07 +02:00
Filip Hrisafov
9adcb06c34 #3236 Add missing jakarta-cdi to the documentation 2023-04-22 17:46:27 +02:00
Filip Hrisafov
00f891be58 #3112 Add missing brackets 2023-04-22 17:14:46 +02:00
Johnny Lim
3c81d36810
Polish links in docs (#3214) 2023-04-16 21:51:33 +02:00
Filip Hrisafov
162fdb44f4
#3195 Update the location for asking questions 2023-04-16 10:17:32 +02:00
Ben Zegveld
03563d8ffe #3174 Also allow multiple SubclassMapping annotations on an annotation @interface. 2023-04-15 18:31:12 +02:00
Filip Hrisafov
6e9fa87ba9
#3142 Nested forged methods should declare throws from lifecycle methods 2023-04-13 21:52:15 +02:00
Filip Hrisafov
2be1f306a1 #3135 BeanMapping#mappingControl should be inherited by forged methods 2023-04-13 21:50:40 +02:00
José Carlos Campanero Ortiz
62c7ce1cdf
#3112 Document <THROW_EXCEPTION> in the reference guide 2023-04-13 21:48:13 +02:00
Claudio Nave
89db26a1af
#3119 Add qualifiedBy and qualifiedByName to SubclassMapping annotation 2023-03-17 09:06:13 +01:00
Claudio Nave
a7ba12676d
#3110 Fix throws declaration for ValueMapping annotated methods (#3122)
#3110 Fix throws declaration for ValueMapping annotated methods
2023-02-05 12:17:02 +01:00
Filip Hrisafov
fd27380185
#2953 Add support for globally defining nullValueMapMappingStrategy and nullValueIterableMappingStrategy 2022-11-13 14:22:25 +01:00
Filip Hrisafov
82b19b0d8a #3077 Add test case 2022-11-13 14:21:03 +01:00
Zegveld
8894cd5935
#3057: limit do not allow self to subclassmappings. (#3063)
* #3057: limit do not allow self to subclassmappings.
* #3057: determine method candidates after all other fields are set in the constructor.

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
Co-authored-by: Filip Hrisafov <filip.hrisafov@gmail.com>
2022-11-04 14:21:05 +01:00
Filip Hrisafov
16e3ceadec
#2952 Do not treat a getter as an alternative write accessor when using CollectionMappingStrategy#TARGET_IMMUTABLE 2022-11-03 23:29:47 +01:00
Orange Add
93f7c3b8ea
#3015 Fix annotations to the forged methods 2022-11-03 23:22:35 +01:00
Orange Add
6a394ad466
#3037 Support @ValueMapping in meta annotations 2022-11-03 23:21:43 +01:00
Orange Add
bb099a55ee
#3040: Allow using only BeanMapping#mappingControl 2022-11-03 22:02:58 +01:00
Filip Hrisafov
81b2f70dac #3039 Upgrade FreeMarker to 2.3.31 2022-11-03 21:49:06 +01:00
Filip Hrisafov
a5d3542c24 Update latest release version to 1.5.3.Final 2022-10-07 20:43:15 +02:00
Filip Hrisafov
481ab36ca3 #3036 Add missing exclude to full feature test 2022-10-03 21:16:25 +02:00
Filip Hrisafov
3a325ea66b
#3036 Fix compile errors when intersection types are used in lifecycle methods 2022-10-03 21:12:19 +02:00
Filip Hrisafov
266c5fa41c
#1216 Pick candidate method with most specific return type
When there are multiple candidate methods returning different types.
We should be able to use the method with the most specific return type (if such a method exists)
2022-10-02 19:20:13 +02:00
Zegveld
411cc24dac
#2955 Fix @AfterMapping with return type not called for update mappings 2022-10-02 09:34:03 +02:00
José Carlos Campanero Ortiz
90a487ac06
#1427 Add support for custom name in Spring stereotype annotations 2022-10-01 13:47:49 +02:00
Filip Hrisafov
af1eab0ece
#2743 BeanMappingOptions should not be inherited for forged methods 2022-09-29 22:10:27 +02:00
Zegveld
608d476ed2
#3018: Use MappingControl with SubclassMapping 2022-09-29 21:35:51 +02:00
Orange Add
e979f506fa
#2773 Copy @Deprecated annotation from method or mapper to implementation 2022-09-28 22:17:59 +02:00
Filip Hrisafov
73e8fd6152
#2840, #2913, #2921: MethodMatcher should not match widening methods
In the MethodMatcher we need to do a special check when the target type is primitive.
The reason for that is that a Long is assignable to a primitive double.
However, doing that means that information can be lost and thus we should not pick such methods.
When the target type is primitive, then a method will be matched if and only if boxed equivalent of the target type is assignable to the boxed equivalent of the candidate return type
2022-09-27 09:35:54 +02:00
Johnny Lim
811cd569bb
Javadoc and documentation polishing (#3026) 2022-09-26 19:02:01 +02:00
Filip Hrisafov
8d670e7db7 #3008 Replace old issue template with new GitHub issue form templates 2022-09-21 22:10:42 +02:00
Prasanth Omanakuttan
d593afed69
Avoid unnecessary unboxing of Boolean (#3003) 2022-09-12 18:45:22 +02:00
José Carlos Campanero Ortiz
97c6755288
#2963 Add support for enum to integer conversion 2022-09-04 13:58:10 +02:00
Iaroslav Bogdanchikov
bbf63ae177
#2730 Add support for Jakarta XML Binding 2022-09-02 22:04:01 +02:00
Filip Hrisafov
21069e5a2e #2895 Generate more readable annotations 2022-09-02 17:24:37 +02:00
Prasanth Omanakuttan
68571de01b Update Typos in java-doc
Closes #2989
2022-08-29 21:32:00 +02:00
Orange Add
3cc2aa7675
#2825 Fix SubclassMapping stackoverflow exception 2022-08-28 12:41:25 +02:00
Orange Add
ac356cab25
#2983 Add @AnnotateWith support to non bean mapping methods 2022-08-28 12:33:46 +02:00
Filip Hrisafov
4708f4b2aa #2950 Disable CDI in the full features tests on Java 8 2022-08-26 19:36:51 +02:00
Filip Hrisafov
4fa66229d9
#2990 Stabilise top level imports 2022-08-26 19:35:54 +02:00
Taihao Zhang
3f798744ac
Fix typo in docs (#2982) 2022-08-24 19:40:08 +02:00
Filip Hrisafov
853e7b27df
#2925 Fix IllegalArgumentException when resolving generic parameters
When resolving the parameter for a method like:

```
<T> Optional<T> from(T value)
```

There was an exception in javac because getting a DeclaredType from an Optional
with a primitive type argument throws an exception.
Therefore, when assigning the type arguments we get the boxed equivalent.
This problem does not happen in the Eclipse compiler
2022-08-24 19:39:09 +02:00
Filip Hrisafov
42500ca755 #2907 Add test case for nested import of array 2022-08-24 19:38:40 +02:00
Filip Hrisafov
71b1a7b8a2 #2945 Stabilise top level imports
Make sure that GeneratedType always gets the imported types
from a Type before adding them
2022-08-24 19:38:40 +02:00
Filip Hrisafov
b24e831cf0
#2937 Fix conditional check for collections with adders 2022-08-24 19:11:52 +02:00
Filip Hrisafov
237543c47c
#2897 Always import types defined in Mapper#imports 2022-08-24 18:59:31 +02:00
Filip Hrisafov
fd4a2548b3 #2928 Add IntelliJ and Eclipse plugin information 2022-08-24 18:55:05 +02:00
Filip Hrisafov
874bf1fd2c
#2950 Add support for Jakarta CDI 2022-08-24 18:38:44 +02:00
Filip Hrisafov
ef4c26b075
#2949 Do not inverse inherit BeanMapping#ignoreUnmappedSourceProperties 2022-08-24 18:36:43 +02:00
Filip Hrisafov
3e0c62ac36 Publish snapshots when on main 2022-08-21 11:26:42 +02:00
Filip Hrisafov
4118a44630 #2974 Fix typos in documentation
Closes #2974
2022-08-21 10:56:25 +02:00
Hakan
54321d6e66
#2839 Keep thrown types when creating a new ForgedMethod with the same arguments
This fixes a compilation error when mapping fields with the same type due to not wrapping in a `try-catch` block
2022-08-20 15:23:32 +02:00
Prasanth Omanakuttan
46900cabde
Update Typos in javadoc (#2958) 2022-08-20 13:37:43 +02:00
Zegveld
17997ef617
#2901: Fix @TargetType annotation on a @Condition annotated method for a Collection value 2022-08-20 13:01:47 +02:00
Zegveld
849085e026
#1574: Support for annotating the generated code with custom annotations
Add new `@AnnotateWith` annotation.
This annotation can be used to instruct the MapStruct processor
to generate custom annotations in the generated code.
2022-08-20 12:59:38 +02:00
Nikola Ivačič
8fa286fe4c #2688: Support accessing to the target property name 2022-08-01 19:17:33 +02:00
dependabot[bot]
62e73464b2 Bump kotlin-stdlib in /integrationtest/src/test/resources/kotlinDataTest
Bumps [kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.3.70 to 1.6.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.6.0/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.3.70...v1.6.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-24 17:55:38 +02:00
Cassius Vinicius de Magalhães
6914889510 Update Chapter 11.2 - Inverse Mappings
Clarification of the inverse mapping usage.
2022-07-14 21:30:16 +02:00
Filip Hrisafov
dd5ac3b637 #2929 Improve documentation for BeanMapping#ignoreByDefault 2022-07-09 21:35:36 +02:00
Iaroslav Bogdanchikov
4b43f9079d #2922 Fix protobuf tests for M1 Macs 2022-07-08 20:35:26 +02:00
fml2
a2b4454a66 fix(docs): No Lombok classes in the runtime 2022-07-01 17:47:55 +02:00
Filip Hrisafov
de8c0c7070 Use UTF-8 when compiling the tests
The test infrastructure that we are using should use UTF-8 for generating the StandardJavaFileManager
2022-06-29 08:37:50 +02:00
Ben Zegveld
88745d151e #2882: target type is now correctly passed on through the MethodReferencePresenceCheck to the MethodReference. 2022-06-18 23:32:26 +02:00
Filip Hrisafov
07d144ebd1 Update readme with latest released 1.5.2.Final release 2022-06-18 19:11:39 +02:00
Filip Hrisafov
1459aabfc3 [maven-release-plugin] prepare for next development iteration 2022-06-18 19:01:18 +02:00
Filip Hrisafov
19973ff818 [maven-release-plugin] prepare release 1.5.2.Final 2022-06-18 19:01:16 +02:00
Sergei Portnov
406ae3fc13
#2891 Fix subclass mapping while superclass has non-empty constructor
Co-authored-by: Filip Hrisafov <filip.hrisafov@gmail.com>
2022-06-18 18:47:07 +02:00
Filip Hrisafov
98eb46aee9
#2880 Fix missing import for array mapping methods
Co-authored-by: Martin Kamp Jensen <martin.kamp.jensen@se.com>
2022-06-18 13:59:03 +02:00
Filip Hrisafov
fa800926e7 #2837 Add support for text blocks in expressions 2022-06-18 12:32:05 +02:00
Filip Hrisafov
22ad9f636d
#2806 Try to stabilise some date conversion tests by locking them on reading the default timezone 2022-06-14 22:03:32 +02:00
Filip Hrisafov
05ae9922ea Update GitHub actions
Run tests with Java 18
Change actions/checkout to v3
Change actions/setup-java to v3
2022-06-14 22:01:43 +02:00
Filip Hrisafov
d7c0d15fe1 Change required Java version for running MapStruct in the readme 2022-06-05 15:58:37 +02:00
Filip Hrisafov
9247c5d7fb #2870 Use codecov action v2 2022-06-05 13:22:37 +02:00
Filip Hrisafov
4c9aa00369 Update readme with latest released 1.5.1.Final release 2022-06-05 08:53:10 +02:00
Filip Hrisafov
20e97714d4 [maven-release-plugin] prepare for next development iteration 2022-06-05 08:42:56 +02:00
Filip Hrisafov
ec9288ce66 [maven-release-plugin] prepare release 1.5.1.Final 2022-06-05 08:42:55 +02:00
Filip Hrisafov
46b78bfe59 #2867 Fix NPE when reporting message on parent mappers 2022-06-05 08:35:31 +02:00
Filip Hrisafov
0726563024 Update readme with latest released 1.5.0.Final release 2022-06-02 23:30:17 +02:00
Filip Hrisafov
5efe5e291c [maven-release-plugin] prepare for next development iteration 2022-06-02 23:11:41 +02:00
Filip Hrisafov
efa11ba312 [maven-release-plugin] prepare release 1.5.0.Final 2022-06-02 23:11:41 +02:00
Filip Hrisafov
a1a0786cf2
#2846 Add test case showing that everything works as expected 2022-06-02 22:14:42 +02:00
Zegveld
0559c47c21
#2739 Enhance documentation around SPI usage 2022-05-30 21:51:57 +02:00
Filip Hrisafov
c945ccd628 #2835 Upgrade jacoco-maven-plugin to latest 0.8.8 to support Java 17 2022-05-30 21:50:13 +02:00
Filip Hrisafov
9769f51756 #2851 Fix typo in readme 2022-05-30 21:49:57 +02:00
Hao Zhang
a4162809a4
Doc: correct the annotation processor version (#2859)
The lombok-mapstruct-binding anotation procossor version given by document will result a compile problem, correct it by the example repository so that work fine
2022-05-28 11:37:21 +02:00
Zegveld
437a70d6df
#2807: Include LifeCycleMethod importTypes in the list of importTypes. (#2808)
Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
2022-04-08 20:57:40 +02:00
Filip Hrisafov
6604617730 #2794 Compile error when condition expression used with constant or expression 2022-04-03 15:20:00 +02:00
Zegveld
03d44b5a87
#2795: use 'includeModel' for the 'sourcePresenceCheckerReference' in the 'UpdateWrapper'. (#2796)
* #2795: use 'includeModel' for the 'sourcePresenceCheckerReference' in the 'UpdateWrapper'.
* Simplify the tests

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
Co-authored-by: Filip Hrisafov <filip.hrisafov@gmail.com>
2022-04-02 18:55:06 +02:00
Zegveld
2473c3eaaa
#2797: Add the nested type import types in the NestedPropertyMappingMethod
* #2797: Reproduction scenario
* Add the nested type import types in the NestedPropertyMappingMethod

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
Co-authored-by: Filip Hrisafov <filip.hrisafov@gmail.com>
2022-04-02 11:59:25 +02:00
dependabot[bot]
07eeea6bc9 Bump spring-beans from 5.3.15 to 5.3.18 in /parent
Bumps [spring-beans](https://github.com/spring-projects/spring-framework) from 5.3.15 to 5.3.18.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.15...v5.3.18)

---
updated-dependencies:
- dependency-name: org.springframework:spring-beans
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-02 11:25:14 +02:00
Filip Hrisafov
08a0313840 [maven-release-plugin] prepare for next development iteration 2022-03-21 08:14:09 +01:00
Filip Hrisafov
7e00af6ff4 [maven-release-plugin] prepare release 1.5.0.RC1 2022-03-21 08:14:08 +01:00
Filip Hrisafov
190b486b79
Add users that have contributed post 1.5.0.Beta2 to copyright.txt 2022-03-19 12:09:30 +01:00
Chris DeLashmutt
ab52867831
#2748 Support mapping map keys with invalid chars for methods 2022-03-19 11:51:19 +01:00
Filip Hrisafov
ad00adfa86
#2538 Allow using 2 step mappings with only one of the 2 methods being qualified 2022-03-12 23:57:17 +01:00
Zegveld
0a69492983
#2755: use raw Type when calling a static method. (#2762) 2022-03-12 18:02:01 +01:00
Zegveld
b6a3aa1512
#2758: fallback to param.variableName if ext.targetBeanName is not present in MethodReference handling. (#2759) 2022-03-12 18:01:15 +01:00
Filip Hrisafov
0b2c7e58b2
Add Christian Kosmowski to the copyright.txt 2022-02-23 15:55:20 +01:00
Zegveld
9b434f80f8
#2715: Updated documentation to reflect impact of conditions on update mappers. (#2740)
* #2715: added an example with an update mapper for Conditional behavior.
2022-02-06 20:05:42 +01:00
Zegveld
7bb85d05c0
2696: Invert @SubclassMappings with @InheritInverseConfiguration. (#2708)
* #2696: Added support for '@InheritInverseConfiguration' with '@SubclassMappings'.
* #2696: Overriding of inverse inheritence implemented. New order has preference over inherited order.
2022-02-06 20:03:23 +01:00
Filip Hrisafov
2a2c11e871
#2629 Use ModuleElement when getting type element
Prior to this MapStruct would only use `Elements#getTypeElement`.
With this PR if the mapper being generated is within a module MapStruct will use that module
for the methods that are needed (getTypeElement and getPackageElement).

Adapt the build to require a minimum Java 11 for building the processor module.
Adapt the GitHub actions to properly run integration tests with Java 8
Ignore Java 9 usages for the animal-sniffer-plugin
2022-01-30 20:52:22 +01:00
Filip Hrisafov
37835a5607
#2677 Use type without bounds when looking for read / presence accessor in a SourceReference 2022-01-30 20:49:05 +01:00
Filip Hrisafov
12070186a4 #2567 Add support for Jakarta Injection
Support for Jakarta is done in 2 ways:

* current jsr330 component model - In this case Jakarta Inject will be used if javax.inject is not available
* new jakarta component model - In this case Jakarta Inject will always be used
2022-01-30 13:31:50 +01:00
Filip Hrisafov
aed3ff5295 #1997 Use builders to construct empty objects in update wrapper 2022-01-30 12:38:42 +01:00
Filip Hrisafov
464adc9143 #2682 Change RetentionPolicy of @DecoratedWith to CLASS
In some circumstances (used with other types of aggregating processors, e.g. Spring)
the Gradle incremental compilation works correctly only for classes annotated with the `CLASS` or `RUNTIME`
retention policy.

The `@DecoratedWith` is the only annotation from MapStruct that was `SOURCE` retention.
With this commit we are changing its retention policy in order for better compatibility with the Gradle Incremental compilation
2022-01-29 17:03:03 +01:00
Filip Hrisafov
5f4d355838
#1661 Add support for globally disabling builders 2022-01-29 11:46:34 +01:00
Filip Hrisafov
aade31f095 #2468 Update needed dependencies for running CDI tests on Java 16+ 2022-01-29 11:25:40 +01:00
Filip Hrisafov
20ff51ebb8
#2728 Add new WithTestDependency annotation for our processor testing
Adding this dependency allows us to dynamically pick the dependencies that we want to have on the test compilation classpath.
It would allow us to more granularly test things with different dependencies, such as javax inject and jakarta inject
2022-01-29 11:13:16 +01:00
Filip Hrisafov
ec30f5d279
#2725 Update tools-gem to 1.0.0.Alpha3 and run GitHub Action with Java 19-ea
Also update Spring to 5.3.15 to be able to run on Java 19
2022-01-29 11:03:44 +01:00
Zegveld
9105041522
#2668: Added support for collections and maps with a no-args constructor (#2671)
#2668: Added support for collections and maps with a no-args constructor. Added a compiler error in case of a collection or map without either a no-arg constructor or a copy constructor.
2022-01-29 00:37:24 +01:00
Justyna
b22efd9ad7
#2674: Add check if method without implementation doesn’t have @AfterMapping / @BeforeMapping annotation 2022-01-24 14:17:24 +01:00
Goni-Dev
0a8e9b738c
#2709 Corrected description for example demonstrating default expression 2022-01-23 17:26:21 +01:00
dependabot[bot]
0f297ae60f Bump protobuf-java from 3.6.0 to 3.16.1 in /parent
Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.6.0 to 3.16.1.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.6.0...v3.16.1)

---
updated-dependencies:
- dependency-name: com.google.protobuf:protobuf-java
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 13:24:54 +01:00
Ben Zegveld
277b6f5d2b #2719: added a note at the builder documentation to point towards the Before-/AfterMapping documentation. 2022-01-22 13:12:06 +01:00
Filip Hrisafov
59c5f40ac3
#2686 Add documentation about when mappers are injected 2022-01-18 18:22:47 +01:00
Filip Hrisafov
f7f65ac1de #2687 Add documentation for NullValueMappingStrategy for collections and maps 2022-01-15 10:34:37 +01:00
Valentin Kulesh
42cfa05c40
#2704 Fix broken reference to constants within nested enums 2021-12-26 11:22:05 +01:00
Ben Zegveld
0a7b8134d4 #2689: documentation: fix example title. 2021-12-25 11:50:48 +01:00
Filip Hrisafov
930b07aab8 [maven-release-plugin] prepare for next development iteration 2021-12-12 12:48:56 +01:00
Filip Hrisafov
0de10ca83c [maven-release-plugin] prepare release 1.5.0.Beta2 2021-12-12 12:48:55 +01:00
Zegveld
ea45666d66
#2673: Fix optional wrapping pattern throwing exception when primitive types are present
MethodMatcher incorrectly reported that a primitive type matches a candidate for a type var
2021-12-11 14:16:19 +01:00
Filip Hrisafov
5de813c16f #2666 Presence Check should be applied to source parameters when used in @Mapping 2021-12-04 12:37:15 +01:00
Filip Hrisafov
00df0bc3d0
#2680 Refactor accessors
Split the `PresenceCheck`s accessor from the current `Accessor`.
Introduce a `ReadAccessor` that would allow us to more easily implement certain things.
Remove `MAP_GET` and `MAP_CONTAINS` from the AccessorType and use the new refactored mechanism
2021-12-03 19:13:41 +01:00
dersvenhesse
754aaf2ef4 [DOCS] Fixed reference variable 2021-12-03 19:10:25 +01:00
Filip Hrisafov
13bc0c023c
#2553 Support source property paths for maps 2021-11-20 08:48:08 +01:00
Filip Hrisafov
29008e12bf #2005 Parameter type should only be checked if we are mapping from a single argument source 2021-11-20 08:00:32 +01:00
Zegveld
72e6b1feb5
#2636: defaultValue combined with qualified should not convert if not needed (#2637) 2021-11-14 20:11:05 +01:00
Filip Hrisafov
735a5bef6a
#2225 Add support for suppressing the generation of the timestamp through Mapper and MapperConfig 2021-11-06 09:21:16 +01:00
Filip Hrisafov
166eb699c7 #1752 Always return mapping target when using update methods with return 2021-10-31 17:41:49 +01:00
Filip Hrisafov
907d605160 #2624 Nested target methods should be inherited for forged Map to Bean methods 2021-10-31 17:11:54 +01:00
Zegveld
ca2529f862
#598: Errors/Warnings now end up in the @Mapper annotated class. (#2634)
#598: Errors/Warnings now end up in the @Mapper annotated class.

Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
2021-10-31 16:46:35 +01:00
Henning Pöttker
e32fc8c283 #2351 NullValueMappingStrategy for Maps and Iterables
With two new parameters for Mapper and MapperConfig, it is now possible to override the nullValueMappingStrategy specifically for MapMapping and IterableMapping.
2021-10-30 13:36:26 +02:00
Filip Hrisafov
80d26a1a9c
#148, #1386, #2593 Only import top level classes
Instead of importing all classes, inner classes will be used through their top level classes only.
This also fixes the problem in #2593 since inner classes are no longer imported but used through their top classes
2021-10-25 08:22:26 +02:00
Filip Hrisafov
564455ee45 #2596 Record components should have the highest priority for read accessors 2021-10-25 08:21:18 +02:00
Filip Hrisafov
935c03e822 #2614 Do not use FQN when mapping Stream to Array 2021-10-25 08:20:59 +02:00
Filip Hrisafov
e86c0faf04 #2611 Scope org.eclipse.tycho:tycho-compiler-jdt under test
Since the removal of the Eclipse Specific compiler workarounds in c2e803403027f3fae92bd15b0ba50ab7df5063e6
the org.eclipse.tycho:tycho-compiler-jdt dependency is no longer needed in the compile code, we only need it for tests
2021-10-24 12:50:49 +02:00
Zegveld
5df6b7a75b
#131, #2438, #366 Add support for Type-Refinement (Downcast) Mapping (#2512)
Add new `@SubclassMapping` for creating Downcast mapping.
When a parent mapping method is annotated with `@SubclassMapping` 
it will now generate an instanceof check inside the parent mapping 
and generate the subclass mappings if they are not manually defined. 

There is also `SubclassExhaustiveStrategy` for controlling what MapStruct should do in case the target type is abstract and there is no suitable way to create it.
2021-10-19 20:44:25 +02:00
valery1707
f167e7a20c Fix typo in JavaDoc 2021-10-16 21:01:20 +02:00
Filip Hrisafov
8b84f5b7d7 #2591 Update dependencies so tests run on Java 18
Update GitHub Actions for tests to run on Java 11, 13, 16, 17 and 18-ea
2021-09-21 22:17:24 +02:00
Filip Hrisafov
b59a23965a #2554 Records should not treat collections as alternative target accessors 2021-09-19 11:42:33 +02:00
Filip Hrisafov
2c23b935db #2541 fix incorrect name for TypeVar with ElementType.TYPE_USE for javac-with-errorprone 2021-09-19 11:41:57 +02:00
Yusuf Kemal Özcan
f0a13bb306 #2555 Add unmappedSourcePolicy annotation processor argument 2021-09-07 22:12:01 +02:00
Tobias Meggendorfer
9057d68cd2
Use DefaultLocale for more stable Issue2544MapperTest (#2569) 2021-08-31 21:45:25 +02:00
Tobias Meggendorfer
9ed4e389f8 #2560 Ignore source properties if ignoreByDefault = true 2021-08-30 21:36:55 +02:00
Daniel Franco
7064e0bc97
Update maven wrapper version to 3.8.2 (#2557) 2021-08-23 16:19:02 +02:00
Adam Szatyin
c52ff812aa
#2552 Add built in conversion between URL and String 2021-08-17 19:21:07 +02:00
Bas Claessen
eb12c485ee
#2515 add ambiguous constructors to ambiguous constructor error message 2021-08-14 09:07:22 +02:00
Bas Claessen
06c416043c
#2515 add ambiguous constructors to ambiguous constructor error message 2021-08-14 09:06:54 +02:00
Henning Pöttker
0d8729767b
Remove remaining references to Hickory (#2511) 2021-08-14 08:38:36 +02:00
Zegveld
c1fa9bd0bd
#2537 Fix incorrect unmapped source property when only defined in Mapping#target 2021-08-14 08:37:20 +02:00
Zegveld
8ad55b164f
#2544: fix missing helper methods for ReverseConversion 2021-08-11 11:35:45 +02:00
Zegveld
196528e578
#2530: fix missing supporting fields for ReverseConversion 2021-08-11 06:49:05 +02:00
Amogh
e6e9b6ce92
#2525 add available transformations to CaseEnumTransformationStrategy exception 2021-07-22 10:08:54 +02:00
Filip Hrisafov
8c554b9556 Change reference from Google Group to GitHub Discussions in reference guide 2021-07-18 15:55:17 +02:00
Filip Hrisafov
43dfd92e05 [maven-release-plugin] prepare for next development iteration 2021-07-18 15:15:44 +02:00
Filip Hrisafov
a91b93f357 [maven-release-plugin] prepare release 1.5.0.Beta1 2021-07-18 15:15:44 +02:00
Sjaak Derksen
a95d1c59c3
#2505 deepclone generates enum mapping method (#2507) 2021-07-03 15:28:10 +02:00
Filip Hrisafov
38744d9f73 Add users that have contributed to 1.5 to the copyright.txt 2021-06-27 18:43:13 +02:00
Christian Kosmowski
985ca2fe64 #1075 Support for Mapping from Map<String, ???> to Bean
Co-authored-by: Filip Hrisafov <filip.hrisafov@gmail.com>
2021-06-27 07:34:37 +02:00
Filip Hrisafov
fb9c7a3ded #2491 Do not use types not part of java.base in MapStruct processor
MapStruct should not use types that are outside of java.base.
This makes sure that no additional dependencies (such as jaxb-api) are needed on the annotation processor path
2021-06-27 07:34:14 +02:00
Filip Hrisafov
c5c292f602 #2501 Add test case 2021-06-27 07:33:45 +02:00
Andrew
1bf698785c
Fix typo in Named.java (#2500) 2021-06-24 19:19:02 +02:00
chaos
4c338fa1db fix(pom): fix gradle repo url 404
original url is 404 replace with new url
2021-06-23 13:02:14 +02:00
Tobias Meggendorfer
5d8fcfa033 #2481 Report ignored source properties which are missing 2021-06-21 19:50:58 +02:00
Filip Hrisafov
845d83e9d5 #2439 Do not throw NPE getting accessors for null typeElement
Provide better error message if the source type has no read properties
2021-06-20 08:55:57 +02:00
Filip Hrisafov
934a47323a #2478 Make sure that forged methods do not generate duplicate method parameters 2021-06-19 15:55:41 +02:00
Sjaak Derksen
7f38efad4d
#2463 Selection stops when method type-args are flipped (#2487) 2021-06-19 13:56:26 +02:00
Filip Hrisafov
55c62ab43f #2108 Make sure Javadoc can be generated with Java 11
Remove the org.jboss.apiviz.APIviz doclet since it is no longer compatible with Java 11.
Add new group in the Javadoc for the MapStruct Processor SPI
Fix Javadoc warnings
2021-06-19 11:50:48 +02:00
Filip Hrisafov
046077f701 #2483: Update Asciidoctor to latest versions 2021-06-19 11:49:38 +02:00
Filip Hrisafov
08016d9ef2
#2446 Use DefaultLocale and DefaultTimeZone from JUnit Pioneer
Configure the JUnit Platform to run the processor tests in parallel by running different test classes in concurrent threads
2021-06-12 10:16:40 +02:00
Filip Hrisafov
9ce9d4fb3a #2375 Add records cross module integration test 2021-06-12 09:23:36 +02:00
Filip Hrisafov
857f87276f #2466 Update dependencies so tests run on Java 16+
Update GitHub Actions for tests to run on Java 11, 13, 16 and 17-ea.
Update Lombok so tests can run on 16+
Add jaxb-runtime dependency to the maven-jaxb2-plugin see https://github.com/highsource/maven-jaxb2-plugin/issues/148
Disable cdi integration test on Java 16+ until we find a solution for them
2021-06-12 09:20:33 +02:00
Filip Hrisafov
70ea65f7aa #2437 Do not visit the same type element twice when retrieving methods
When using the diamond inheritance a TypeElement might be visited twice.
This is however, incorrect. We need to visit a TypeElement exactly once.
2021-05-16 19:08:45 +02:00
Ewald volkert
a6ac4f3fd6 #2329 use fields for custom date formats with DateTimeFormatter in order to optimise its usage
* GetDateTimeFormatterField
** a FieldReference that creates DateTimeFormatters
   for given dateFormat as mapper fields
** variableName is created using given dateFormat

* AbstractJavaTimeToStringConversion
  provides GetDateTimeFormatterField as required helper field
  using DateTimeFormatter instances provided as mapper fields
  by GetDateTimeFormatterField

* ConversionProvider
  might provide supporting fields directly

* Refactoring
  moved/renamed BuiltInFieldReference, BuiltInConstuctorFragment to
package model/common

* MappingBuilderContext provides access to mapper support fields
  (that are independent of mapper methods)

* MappingResolverImpl / MapperCreationProcessor
  process supporting fields provided by ConversionProvider

* HelperMethod
** extended to supply additional template parameters to
   be more flexible in freemarker templates
** extended to support mapper field reference / constructor fragment

* SupportingMappingMethod
** provide templateParameters to freemarker
** hashCode/equals base on name property instead of template name,
   as we use one specific template multiple times with different
   parameters generating multiple methods
** #getSafeField extracted to SupportingField

* SupportingField
** removed hashCode/equals based on template name,
   as we use one specific template multiple times with different
   parameters generating multiple methods
   (superclass equals/hashcode is fine for that)
** added support for template parameters to be more flexible when
  compiling templates

* Tests to verify DateTimeFormatter instance field creation
2021-05-16 13:22:56 +02:00
Lukas Lazar
cc1562c5ad #2132 Add unmappedTargetPolicy to @BeanMapping 2021-05-15 18:01:22 +02:00
Filip Hrisafov
4576103752 #2445 Improve error reporting when EnumTransformationStrategy throws an error during transformation 2021-05-15 13:04:15 +02:00
João Paulo Bassinello
fdf3dcc8ef #2445 Support for case changing enum transformation strategy
Available case transformations: upper, lower, capital
2021-05-15 13:04:15 +02:00
dependabot[bot]
5c22eee6c3 Bump commons-io from 2.6 to 2.7 in /parent
Bumps commons-io from 2.6 to 2.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-01 13:41:46 +02:00
jason.bodnar@blackbaud.com
0cb053df8d #2391 Add implicit conversion between UUID <-> String 2021-05-01 13:28:03 +02:00
Filip Hrisafov
627be53088 Migrate mapstruct core tests to JUnit Jupiter 2021-04-25 13:05:26 +02:00
Filip Hrisafov
2d66f08ee5 Migrate process tests to use new JUnit Jupiter Infrastructure
Update test annotations to be JUnit Jupiter compatible

Replace all Test annotations from tests that are run with the AnnotationProcessorTestRunner with ProcessorTest.
Replace JUnit 4 Test#expected with assertThatThrownBy from AssertJ.
Replace Rule for GeneratedSource with RegisterExtension.
Fix some tests that were not reverting the changes to the default Locale and TimeZone.
Replace usage of org.junit.Assert with equivalent from org.junit.jupiter.api.Assertions or AssertJ.
2021-04-25 13:05:26 +02:00
Filip Hrisafov
5bbd1a78ea Migrate the processor test infrastructure from JUnit 4 to JUnit Jupiter
With JUnit Jupiter it is still not possible to set the ClassLoader for loading the test class.
However, since 5.8 M1 there is a way to hook into the launcher discovery process and change the Current Thread ContextClassLoader which would load the classes with our customer ClassLoader.
Once JUnit Jupiter 201 is resolved we can simplify this.

The CompilationCache is stored in the GlobalCache with the CompilationRequest as key.
This means that even when methods are not executed in some particular order if they have same WithClasses then they would reuse the cache.
2021-04-25 13:05:26 +02:00
Filip Hrisafov
51cdbd67e3 #2051, #2084 Add new @Condition annotation for custom presence check methods 2021-04-25 12:09:23 +02:00
Filip Hrisafov
a2e1404b93 Refactor presence checks to object in order to simplify the conditional mapping 2021-04-25 12:09:23 +02:00
Filip Hrisafov
1c8fff1475 #2423 Use SetterWrapperForCollectionsAndMapsWithNullCheck if the source has a presence check method 2021-04-24 22:01:09 +02:00
Filip Hrisafov
903e6f3f44 #597 Add String <-> StringBuilder conversion in the documentation 2021-04-24 16:47:27 +02:00
Filip Hrisafov
2be536bb65 #2303 Generated code should use iteration order preserving LinkedHash(Map|Set) instead of Hash(Map|Set) 2021-04-24 14:19:01 +02:00
Filip Hrisafov
5f1b3d7862 #2402 Always add source parameter name when constructing the source references for target this 2021-04-23 07:58:24 +02:00
Filip Hrisafov
c9199b7068 #2393 Use includeModel when generating GeneratedType
With this we make sure that the implementation type will have a correct import in case of a clash with another mapper named the same
2021-04-16 08:41:53 +02:00
Silvère Marie
85d3b310f7 Fix method naming 2021-03-30 08:45:17 +02:00
Sjaak Derksen
1187e357c1
#2239 matching generics (#2320) 2021-03-28 17:34:59 +02:00
Filip Hrisafov
e7f6813d9a #2356 Implicitly ignore reverse inherited mappings that do not have read and write methods 2021-03-27 16:12:44 +01:00
Filip Hrisafov
1964c809d8 Fix typo in missing code formatting in documentation
Closes #2385
2021-03-16 20:24:51 +01:00
Filip Hrisafov
197dd4327a #2339 Polish PR #2362
Use MappingEntry for defaultTarget and nullTarget in ValueMappingMethod to simplify certain things
2021-03-15 00:32:02 +01:00
jude.niroshan11@gmail.com
c4135e68ed #2339 Support throwing an exception as an Enum Mapping option 2021-03-15 00:32:01 +01:00
Jude Niroshan
228660c74f
#2366 Update documentation in regards to Java Module System 2021-03-07 14:11:45 +01:00
Jeroen van Wilgenburg
d9fdd86b94 #2368 fix order of target parameter in tests (and removed some spaces) 2021-03-01 20:58:13 +01:00
Jeroen van Wilgenburg
d5703d3ee8 #2368 fix order of target parameter in documentation 2021-03-01 20:58:13 +01:00
Filip Hrisafov
85af901ea7 #2350 Generate core string to enum mapping when AnyRemaining or AnyUnmapped is not used 2021-02-07 11:21:59 +01:00
Filip Hrisafov
07f5189a72 #2347 Do not generate mapper implementation for private mappers
Provide a compiler error message instead of generating code that will not compile
2021-02-07 11:03:04 +01:00
Filip Hrisafov
f4b62ded89 #2352 Add source element type for Iterable mappings
When the Iterable type we are mapping is not generic
(i.e. it is a custom type extending an Iterable) then the source element type
which is included in the loop was not imported.
2021-02-07 10:37:49 +01:00
Filip Hrisafov
630a8da904 Fix a typo in the Mapper#componentModel 2021-02-06 16:44:07 +01:00
Filip Hrisafov
b643061b57 #2277 Add tests with fixtures with testing the generated source code 2021-02-06 16:31:18 +01:00
dmngb
c59ca79e7f
#2277 default component model: mapper reference use singleton INSTANCE if it exists (#2280)
This allows to easily avoid the runtime dependency on mapstruct.jar:
we can avoid Mappers.getMapper(...) for instantiating used mappers if
the code follows the conventional pattern for creating mapper singletons.

Co-authored-by: GIBOU Damien <damien.gibou@biomerieux.com>
2021-02-06 16:10:32 +01:00
Filip Hrisafov
aeadf8cb77
Update readme with latest released 1.4.2.Final release 2021-01-31 14:52:11 +01:00
Filip Hrisafov
08af258533 #2346 Update documentation and readme about MapStruct and Gradle
Remove obsolete net.ltgt.apt
Use com.diffplug.eclipse.apt for Eclipse
IntelliJ works transparently
2021-01-31 09:26:58 +01:00
Filip Hrisafov
0d8bbacc53 #2250, #2324 Do not throw error when qualifier is missing on iterable mapping
With this change we are relaxing the error handling when qualifiers are used in `@Mapping`
and we allow defining qualifiers for collections / array mapping to mean the same as if it was defined on `@IterableMapping`
i.e. apply the qualifier on the element mapping
2021-01-30 09:43:16 +01:00
Sjaak Derksen
8478a5455b
#2278 inherited property ignored due to ignore on nested level (#2332)
Co-authored-by: sjaakd <sjaakderksen@zonnet.nl>
2021-01-23 09:15:46 +01:00
Filip Hrisafov
dfc7528096 Small fixes for Java 16
* Upgrade japicmp-maven-plugin to 0.15.2
* Do not use deprecated for removal Long constructor
* Update Spring to 5.3.3
* Upgrade Lombok to 1.18.16 + add lombok-mapstruct-binding
2021-01-22 08:33:03 +01:00
Tomas Poledny
4223e3ab81 #2255 Add constants for componentModel 2021-01-17 15:13:42 +01:00
Filip Hrisafov
700293f089 #2301 Implicitly ignore forward inherited mappings from different method types 2021-01-01 21:30:25 +01:00
Filip Hrisafov
f84f756a4c #2293 Use MapperConfig instead of MappingConfig in the documentation 2021-01-01 21:29:48 +01:00
Michael Düsterhus
e73dd1b485 Update chapter-2-set-up.asciidoc
See official maven doc on this: https://maven.apache.org/plugins/maven-compiler-plugin/examples/pass-compiler-arguments.html
Otherwise for example intellij doesn't recognize the compiler options on maven import
2020-12-15 19:32:19 +01:00
Filip Hrisafov
84c3bda5a2
#2274, #2023 Fix problems with property mapping using source parameters
Fixes problems when property mapping is using source parameter and has default value / expression or is doing an update
2020-11-22 13:04:26 +01:00
Nikolas Charalambidis
6daea86a1b
Add Lombok subsection in the documentation (#2266) 2020-11-11 22:28:53 +01:00
Filip Hrisafov
6df9243d92 #2253 remove unmapped source properties when source parameter is directly mapped 2020-11-08 09:57:10 +01:00
Filip Hrisafov
75f963adf6 #2263 Fix IndexOutOfBoundsException when resolving TypeVar to a Type 2020-11-08 09:56:15 +01:00
Nikolas Charalambidis
8f9df5b69b
#2258 Fixes vague description of @Default and @ConstructorProperties annotations 2020-11-04 21:39:06 +01:00
Filip Hrisafov
749ded96c1 #2251 Fix incorrect code generated for constructor mapping from implicit source parameter matching 2020-11-01 14:59:44 +01:00
Filip Hrisafov
3256abb79c #2244 Mark mapstruct-processor jar as Spring-Boot-Jar-Type: annotation-processor
Doing this would make sure that starting from Spring Boot 2.4 the mapstruct-processor will not be included in the fat jar produced by the Spring Boot maven plugin if people have it as a maven provided dependency.

This is an alternative if people are not using the maven-compiler-plugin annotationProcessorPaths
2020-10-28 08:54:01 +01:00
Filip Hrisafov
85890dd442 #2245 Local variable should be created when using default value 2020-10-25 14:42:48 +01:00
Kemal Ozcan
e67daa3710 mapstruct#597 Built-In conversion between String and StringBuilder 2020-10-24 17:33:53 +02:00
Saheb Preet Singh
26f62b7ef0 #607 Mapping Iterable<?> object to an object instead of collection 2020-10-21 21:20:46 +02:00
Sjaak Derksen
c2e8034030
861 remove compiler specific workarounds (#2227) 2020-10-21 20:02:28 +02:00
Filip Hrisafov
74d06fea5d #2233 Allow generic of generics in types when matching
e.g. method signature such as
<T> T fromOptional(Optional<T> optional)

when T resolves to another generic class such as Collection<String>
2020-10-21 17:00:05 +02:00
Filip Hrisafov
6102d0cc8e #2236 Different nested target mappings should generate different intermediate methods
Make sure that MappingReferences are taken into consideration when comparing whether 2 mapping methods are equal.
This makes sure that when using nested target mappings that have the same property mappings, but different mappings 2 distinct methods will be created
2020-10-18 22:31:08 +02:00
Filip Hrisafov
50aa9cdbdc Use junit version defined in parent in integration tests 2020-10-17 12:30:43 +02:00
dependabot[bot]
4ddfd2ff51 Bump junit from 4.12 to 4.13.1 in /parent
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-14 15:46:19 +02:00
Filip Hrisafov
0e902d6412 Update versions in readme to 1.4.1.Final 2020-10-11 10:00:24 +02:00
Filip Hrisafov
58dbaee472 [maven-release-plugin] prepare for next development iteration 2020-10-11 09:35:30 +02:00
Filip Hrisafov
d8f22f8311 [maven-release-plugin] prepare release 1.4.1.Final 2020-10-11 09:35:30 +02:00
Filip Hrisafov
2b95e07d8e Turn of Maven connection pooling to avoid connection issues on CI
The problem and solution are explained in https://github.com/actions/virtual-environments/issues/1499#issuecomment-689467080
2020-10-10 10:22:43 +02:00
Filip Hrisafov
53a5c34ed6 #2206, #2214, #2220: Source property should be correctly determined when only target is defined
When having multiple source properties and only target is defined then the same rules should be applied as if there was no mapping:

* First we check for a matching property in any of the source type
* Second we check if the parameter name matches
2020-10-10 09:44:01 +02:00
Filip Hrisafov
a5f49e591e #2221: Fix configuration inheritance when there are multiple matching source parameters of the same type 2020-10-10 09:43:30 +02:00
Filip Hrisafov
823b5edd9f #2213: primitive arrays should be directly mapped (we are cloning them anyways)
Additionally fix problem when annotations `ElementType.TYPE_USE` not handled correctly for javac
2020-10-03 16:54:01 +02:00
Filip Hrisafov
233fc6de98 #2215: Avoid NPE in IntelliJ EAP 2020.3
Starting from IntelliJ 2020.3 (Build 203.4203.26) the ProcessingEnvironment is wrapped in a Proxy class by IntelliJ.
MapStruct should gracefully handle that and not throw an NPE.
Additionally, we should do best effort to put the used compiler in the generated annotation info
2020-10-03 16:47:31 +02:00
Jasper Vandemalle
4480e0f367 Fix minor typos 2020-10-03 00:35:28 +02:00
Filip Hrisafov
2d750193d1 Update versions in readme to 1.4.0.Final 2020-09-26 11:45:27 +02:00
Filip Hrisafov
eb5b8bb71e [maven-release-plugin] prepare for next development iteration 2020-09-26 10:20:47 +02:00
Filip Hrisafov
9973b92ccb [maven-release-plugin] prepare release 1.4.0.Final 2020-09-26 10:20:45 +02:00
Filip Hrisafov
060f17e3e2 #2125 Report an error when source parameter could not be determined from target mapping 2020-09-21 07:55:45 +02:00
Filip Hrisafov
e17e744b20 Dependency upgrades
Upgrades:
* Maven Enforcer Plugin to 3.0.0-M3
* Maven Surefire Plugin to 3.0.0-M5
* Maven Checkstyle Plugin to 3.1.1
* Maven Bundle Plugin to 5.1.1
* Jacoco Maven Plugin to 0.8.6
* Checkstyle to 8.36.1
* JUnit Jipiter to 5.7.0
* AssertJ to 3.17.2
* Guava to 29.0-jre

Fix AssertJ breaking changes
Use Java 8 or Apache Commons IO instead of Guava where possible
Update GitHub Actions to use JDK 14 and JDK 15-ea
2020-09-20 11:37:23 +02:00
Filip Hrisafov
52ab22bbd8 #2197 Strip leading underscores and digits when sanitizing identifier name 2020-09-20 09:46:41 +02:00
Sjaak Derksen
427f5023ef
#2195 @Beanmapping#resultType should be used to construct return type also if it's a builder (#2196) 2020-09-06 16:12:01 +02:00
Filip Hrisafov
8b22654abd [maven-release-plugin] prepare for next development iteration 2020-08-30 16:31:45 +02:00
Filip Hrisafov
c962962546 [maven-release-plugin] prepare release 1.4.0.CR1 2020-08-30 16:31:44 +02:00
Sjaak Derksen
e0eb0f6bb8
#2164 parameter matching should be done based on name when source name is absent (#2193) 2020-08-30 12:49:09 +02:00
Filip Hrisafov
7dcbef349d
#2169 Add support for defining a custom unexpected value mapping exception
Expose definition via:

* `@EnumMapping`
* `@Mapper`
* `@MapperConfig`
* `EnumMappingStrategy` SPI

Rename `EnumNamingStrategy` to `EnumMappingStrategy`
2020-08-29 13:53:30 +02:00
Filip Hrisafov
a9451b1159 #2185 Fix StackOverflow error when recursive use of mapper in Mapper#uses 2020-08-29 11:56:02 +02:00
Filip Hrisafov
c1feafef4c
#2177 Mapping into a generic class / record with a typed constructor argument should work 2020-08-29 11:22:18 +02:00
Filip Hrisafov
3ce9786cf6
#2174 Forged methods should inherit all thrown exceptions from their property mappings 2020-08-29 08:59:20 +02:00
Makoto Oda
99a1fd609c
Documentation: typo? (#2186) 2020-08-25 20:38:19 +02:00
Makoto Oda
18a5f1bdc0
Documentation: Extra phrase inserted? (#2187) 2020-08-25 20:37:02 +02:00
Makoto Oda
e6279d10c7
Is 'r' missing? (#2188) 2020-08-25 20:34:02 +02:00
Filip Hrisafov
ed16d62a91 #2170 Make sure that an import is created for constructor mapping defined variables 2020-08-02 09:36:22 +02:00
Filip Hrisafov
609824037b #2167 Add missing @since 1.4 to new interfaces, classes and methods 2020-08-01 11:43:46 +02:00
Sjaak Derksen
2e2c20fed7
#2161 use short names instead of FQN's in error messages (#2166) 2020-07-24 23:19:47 +02:00
Filip Hrisafov
ef4bfc9aad [maven-release-plugin] prepare for next development iteration 2020-07-19 18:10:13 +02:00
Filip Hrisafov
1d223284c2 [maven-release-plugin] prepare release 1.4.0.Beta3 2020-07-19 18:10:12 +02:00
Filip Hrisafov
0495cb7fa7 #2149 Do not allow using BeanMapping(ignoreByDefault = true) in combination with Mapping(target = ".")
This fixes an ArrayIndexOutOfBoundsException when they were used together
2020-07-19 16:19:52 +02:00
Filip Hrisafov
28017e2b0c Add test case for demonstrating how the ignoreByDefault can be overridden from the base configuration 2020-07-19 16:19:52 +02:00
Sjaak Derksen
36349c49e9
#2156 ambiguous mapping message: location and limit # candidates (#2162) 2020-07-19 15:40:30 +02:00
Filip Hrisafov
cb432fa61b
#2150 Change the rules for how a constructor for mapping is picked
New rules:

1. Constructor annotated with @Default (from any package) has highest precedence
2. If there is a single public constructor then it would be used to construct the object
3. If a parameterless constructor exists then it would be used to construct the object, and the other constructors will be ignored
2020-07-18 18:53:32 +02:00
Filip Hrisafov
6aa39ff428 #2142: Strip leading underscore when sanitizing identifier name 2020-07-18 11:35:25 +02:00
Sjaak Derksen
2a849dca12 #2145 fixing 2 step mapping methods (refactoring) (#2146) 2020-07-18 01:12:21 +02:00
Sjaak Derksen
74f281fa3e #2135 improved messages for not able to select qualified method (#2141)
* #2135 improved messages for not able to select qualified method
2020-07-18 01:12:15 +02:00
Sjaak Derksen
c0d88f86bf #2139 reproducer (#2140)
* #2139 reproducer

* #2139 solution

* #2139 license
2020-07-18 01:12:11 +02:00
Filip Hrisafov
2fede3583d [maven-release-plugin] prepare for next development iteration 2020-07-18 01:12:05 +02:00
Filip Hrisafov
fc4f65ddb6 [maven-release-plugin] prepare release 1.4.0.Beta2 2020-07-05 23:11:41 +02:00
Sjaak Derksen
12ac348609
#2136 performance improvement 2 step mappings (#2138) 2020-07-04 22:06:30 +02:00
Sjaak Derksen
1ce282362c
#2101 inherited properties need to be analysed against redefined properties when inheriting mappings (#2103) 2020-07-04 21:50:20 +02:00
Sjaak Derksen
971abc48c7
#2122 fix for 2 step mapping and generics (#2129) 2020-07-04 18:49:33 +02:00
Sjaak Derksen
81a88bdb6c
#2133 @BeanMapping#resultType should not be applied to forged methods (#2134)
* #2133 reproducer

* #2133 solution
2020-07-04 18:20:09 +02:00
Filip Hrisafov
082704cc55 #2131 Add extra test case 2020-07-04 17:27:55 +02:00
SahinSarkar
13df6a21bc Fixed typo in documentation 2020-07-04 16:55:24 +02:00
Filip Hrisafov
92b4316abd #2117 Add extra test case 2020-06-21 23:47:02 +02:00
Filip Hrisafov
bdc58b9602 #2124 Add extra test case 2020-06-21 23:24:56 +02:00
Filip Hrisafov
ef3cbc1b36 #2121 Add extra test case 2020-06-21 23:19:23 +02:00
Sjaak Derksen
29b82e772c
#2111 extra tests (#2119) 2020-06-14 21:04:31 +02:00
Filip Hrisafov
da37d40152
#2109 Generate correct array assignment for constructor parameter mapping 2020-06-07 16:45:32 +02:00
Filip Hrisafov
d87d75a7a8 [maven-release-plugin] prepare for next development iteration 2020-06-01 13:42:32 +02:00
Filip Hrisafov
72ce5f3bd2 [maven-release-plugin] prepare release 1.4.0.Beta1 2020-06-01 13:42:32 +02:00
Filip Hrisafov
fc5e1ffe6b Upgrade to latest 1.6.2 asciidoc and fix incorrect closing documentation tag 2020-05-30 15:34:54 +02:00
Filip Hrisafov
850a55cd5d #695 Check for direct assignment for iterable, stream or map types should be done on their type parameters instead of the types themselves 2020-05-30 14:27:15 +02:00
Filip Hrisafov
c23592a7fe
Add EnumNamingStrategy SPI (#2100)
Add a new EnumNamingStrategy SPI which can be used for customising the way enums are matched by name.
It is similar to the AccessorNamingStrategy such that it allows implementors to provide a custom way of defining a property.

Related to #796, #1220, #1789 and #1667
2020-05-25 21:31:29 +02:00
Filip Hrisafov
7b5a54971f
Add EnumTransformationStrategy SPI (#2089)
Add a new custom EnumTransformationStrategy SPI which can be used for providing custom way of name based mapping for enums.

Add 4 out of the box transformation strategies:

* prefix - add a prefix to the name based enum mapping
* stripPrefix - remove a prefix from the name based enum mapping
* suffix - add a suffix to the name based enum mapping
* stripSuffix - remove a suffix from the name based enum mapping

This can be achieved by using the new `EnumMapping`

e.g.

Add suffix `_TYPE` to all enums:

`@EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")`

With this it would be possible to achieve what is needed in #796, #1220, #1789.
2020-05-18 07:17:30 +02:00
Filip Hrisafov
b5fe96c9da Add myself as developer 2020-05-17 18:46:33 +02:00
Filip Hrisafov
73a79cf009 #1857 Use flatten-maven-plugin to remove test dependencies from released pom
For some reason when using annotationProcessorPaths IntelliJ includes the provided and test scoped dependencies on the annotation processor paths.
With this change there would be no test dependencies that IntelliJ can add.
2020-05-17 18:45:05 +02:00
Gunnar Morling
f7c1182ae6
Updating my developer URL 2020-05-17 13:27:32 +02:00
Filip Hrisafov
42e0ec395b Add dedicated action for Java EA builds and update main CI to use only 11, 13 and 14 2020-05-17 12:17:36 +02:00
Filip Hrisafov
2b2299a730 #73 Add support for using constructor arguments when instantiating mapping targets
By default the constructor argument names are used to extract the target properties.
If a constructor is annotated with an annotation named `@ConstructorProperties` (from any package) then it would be used to extract the target properties.

If a mapping target has a parameterless empty constructor it would be used to instantiate the target.
When there are multiple constructors then an annotation named `@Default` (from any package) can be used to mark a constructor that should be used by default when instantiating the target.

Supports mapping into Java 14 Records and Kotlin data classes out of the box
2020-04-26 12:44:41 +02:00
Filip Hrisafov
d6ff5204d7 Accessor#getSimpleName should return a String 2020-04-26 12:44:41 +02:00
Sjaak Derksen
7c62aec281
#2077 nullpointer due to no-getter source (#2078) 2020-04-25 17:01:22 +02:00
Filip Hrisafov
3bffe96983 #1159: Do not fail loading AnnotationProcessorContext if loading AstModifyingAnnotationProcessor fails 2020-04-13 17:40:47 +02:00
Filip Hrisafov
c58f80cc5f #2069 Refactor target reference to report errors during bean mapping instead of creation of the target reference
With this we can more easily go in the direction of using constructor to map into target beans.
2020-04-13 13:20:09 +02:00
Filip Hrisafov
1bbc4e1ca8 Use expected.getMessageRegex when no message for the Diagnostic descriptor has been defined 2020-04-12 12:26:07 +02:00
Filip Hrisafov
6797afb647 Use exact message for Diagnostic test messages assertions 2020-04-12 09:55:44 +02:00
Filip Hrisafov
a845197b0b #2056: Handle null TypeMirror in uses and import gracefully
Due to a bug in javac (JDK-8229535) for an annotation with Class values, instead of returning a TypeMirror with TypeKind#ERROR the compiler returns the string "<error>". Eclipse doesn't have this problem currently.
2020-04-10 11:20:20 +02:00
Filip Hrisafov
c410379f83 #1553: Update tycho-compiler-jdt to latest 1.6.0 version
Disable one test in GenericsHierarchyTest for Eclipse on Java 8 due to a bug in the Tycho compiler.
Disable freeBuilder integration test for Eclipse since there are some problems in the second round of annotation processing (no ModelElementProcessor(s) are found)
2020-04-10 09:33:09 +02:00
Filip Hrisafov
853ff7f74f #2060: MapStruct should work properly on the module path 2020-04-05 16:37:51 +02:00
Filip Hrisafov
4f76208c62 #2021 Generate compilable code when Decorator is nested within the Mapper
We need to treat the import of the decoratorType specially when it is nested.
Calling addIfImportRequired is not the most correct approach since it would
lead to checking if the type is to be imported and that would be false
since the Decorator is a nested class within the Mapper.
However, when generating the Decorator this is not needed, because the Decorator is a top level class itself

In a nutshell creating the Decorator should have its own ProcessorContext, but it doesn't
2020-04-05 14:09:23 +02:00
Filip Hrisafov
b7d5e557c1 Checkstyle max line length should be 120 2020-04-05 09:29:00 +02:00
Pawel Radzinski
bbd68baf64
Update chapter-10-advanced-mapping-options.asciidoc (#2050)
Correct some typos and punctuation here and there.
2020-03-30 21:11:07 +02:00
Filip Hrisafov
f8a3924005 Use fix version for maven-processor-plugin
Using the latest (4.0-beta1) does not work on Java 8 since it is compiled with Java 9
2020-03-07 16:44:19 +01:00
Filip Hrisafov
63c2edd333 Reset locale and time zone in every test 2020-03-07 16:44:19 +01:00
Filip Hrisafov
551c104295 #2019 Setup GitHub Actions
Add Maven Wrapper for using in GitHub Actions
Remove Travis
2020-03-07 16:44:19 +01:00
Filip Hrisafov
fe91c6d523 Disable Checkstyle for Target for #2018 test 2020-02-23 19:06:46 +01:00
Filip Hrisafov
b9f86fe6ac #2018 Add test case with properties with underscore verifying that it is working as expected 2020-02-23 17:33:32 +01:00
Filip Hrisafov
f5771c4177 #2014 Add support for mapping from Java 14 records 2020-02-23 13:34:08 +01:00
Sean C. Sullivan
273487f152 maven-compiler-plugin 3.8.1 2020-02-18 07:16:31 +01:00
Tim J. Baumann
a6b3cc364a Fix Javadoc of resolveViaMethodAndConversion
and fix some smaller Javadoc typos.
2020-02-07 17:38:59 +01:00
Robin Clarke
015468b461 Updated versions on readme.md to 1.3.1.Final 2020-02-07 17:37:06 +01:00
Filip Hrisafov
c64e03468e Fix Checkstyle error 2020-02-07 17:35:51 +01:00
Filip Hrisafov
f382903bc6
Add proper attribution for the code for generating the sourceTargetVersion for other JRE 2020-02-04 13:05:53 +01:00
Filip Hrisafov
02a113b2d4
#1308 Switch to JUnit Jupiter and do not use Toolchains for the integration tests (#2013)
The CI should be setup to run on different Java versions, for which the integration tests are going to be run
2020-02-03 21:56:48 +01:00
Filip Hrisafov
95ceba1a1e #2016 Update Checkstyle to 8.29
Adapt checkstyle configuration with new changes:
* Move cacheFile to Checker module
* Move LineLength to Checker module
* Use SuppressWithPlainTextCommentFilter
2020-02-02 14:33:45 +01:00
Sjaak Derksen
58da2d293f
#695 user control over mapping means (direct, method, conversion, 2step) 2020-02-02 10:05:26 +01:00
Sjaak Derksen
2d3761051a
#2011 Introduce gem as replacement of hickory:prism (#2007) 2020-01-27 20:16:04 +01:00
sjaakd
6b49c838eb #1998 simplify usage of inheritance of annotation fields 2020-01-25 09:13:50 +01:00
sjaakd
b84526b24a #1998 preparation move classes to new names 2 2020-01-25 09:13:50 +01:00
sjaakd
2b36c220a3 #1998 preparation move classes to new names 1 2020-01-25 09:13:50 +01:00
Filip Hrisafov
d056570267 #2001 Avoid NPE when checking whether import type element is nested
When the typeToAdd is an array then TypeElement is null and ComponentType is the one that would be imported
2020-01-24 22:02:51 +01:00
Marcel Overdijk
327730127b Updated docs to add compilerArgs instead of replacing them.
When configuring MapStruct compilerArgs *add* them, no *replace*
2020-01-24 21:59:46 +01:00
Sjaak Derksen
076f3ba839
#1995 follow up, small typo 2020-01-10 20:16:25 +01:00
Sjaak Derksen
57cfd35afc
#1995 and mapstruct/mapstruct.org#101 nesting calling custom method (#1999)
Co-authored-by: coffman21 <fire2low@gmail.com>
2020-01-09 20:59:36 +01:00
Sjaak Derksen
c153c8bf5a
#1983 javadoc and doc on @ignore in @InheritInverseConfituration (#1986) 2019-12-23 20:41:38 +01:00
fml2
84062bf78f docs: Fix typo in the builder section 2019-12-09 21:01:19 +01:00
ttzn
779eb1bd47 #1420 Add support for Gradle incremental annotation processing (#1971)
* Add relevant file in META-INF
* Add integration test with Gradle test kit dependencies
* Test with Gradle 5 and Gradle 6
2019-12-08 19:55:16 +01:00
Sjaak Derksen
ee794d042c
#807 meta annotations and duck typing (#1979) 2019-12-07 22:20:11 +01:00
Sjaak Derksen
071e5dc6b2
#1966 extra unit test (#1967) 2019-11-10 20:14:33 +01:00
Andrei Arlou
b26cd4e0cb
#1953 Fix TODO items for: integrationtest-java8Test, integrationtest-lombokBuilderTest, processor-nullvaluemappingTest (#1955) 2019-10-26 22:24:59 +03:00
Andrei Arlou
efea2fb662
#1951 Use Map.computeIfAbsent in NestedTargetPropertyMappingHolder (#1952) 2019-10-20 22:11:17 +03:00
Andrei Arlou
74a2e358e8 #1946 Simplify conditions in classes: Parameter, SelectionParameters, MethodReference, PropertyMapping 2019-10-20 21:01:58 +02:00
Andrei Arlou
e8a7832d5b
#1948 Fix minor warnings with using collection in package model and class util/workarounds/EclipseAsMemberOfWorkaround (#1949) 2019-10-20 14:16:26 +03:00
Andrei Arlou
fe37b01c65
#1944 Remove unused constants in JavaStreamConstants (#1945) 2019-10-20 14:13:38 +03:00
Sjaak Derksen
5fbc86d92f
#1788 allowing enum / not enum by removing checks (#1939) 2019-10-08 21:06:47 +02:00
Sjaak Derksen
507ec1b384
#1557 & #1936 String-to-enum and enum-to-String (#1938)
* #1557 making enum-to-string and string-to-enum

* #1936 <ANY_REMAINING> and <ANY_UNMAPPED> not ignored when inverting
2019-10-07 20:30:20 +02:00
Filip Hrisafov
44ae27d7ca #1881 Use ReportingPolicy#IGNORE for unmappedSourcePolicy when mapping references are for forged methods
This aligns with the way the ReportingPolicy is handled for unmappedTargetPolicy when mapping references are for forged methods
2019-10-06 21:05:25 +02:00
Sjaak Derksen
1cb8291fb9
#1933 ignoreByDefault not inherited from config (#1935) 2019-10-05 15:47:25 +02:00
Filip Hrisafov
b3023b3902
#1828 Use update method when nested property of target is populated with multiple nested properties of source (#1931) 2019-09-29 17:21:20 +02:00
Filip Hrisafov
999d428ad8 #1742 Add test case 2019-09-28 19:15:22 +02:00
Andrei Arlou
81cd439343 #1921 Fix minor warnings in testutil 2019-09-26 20:02:53 +02:00
Sjaak Derksen
0d23f09e37
#1918 multiple target this mappings (#1920) 2019-09-23 20:04:56 +02:00
Andrei Arlou
61f941aa80
#166 Add code-examples to Javadoc of org.mapstruct.* annotations (#1876) 2019-09-22 21:25:57 +03:00
Andrei Arlou
70de843bea
#1911 Change return type MapperConfiguration.getBuilderPrism from Optional<BuilderPrism> to BuilderPrism (#1912) 2019-09-22 20:41:35 +03:00
Sjaak Derksen
ade4f4d7e2
#1821 nullpointer due to @BeanMapping via inheritance (#1822) 2019-09-22 19:25:43 +02:00
Filip Hrisafov
1c4bbba442 #1799 Fluent setters starting with set should work properly 2019-09-22 19:09:04 +02:00
Dainius Figoras
88a8669642 #1406 targeting . as current object 2019-09-22 19:08:05 +02:00
Filip Hrisafov
f0a00eb0d5
Add Dekel Pilli to copyright.txt 2019-09-22 15:07:04 +02:00
dekelpilli
f84f6501c8 #1851 Do not allow using qualifiedBy and qualifiedByName with expression in Mapping 2019-09-22 13:53:19 +02:00
Andrei Arlou
55fe94a93e
#1914 Remove unused method Type.isAnnotatedWith (#1915) 2019-09-22 10:50:38 +03:00
Andrei Arlou
f4c9313972
#1791 Support for conversion between java.time.LocalDateTime and javax.xml.datatype.XMLGregorianCalendar (#1894) 2019-09-21 22:27:45 +03:00
Filip Hrisafov
fcdf852a17 #1904 Do not include private methods when getting enclosed executable elements 2019-09-21 21:10:58 +02:00
Filip Hrisafov
e92e3b45c6 #1904 Create compilation error if a mapper could not be created due to a TypeHierarchyErroneousException 2019-09-21 21:10:58 +02:00
Andrei Arlou
750ce48023 #1792 Annotation processor option for default injection strategy 2019-09-18 19:51:36 +02:00
Filip Hrisafov
d018aed251
#1790 Use mapperPrism.values.nullValuePropertyMappingStrategy when retrieving NullValuePropertyMappingStrategy 2019-09-18 13:09:40 +02:00
Andrei Arlou
447bb00f89
#1773 Update documentation: componentModel=jsr330 with @DecoratedWith not longer experimental (#1907) 2019-09-18 07:18:05 +03:00
Sjaak Derksen
7e0327767f
#1801 Using constructor as builderCreationMethod in custom builder (#1905) 2019-09-15 21:43:22 +02:00
Andrei Arlou
f3b0badcef
Add Andrei Arlou in copyright list (#1906) 2019-09-15 13:37:42 +03:00
Andrei Arlou
c044a87969
#1889 Remove unused parameters from classes TargetTypeSelector, ValueMapping, MethodRetrievalProcessor (#1890) 2019-09-14 04:12:38 +03:00
Andrei Arlou
2043506179
#1895 Refactor class org.mapstruct.ap.internal.util.Filters (#1896) 2019-09-14 03:57:42 +03:00
Andrei Arlou
3868735da7
#1897 Remove unused methods from class SourceMethod (#1898) 2019-09-14 03:43:27 +03:00
Andrei Arlou
7af107c9f2
#1883 Remove not used method "asCollectionOrMap" from TypeFactory (#1884) 2019-09-14 03:24:43 +03:00
Andrei Arlou
f95648cef8
#1088 Refactor constructor ForgedMethod (#1888) 2019-09-14 03:03:13 +03:00
Andrei Arlou
e068564017
#724 Remove JaxbMapper from integration test (#1892) 2019-09-14 02:48:22 +03:00
Sjaak Derksen
e12f9ffd7b
Refactoring of BeanMapping and Source/TargetReferences (common base class) (#1903) 2019-09-13 19:41:06 +02:00
Sjaak Derksen
6d9a50601e
#1867 refactor make the source model only a reflection of the source (#1868) 2019-09-12 20:21:39 +02:00
Andrei Arlou
3fd29f3ff9
Merge pull request #1879 from Captain1653/asccii_doctor_split_reference
#991 Split reference guide source into an adoc file per chapter
2019-08-26 22:45:16 +03:00
Andrei Arlou
ba90c95f23 #991 Split reference guide source into an adoc file per chapter 2019-08-24 20:51:16 +03:00
Andrei Arlou
8e37159a00 Fix minor warnings:
remove unnecessary generic type for collections,
remove unnecessary exceptions from signature,
fix typos
2019-08-24 09:23:58 +02:00
Andrei Arlou
55048ab045 Simplify consistent checks in class Mapping 2019-08-19 20:43:14 +02:00
Andrei Arlou
ee8f9283f2 Simplify conditions in package org.mapstruct.ap.internal.model
and classes: Conversions.java, MappingResolverImpl.java,
EclipseAsMemberOfWorkaround.java
2019-08-18 21:01:05 +02:00
Andrei Arlou
59a5182dab Fix minor warnings:
remove unnecessary generic type for collections,
replace Charset.forName on StandartCharset
2019-08-18 16:24:14 +02:00
Andrei Arlou
dbe761e738 Fix minor warnings in packages test and testutil:
remove unnecessary generic types for collection,
replace numbers months on Calendar constants,
remove unnecessary exceptions from signature test methods
2019-08-18 15:53:50 +02:00
Andrei Arlou
1ee59fd123 Fix minor warnings in tests, packages internal, spi and some others packages:
remove unnecessary generic-type in collections, simplify conditions,
remove unnecessary exception from signature,
replace months numbers on Calendar constants
2019-08-18 12:20:07 +02:00
Andrei Arlou
148466ae3e Fix minor warnings in package test.collection:
remove unnecessary generic-type in collections,
remove unnecessary exception from signature,
replace months numbers on Calendar constants
2019-08-18 12:13:40 +02:00
Andrei Arlou
dc86d5df45 Fix minor warnings in test packages java8stream and nestedbeans:
remove unnecessary generic types from collection,
remove unnecessary throws from test methods,
simplify equals in test dto
2019-08-18 12:11:01 +02:00
Andrei Arlou
281b792cf6 Fix minor warnings in test package bugs:
remove unnecessary generic type for collections,
remove unnecessary exceptions from signature test methods
2019-08-18 10:11:57 +02:00
Sjaak Derksen
66e57b0dfe
#1862 Update @MappingTarget documentation to take builders (#1864) 2019-08-13 18:44:49 +02:00
Andrei Arlou
716b85aa2c Simplify conditions in org.mapstruct.ap.internal.model.source 2019-08-11 22:18:15 +02:00
Andrei Arlou
f02b3d1a42 Remove unnecessary generic type in Collections.empty*,
replace "for" on Stream API,
replace anonymous classes on lambda
2019-08-11 14:48:08 +02:00
Andrei Arlou
e5c5550182 Fix warning addAll in org.mapstruct.ap.internal.model.assignment
Fix typo in org.mapstruct.ap.internal.model.common.Type
2019-08-11 14:45:45 +02:00
Filip Hrisafov
5f2a53afe1 Use trusty dist for JDK 8 build, add OpenJDK13 build and remove OpenJDK12 build 2019-08-11 09:04:54 +02:00
Sjaak Derksen
7bee12138d
#1307 & #1845 remove deprecated enummapping & create ST - refs under bean mapping
#1307 remove deprecated enum mapping 
#1845 create source / target references under bean mapping
2019-07-18 07:20:22 +02:00
Jonathan Kraska
119826982a #1826: fixed null pointer in nested property mapping when using presence checking (#1827) 2019-05-26 17:41:44 +02:00
Sjaak Derksen
d50e41cdbb
#1776 adding a message when no qualifiers are found (#1786) 2019-05-25 18:14:00 +02:00
Matt Drees
9c33199a66 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-05-25 11:34:30 +02:00
Sjaak Derksen
33710584d3
#1742 & #1661 refactoring and making builder optional (#1811) 2019-05-24 23:30:16 +02:00
Filip Hrisafov
60c159a0a1
#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-05-24 23:26:25 +02:00
Sjaak Derksen
648ebceb30
#1819 documentation clarification on obtaining Mapper (#1820) 2019-05-16 22:23:37 +02:00
Filip Hrisafov
1415e32761
#1797 Use EnumSet.noneOf when creating a new instance of EnumSet 2019-05-05 12:42:25 +02:00
Sjaak Derksen
871353fccb
#1784 NullValueMappingStrategy.RETURN_DEFAULT refers wrongly to primitive types (#1785) 2019-04-07 21:38:22 +02:00
Sjaak Derksen
da8c1d0e4b
#1772 unmapped source prop remaining when target and source entry diff (#1778) 2019-04-07 21:37:36 +02:00
Christian Bandowski
f82522fa77
[#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-04-07 18:56:05 +02:00
Christian Bandowski
7a19b2ba75
Merge pull request #1783 from chris922/update-readme
Update Readme / Java 1.8 required
2019-04-03 22:02:06 +02:00
Christian Bandowski
3ca4c3fcef Update Readme 2019-04-03 20:29:10 +02:00
power721
7e112ccc2f Fix typo and code error in documentation (#1779)
* fix typo in documentation "Using builders"

* fix generated code example in stream mapping
2019-04-03 20:21:08 +02:00
Filip Hrisafov
3790f1919a
Make hickory an optional dependency as well (#1765)
Using optional because IntelliJ is picking up hickory as a transitive
dependency and runs the hickory processor in projects using mapstruct-processor.
This happens only when the processor is defined in the maven-compiler annotationProcessorPaths.
This is related to https://youtrack.jetbrains.com/issue/IDEA-200481.
2019-03-30 09:47:35 +01:00
Sjaak Derksen
6c838e6e0c
#1714 Qualifiers should not qualfiy when no qualfier is found (#1739) 2019-03-23 22:08:18 +01:00
Filip Hrisafov
f5ee2c6729
Remove use of prerequisites from parent pom.xml (#1766) 2019-03-23 21:27:15 +01:00
Filip Hrisafov
39481f98c5
Update checkstyle to latest version and replace deprecated methods (#1764) 2019-03-23 21:25:08 +01:00
juliojgd
63c5fc8eff Fix typo in documentation (#1760)
It is "then" instead "than
2019-03-20 21:37:47 +01:00
Sjaak Derksen
fcf96c36eb
#1756 better forged method error based on empty target bean properties (#1757) 2019-03-19 09:44:27 +01:00
Sjaak Derksen
b53741d960
#37 Rudimentary logging in mapstruct (#1741)
* #37 Rudimentary logging in mapstruct

* #37 Rudimentary logging in mapstruct changed order

* #37 rework

* #37 documentation

* #37 comments

* #37 docmentation revisited

* #37 review comments

* #37 unit test

* #37 unit test fixing empty mapper

* #37 rework comments christian

* #37 adding deferred mapper logging

* #37 adding unit test for deferred mapper logging

* #37 processing comments Filip
2019-03-17 16:45:22 +01:00
Filip Hrisafov
bc010a52dc #1738: Use typeBound for the return type of the nested property mapping method and for the definition of the properties within the method 2019-03-15 20:36:03 +01:00
Filip Hrisafov
4f2f546ffc Update plugins to latest versions 2019-03-15 20:35:13 +01:00
Filip Hrisafov
07590cc0d1 Add Open JDK 12 to the Travis CI matrix 2019-03-09 21:50:57 +01:00
Filip Hrisafov
b9b9b60a38 Travis CI deploy snapshots only from the mapstruct/mapstruct repo + skip build of distribution when deploying 2019-03-09 21:39:41 +01:00
Filip Hrisafov
5e96dc8085 Deploy coverage reports only on JDK 8 + deploy snapshots only from JDK 8 build 2019-03-09 21:17:16 +01:00
Filip Hrisafov
3e6ea0ef8f Update Javadoc and set source to 8 for proper compilation on Java 11
See https://bugs.openjdk.java.net/browse/JDK-8212233 for more information
2019-03-09 20:19:28 +01:00
Filip Hrisafov
92bed79144 Fix .travis.yml 2019-03-09 20:05:41 +01:00
Filip Hrisafov
643cc85e50 #1675 Tests should run properly in Java 11
* Add Travis CI build matrix to run build on OpenJDK 11 and EA
* Add regex for the Java 11 Generated annotation in order for the JavaFileAssert to work properly
* Do not use eclipse compiler if running on Java 9+
* Add JDK11 util compiler that reports all errors (also when multiple on same line)
* Whitelist jaxb-api to the test compilation classpath
* Add specific ignores for running some tests with Java 11 (See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+)
2019-03-09 19:54:40 +01:00
Gunnar Morling
ae3758674a #1675 Making MapStruct compileable with OpenJDK 11 2019-03-09 19:54:40 +01:00
Sjaak Derksen
98d86cee84
#1745 refactoring source reference (#1746) 2019-03-09 18:36:33 +01:00
Sjaak Derksen
6c1108d5bb
#1698 Skip "java.lang.Object" as intermediate result additional unit test (#1737) 2019-02-25 20:56:28 +01:00
Sjaak Derksen
51bd43fc1b
#1719 strange error message for selecting collection update methods (#1724) 2019-02-25 19:47:53 +01:00
Xavier RENE-CORAIL
002a8b0562 Add LGTM.com code quality badges 2019-02-19 21:49:25 +01:00
Filip Hrisafov
635cdbf4ea
Add Thibault to copyright.txt 2019-02-14 22:27:43 +01:00
Thibault Duperron
60208b67af #1435 add import to MapperConfig 2019-02-14 22:24:42 +01:00
Sjaak Derksen
23608477b7
#1698 Skip "java.lang.Object" as intermediate result in 2 step mappings (#1712) 2019-02-12 10:17:50 +01:00
Sjaak Derksen
160549a788
#1142 update documentation (#1710)
* #1142 update documentation

* #1142 comment
2019-02-10 22:08:38 +01:00
2508 changed files with 95797 additions and 18567 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: mapstruct
open_collective: mapstruct

45
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Bug report
description: Create a report and help us improve
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Please fill in all required fields with as many details as possible.
- type: textarea
id: expected
attributes:
label: Expected behavior
description: |
Describe what you were expecting MapStruct to do
placeholder: |
Here you can also add the generated code that you would like MapStruct to generate
- type: textarea
id: actual
attributes:
label: Actual behavior
description: |
Describe what you observed MapStruct did instead
placeholder: |
Here you can also add the generated code that MapStruct generated
- type: textarea
id: steps
attributes:
label: Steps to reproduce the problem
description: |
- Share your mapping configuration
- An [MCVE (Minimal Complete Verifiable Example)](https://stackoverflow.com/help/minimal-reproducible-example) can be helpful to provide a complete reproduction case
placeholder: |
Share your MapStruct configuration
validations:
required: true
- type: input
id: mapstruct-version
attributes:
label: MapStruct Version
description: |
Which MapStruct version did you use?
Note: While you can obviously continue using older versions of MapStruct, it may well be that your bug is already fixed. If you're using an older version, please also try to reproduce the bug in the latest version of MapStruct before reporting it.
placeholder: ex. MapStruct 1.5.2
validations:
required: true

16
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,16 @@
contact_links:
- name: MapStruct Discussions
url: https://github.com/mapstruct/mapstruct/discussions
about: Please use the MapStruct GitHub Discussions for open ended discussions and to reach out to the community.
- name: Stack Overflow
url: https://stackoverflow.com/questions/tagged/mapstruct
about: For questions about how to use MapStruct, consider asking your question on Stack Overflow, tagged [mapstruct].
- name: Documentation
url: https://mapstruct.org/documentation/stable/reference/html/
about: The MapStruct reference documentation.
- name: Gitter Chat
url: https://gitter.im/mapstruct/mapstruct-users
about: For realtime communication with the MapStruct community, consider writing in our Gitter chat room.
- name: MapStruct Examples
url: https://github.com/mapstruct/mapstruct-examples
about: Some examples of what can be achieved with MapStruct. (contributions are always welcome)

View File

@ -0,0 +1,43 @@
name: Feature Request
description: Suggest an idea
body:
- type: markdown
attributes:
value: |
Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
- type: textarea
id: use-case
attributes:
label: Use case
description: |
Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature.
placeholder: Describe the use-case here
validations:
required: true
- type: textarea
id: solution
attributes:
label: Generated Code
description: |
Please describe the possible generated code you'd like to see in MapStruct generate.
Please note, it's not always easy to describe a good solution. Describing the use-case above is much more important to us.
placeholder: Describe the possible solution here.
validations:
required: false
- type: textarea
id: workarounds
attributes:
label: Possible workarounds
description: |
Please describe the possible workarounds you've implemented to work around the lacking functionality.
placeholder: Describe the possible workarounds here.
validations:
required: false
- type: input
id: mapstruct-version
attributes:
label: MapStruct Version
description: What MapStruct version and edition did you try?
placeholder: ex. MapStruct 1.5.2
validations:
required: false

82
.github/scripts/update-website.sh vendored Normal file
View File

@ -0,0 +1,82 @@
#!/bin/bash
#
# Copyright MapStruct Authors.
#
# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
#
# env vars:
# VERSION
# GH_BOT_EMAIL
# This script has been inspired by the JReleaser update-website.sh (https://github.com/jreleaser/jreleaser/blob/main/.github/scripts/update-website.sh)
set -e
function computePlainVersion() {
echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2.\3/'
}
function computeMajorMinorVersion() {
echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2/'
}
function isStable() {
local PLAIN_VERSION=$(computePlainVersion $1)
if [ "${PLAIN_VERSION}" == "$1" ]; then
echo "yes"
else
echo "no"
fi
}
STABLE=$(isStable $VERSION)
MAJOR_MINOR_VERSION=$(computeMajorMinorVersion $VERSION)
DEV_VERSION=`grep devVersion config.toml | sed 's/.*"\(.*\)"/\1/'`
MAJOR_MINOR_DEV_VERSION=$(computeMajorMinorVersion $DEV_VERSION)
STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'`
MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION)
echo "📝 Updating versions"
SEDOPTION="-i"
if [[ "$OSTYPE" == "darwin"* ]]; then
SEDOPTION="-i ''"
fi
sed $SEDOPTION -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml
if [ "${STABLE}" == "yes" ]; then
sed $SEDOPTION -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml
if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then
echo "📝 Updating new stable version"
# This means that we have a new stable version and we need to change the order of the releases.
sed $SEDOPTION -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml
NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2))
sed $SEDOPTION -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml
git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml
fi
elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then
echo "📝 Updating new dev version"
# This means that we are updating for a new dev version, but the last dev version is not the one that we are doing.
# Therefore, we need to update add the new data configuration
cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml
sed $SEDOPTION -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml
fi
sed $SEDOPTION -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml
sed $SEDOPTION -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml
git add data/releases/${MAJOR_MINOR_VERSION}.toml
git add config.toml
echo "📝 Updating distribution resources"
tar -xf tmp/mapstruct-${VERSION}-dist.tar.gz --directory tmp
rm -rf static/documentation/${MAJOR_MINOR_VERSION}
cp -R tmp/mapstruct-${VERSION}/docs static/documentation/${MAJOR_MINOR_VERSION}
mv static/documentation/${MAJOR_MINOR_VERSION}/reference/html/mapstruct-reference-guide.html static/documentation/${MAJOR_MINOR_VERSION}/reference/html/index.html
git add static/documentation/${MAJOR_MINOR_VERSION}
git config --global user.email "${GH_BOT_EMAIL}"
git config --global user.name "GitHub Action"
git commit -a -m "Releasing version ${VERSION}"
git push

21
.github/workflows/java-ea.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Java EA
on: [push]
env:
MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
jobs:
test_jdk_ea:
name: 'Linux JDK EA'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Set up JDK'
uses: oracle-actions/setup-java@v1
with:
website: jdk.java.net
release: EA
- name: 'Test'
run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true

20
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Mac OS CI
on: push
env:
MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
jobs:
mac:
name: 'Mac OS'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: 'Set up JDK 21'
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
- name: 'Test'
run: ./mvnw ${MAVEN_ARGS} install

60
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: CI
on: [push, pull_request]
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
jobs:
test_jdk:
strategy:
fail-fast: false
matrix:
java: [21]
name: 'Linux JDK ${{ matrix.java }}'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Set up JDK'
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
- name: 'Test'
run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }}
- name: 'Generate coverage report'
if: matrix.java == 21
run: ./mvnw jacoco:report
- name: 'Upload coverage to Codecov'
if: matrix.java == 21
uses: codecov/codecov-action@v2
- name: 'Publish Snapshots'
if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct'
run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy
integration_test_jdk:
strategy:
fail-fast: false
matrix:
java: [ 8, 11, 17 ]
name: 'Linux JDK ${{ matrix.java }}'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v4
- name: 'Set up JDK 21 for building everything'
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
- name: 'Install Processor'
run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am
- name: 'Set up JDK ${{ matrix.java }} for running integration tests'
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
- name: 'Run integration tests'
run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest

117
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,117 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Release version'
required: true
next:
description: 'Next version'
required: false
jobs:
release:
# This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'zulu'
cache: maven
- name: Set release version
id: version
run: |
RELEASE_VERSION=${{ github.event.inputs.version }}
NEXT_VERSION=${{ github.event.inputs.next }}
PLAIN_VERSION=`echo ${RELEASE_VERSION} | awk 'match($0, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/) { print substr($0, RSTART, RLENGTH); }'`
COMPUTED_NEXT_VERSION="${PLAIN_VERSION}-SNAPSHOT"
if [ -z $NEXT_VERSION ]
then
NEXT_VERSION=$COMPUTED_NEXT_VERSION
fi
./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false
git config --global user.email "${{ vars.GH_BOT_EMAIL }}"
git config --global user.name "GitHub Action"
git commit -a -m "Releasing version $RELEASE_VERSION"
git push
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_ENV
echo "PLAIN_VERSION=$PLAIN_VERSION" >> $GITHUB_ENV
- name: Stage
run: |
export GPG_TTY=$(tty)
./mvnw -ntp -B --file pom.xml \
-Dmaven.site.skip=true -Drelease=true -Ppublication,stage
- name: Release
env:
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
run: |
./mvnw -ntp -B --file pom.xml -pl :mapstruct-parent -Pjreleaser jreleaser:release
- name: JReleaser output
if: always()
uses: actions/upload-artifact@v4
with:
name: jreleaser-release
path: |
parent/target/jreleaser/trace.log
parent/target/jreleaser/output.properties
- name: Reset NEXT_RELEASE_CHANGELOG.md
run: |
echo -e "### Features\n\n### Enhancements\n\n### Bugs\n\n### Documentation\n\n### Build\n" > NEXT_RELEASE_CHANGELOG.md
- name: Set next version
run: |
./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false
sed -i -e "s@project.build.outputTimestamp>.*</project.build.outputTimestamp@project.build.outputTimestamp>\${git.commit.author.time}</project.build.outputTimestamp@g" parent/pom.xml
git config --global user.email "${{ vars.GH_BOT_EMAIL }}"
git config --global user.name "GitHub Action"
git commit -a -m "Next version ${{ env.NEXT_VERSION }}"
git push
update-website:
# This job has been inspired by the JReleaser update-website job (https://github.com/jreleaser/jreleaser/blob/main/.github/workflows/release.yml)
name: Update Website
needs: [release]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: mapstruct/mapstruct.org
ref: main
fetch-depth: 0
token: ${{ secrets.GIT_WEBSITE_ACCESS_TOKEN }}
- name: Download assets
shell: bash
run: |
curl -sL https://raw.githubusercontent.com/mapstruct/mapstruct/main/.github/scripts/update-website.sh --output update-website.sh --create-dirs --output-dir tmp
curl -sL "https://github.com/mapstruct/mapstruct/releases/download/${VERSION}/mapstruct-${VERSION}-dist.tar.gz" --output mapstruct-${VERSION}-dist.tar.gz --create-dirs --output-dir tmp
env:
VERSION: ${{ github.event.inputs.version }}
- name: Commit
shell: bash
env:
VERSION: ${{ github.event.inputs.version }}
GH_BOT_EMAIL: ${{ vars.GH_BOT_EMAIL }}
run: |
chmod +x tmp/update-website.sh
tmp/update-website.sh

20
.github/workflows/windows.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Windows CI
on: push
env:
MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
jobs:
windows:
name: 'Windows'
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: 'Set up JDK 21'
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
- name: 'Test'
run: ./mvnw %MAVEN_ARGS% install

2
.gitignore vendored
View File

@ -23,3 +23,5 @@ test-output
# Misc.
.DS_Store
checkstyle.cache
.flattened-pom.xml

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@ -1,23 +0,0 @@
language: java
jdk:
- oraclejdk8
install: true
script: mvn clean install -DprocessorIntegrationTest.toolchainsFile=etc/toolchains-travis-jenkins.xml -B -V
after_success:
- mvn jacoco:report && bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports"
deploy:
provider: script
script: "test ${TRAVIS_TEST_RESULT} -eq 0 && mvn -s etc/travis-settings.xml -DskipTests=true deploy"
skip_cleanup: true
on:
branch: master
sudo: required
cache:
directories:
- $HOME/.m2
addons:
apt:
packages:
- oracle-java8-installer

View File

@ -5,7 +5,7 @@ You love MapStruct but miss a certain feature? You found a bug and want to repor
* Source code: [http://github.com/mapstruct/mapstruct](http://github.com/mapstruct/mapstruct)
* Issue tracker: [https://github.com/mapstruct/mapstruct/issues](https://github.com/mapstruct/mapstruct/issues)
* Discussions: Join the [mapstruct-users](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) Google group
* CI build: [https://travis-ci.org/mapstruct/mapstruct/](https://travis-ci.org/mapstruct/mapstruct/)
* CI build: [https://github.com/mapstruct/mapstruct/actions/](https://github.com/mapstruct/mapstruct/actions)
MapStruct follows the _Fork & Pull_ development approach. To get started just fork the [MapStruct repository](http://github.com/mapstruct/mapstruct) to your GitHub account and create a new topic branch for each change. Once you are done with your change, submit a [pull request](https://help.github.com/articles/using-pull-requests) against the MapStruct repo.

View File

@ -1,9 +0,0 @@
- [ ] Is this an issue (and hence not a question)?
If this is a question how to use MapStruct there are several resources available.
- Our reference- and API [documentation](http://mapstruct.org/documentation/reference-guide/).
- Our [examples](https://github.com/mapstruct/mapstruct-examples) repository (contributions always welcome)
- Our [FAQ](http://mapstruct.org/faq/)
- [StackOverflow](https://stackoverflow.com), tag MapStruct
- [Gitter](https://gitter.im/mapstruct/mapstruct-users) (you usually get fast feedback)
- Our [google group](https://groups.google.com/forum/#!forum/mapstruct-users)

29
NEXT_RELEASE_CHANGELOG.md Normal file
View File

@ -0,0 +1,29 @@
### Features
* Support for Java 21 Sequenced Collections (#3240)
### Enhancements
* Add support for locale parameter for numberFormat and dateFormat (#3628)
* Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters,
it will be treated as a potential builder.
Builders through static methods on the type have a precedence.
* Behaviour change: Warning when the target has no target properties (#1140)
### Bugs
* Improve error message when mapping non-iterable to array (#3786)
### Documentation
### Build
### Behaviour Change
#### Warning when the target has no target properties
With this change, if the target bean does not have any target properties, a warning will be shown.
This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not.

View File

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

View File

@ -15,6 +15,8 @@
<property name="basedir" value="${basedir}"/>
-->
<property name="cacheFile" value="${checkstyle.cache.file}"/>
<!-- Checks that each Java package has a Javadoc file used for commenting. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
<!--
@ -27,7 +29,16 @@
<!-- 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"/>
<property name="fileExtensions" value="java" />
</module>
<module name="SuppressWithPlainTextCommentFilter"/>
<!-- Following interprets the header file as regular expressions. -->
<!-- <module name="RegexpHeader"/> -->
@ -60,8 +71,6 @@
<module name="TreeWalker">
<property name="cacheFile" value="${checkstyle.cache.file}"/>
<module name="SuppressWarningsHolder"/>
<!-- Checks for Javadoc comments. -->
@ -115,10 +124,6 @@
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength">
<property name="max" value="120"/>
</module>
<module name="MethodLength"/>
<module name="ParameterNumber">
<property name="max" value="10"/>
</module>
@ -141,7 +146,9 @@
</module>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<module name="WhitespaceAround">
<property name="ignoreEnhancedForColon" value="false"/>
</module>
<module name="EmptyLineSeparator">
<property name="tokens" value="IMPORT, CLASS_DEF, INTERFACE_DEF, STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>

View File

@ -10,8 +10,8 @@
<!-- the annotation processor may not use the annotations or enums specified in org.mapstruct directly... -->
<disallow pkg="org.mapstruct" exact-match="true" />
<subpackage name="internal.prism">
<!-- ... with exception to the package org.mapstruct.ap.internal.prism -->
<subpackage name="internal.gem">
<!-- ... with exception to the package org.mapstruct.ap.internal.gem -->
<allow pkg=".*" regex="true" />
</subpackage>
<subpackage name="test">

View File

@ -2,10 +2,14 @@
============
Alexandr Shalugin - https://github.com/shalugin
Amine Touzani - https://github.com/ttzn
Andreas Gudian - https://github.com/agudian
Andrei Arlou - https://github.com/Captain1653
Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon
Arne Seime - https://github.com/seime
Christian Bandowski - https://github.com/chris922
Chris DeLashmutt - https://github.com/cdelashmutt-pivotal
Christian Kosmowski - https://github.com/ckosmowski
Christian Schuster - https://github.com/chschu
Christophe Labouisse - https://github.com/ggtools
Ciaran Liedeman - https://github.com/cliedeman
@ -13,21 +17,35 @@ Cindy Wang - https://github.com/birdfriend
Cornelius Dirmeier - https://github.com/cornzy
David Feinblum - https://github.com/dvfeinblum
Darren Rambaud - https://github.com/xyzst
Dekel Pilli - https://github.com/dekelpilli
Dilip Krishnan - https://github.com/dilipkrish
Dmytro Polovinkin - https://github.com/navpil
Ewald Volkert - https://github.com/eforest
Eric Martineau - https://github.com/ericmartineau
Ewald Volkert - https://github.com/eforest
Filip Hrisafov - https://github.com/filiphr
Florian Tavares - https://github.com/neoXfire
Gervais Blaise - https://github.com/gervaisb
Gibou Damien - https://github.com/dmngb
Gunnar Morling - https://github.com/gunnarmorling
Ivo Smid - https://github.com/bedla
Jason Bodnar - https://github.com/Blackbaud-JasonBodnar
Jeroen van Wilgenburg - https://github.com/jvwilge
Jeff Smyth - https://github.com/smythie86
João Paulo Bassinello - https://github.com/jpbassinello
Jonathan Kraska - https://github.com/jakraska
Joshua Spoerri - https://github.com/spoerri
Jude Niroshan - https://github.com/JudeNiroshan
Justyna Kubica-Ledzion - https://github.com/JKLedzion
Kemal Özcan - https://github.com/yekeoe
Kevin Grüneberg - https://github.com/kevcodez
Lukas Lazar - https://github.com/LukeLaz
Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis
Michael Pardo - https://github.com/pardom
Muhammad Usama - https://github.com/the-mgi
Mustafa Caylak - https://github.com/luxmeter
Oliver Ehrenmüller - https://github.com/greuelpirat
Oliver Erhart - https://github.com/thunderhook
Paul Strugnell - https://github.com/ps-powa
Pascal Grün - https://github.com/pascalgn
Pavel Makhov - https://github.com/streetturtle
@ -43,7 +61,13 @@ Sean Huang - https://github.com/seanjob
Sjaak Derksen - https://github.com/sjaakd
Stefan May - https://github.com/osthus-sm
Taras Mychaskiw - https://github.com/twentylemon
Thibault Duperron - https://github.com/Zomzog
Tomáš Poledný - https://github.com/Saljack
Tobias Meggendorfer - https://github.com/incaseoftrouble
Tillmann Gaida - https://github.com/Tillerino
Timo Eckhardt - https://github.com/timoe
Tomek Gubala - https://github.com/vgtworld
Valentin Kulesh - https://github.com/unshare
Vincent Alexander Beelte - https://github.com/grandmasterpixel
Winter Andreas - https://github.dev/wandi34
Xiu Hong Kooi - https://github.com/kooixh

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.7.0-SNAPSHOT</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.7.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@ -22,8 +22,8 @@
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -0,0 +1,176 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* This can be used to have mapstruct generate additional annotations on classes/methods.
* <p>
* Examples based on the spring framework annotations.
* </p>
* Marking a class as `Lazy`:
*
* <pre><code>
* &#64;AnnotateWith( value = Lazy.class )
* &#64;Mapper
* public interface FooMapper {
* // mapper code
* }
* </code></pre>
*
* The following code would be generated:
*
* <pre><code>
* &#64;Lazy
* public class FooMapperImpl implements FooMapper {
* // mapper code
* }
* </code></pre>
* Setting the profile on the generated implementation:
*
* <pre><code>
* &#64;AnnotateWith( value = Profile.class, elements = @AnnotateWith.Element( strings = "prod" ) )
* &#64;Mapper
* public interface FooMapper {
* // mapper code
* }
* </code></pre>
*
* The following code would be generated:
*
* <pre><code>
* &#64;Profile( value = "prod" )
* public class FooMapperImpl implements FooMapper {
* // mapper code
* }
* </code></pre>
*
* @author Ben Zegveld
* @since 1.6
*/
@Repeatable( AnnotateWiths.class )
@Retention( CLASS )
@Target( { TYPE, METHOD, ANNOTATION_TYPE } )
public @interface AnnotateWith {
/**
* @return the annotation class that needs to be added.
*/
Class<? extends Annotation> value();
/**
* @return the annotation elements that are to be applied to this annotation.
*/
Element[] elements() default {};
/**
* Used in combination with {@link AnnotateWith} to configure the annotation elements. Only 1 value type may be used
* within the same annotation at a time. For example mixing shorts and ints is not allowed.
*
* @author Ben Zegveld
* @since 1.6
*/
@interface Element {
/**
* @return name of the annotation element.
*/
String name() default "value";
/**
* cannot be used in conjunction with other value fields.
*
* @return short value(s) for the annotation element.
*/
short[] shorts() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return byte value(s) for the annotation element.
*/
byte[] bytes() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return int value(s) for the annotation element.
*/
int[] ints() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return long value(s) for the annotation element.
*/
long[] longs() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return float value(s) for the annotation element.
*/
float[] floats() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return double value(s) for the annotation element.
*/
double[] doubles() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return char value(s) for the annotation element.
*/
char[] chars() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return boolean value(s) for the annotation element.
*/
boolean[] booleans() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return string value(s) for the annotation element.
*/
String[] strings() default {};
/**
* cannot be used in conjunction with other value fields.
*
* @return class value(s) for the annotation element.
*/
Class<?>[] classes() default {};
/**
* only used in conjunction with the {@link #enums()} annotation element.
*
* @return the class of the enum.
*/
Class<? extends Enum<?>> enumClass() default NullEnum.class;
/**
* cannot be used in conjunction with other value fields. {@link #enumClass()} is also required when using
* {@link #enums()}
*
* @return enum value(s) for the annotation element.
*/
String[] enums() default {};
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* This can be used to have mapstruct generate additional annotations on classes/methods.
*
* @author Ben Zegveld
* @since 1.6
*/
@Retention( CLASS )
@Target( { TYPE, METHOD } )
public @interface AnnotateWiths {
/**
* The configuration of the additional annotations.
*
* @return The configuration of the additional annotations.
*/
AnnotateWith[] value();
}

View File

@ -11,13 +11,46 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.control.MappingControl;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Configures the mapping between two bean types.
* <p>
* Unless otherwise specified these properties are inherited to the generated bean mapping methods.
* <p>
* Either {@link #resultType()}, {@link #qualifiedBy()} or {@link #nullValueMappingStrategy()} must be specified.
* </p>
* <p><strong>Example:</strong> Determining the result type</p>
* <pre><code class='java'>
* // When result types have an inheritance relation, selecting either mapping method {@link Mapping} or factory method
* // {@link BeanMapping} can be become ambiguous. Parameter {@link BeanMapping#resultType()} can be used.
* public class FruitFactory {
* public Apple createApple() {
* return new Apple();
* }
* public Orange createOrange() {
* return new Orange();
* }
* }
* &#64;Mapper(uses = FruitFactory.class)
* public interface FruitMapper {
* &#64;BeanMapping(resultType = Apple.class)
* Fruit toFruit(FruitDto fruitDto);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class FruitMapperImpl implements FruitMapper {
* &#64;Override
* public Fruit toFruit(FruitDto fruitDto) {
* Apple fruit = fruitFactory.createApple();
* // ...
* }
* }
* </code></pre>
*
* @author Sjaak Derksen
*/
@ -27,6 +60,8 @@ public @interface BeanMapping {
/**
* Specifies the result type of the factory method to be used in case several factory methods qualify.
* <p>
* <b>NOTE</b>: This property is not inherited to generated mapping methods
*
* @return the resultType to select
*/
@ -40,9 +75,9 @@ public @interface BeanMapping {
* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method.
*
* @return the qualifiers
* @see BeanMapping#qualifiedByName()
* @see Qualifier
*/
Class<? extends Annotation>[] qualifiedBy() default { };
Class<? extends Annotation>[] qualifiedBy() default {};
/**
* Similar to {@link #qualifiedBy()}, but used in combination with {@code @}{@link Named} in case no custom
@ -86,9 +121,32 @@ public @interface BeanMapping {
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
/**
* Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
*
* Overrides the setting on {@link MapperConfig} and {@link Mapper}.
*
* @return strategy to handle missing implementation combined with {@link SubclassMappings}.
*
* @since 1.5
*/
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
/**
* Specifies the exception type to be thrown when a missing subclass implementation is detected
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
* <p>
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
*
* @return the exception class to throw when missing implementations are found.
* Defaults to {@link IllegalArgumentException}.
*/
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
/**
* Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No
* warning will be issued on missing target properties.
* warning will be issued on missing source or target properties.
*
* @return The ignore strategy (default false).
*
@ -103,6 +161,8 @@ public @interface BeanMapping {
* source properties report.
* <p>
* <b>NOTE</b>: This does not support ignoring nested source properties
* <p>
* <b>NOTE</b>: This property is not inherited to generated mapping methods
*
* @return The source properties that should be ignored when performing a report
*
@ -110,6 +170,28 @@ public @interface BeanMapping {
*/
String[] ignoreUnmappedSourceProperties() default {};
/**
* How unmapped properties of the source type of a mapping should be reported.
* If no policy is configured, the policy given via {@link MapperConfig#unmappedSourcePolicy()} or
* {@link Mapper#unmappedSourcePolicy()} will be applied, using {@link ReportingPolicy#IGNORE} by default.
*
* @return The reporting policy for unmapped source properties.
*
* @since 1.6
*/
ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE;
/**
* How unmapped properties of the target type of a mapping should be reported.
* If no policy is configured, the policy given via {@link MapperConfig#unmappedTargetPolicy()} or
* {@link Mapper#unmappedTargetPolicy()} will be applied, using {@link ReportingPolicy#WARN} by default.
*
* @return The reporting policy for unmapped target properties.
*
* @since 1.5
*/
ReportingPolicy unmappedTargetPolicy() default ReportingPolicy.WARN;
/**
* The information that should be used for the builder mappings. This can be used to define custom build methods
* for the builder strategy that one uses.
@ -128,4 +210,18 @@ public @interface BeanMapping {
* @since 1.3
*/
Builder builder() default @Builder;
/**
* Allows detailed control over the mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> mappingControl() default MappingControl.class;
}

View File

@ -14,6 +14,32 @@ import org.mapstruct.util.Experimental;
/**
* Configuration of builders, e.g. the name of the final build method.
*
* <p>
* <strong>Example:</strong> Using builder
* </p>
* <pre><code class='java'>
* // Mapper
* &#64;Mapper
* public interface SimpleBuilderMapper {
* &#64;Mapping(target = "name", source = "fullName"),
* &#64;Mapping(target = "job", constant = "programmer"),
* SimpleImmutablePerson toImmutable(SimpleMutablePerson source);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* &#64;Override
* public SimpleImmutablePerson toImmutable(SimpleMutablePerson source) {
* // name method can be changed with parameter {@link #buildMethod()}
* Builder simpleImmutablePerson = SimpleImmutablePerson.builder();
* simpleImmutablePerson.name( source.getFullName() );
* simpleImmutablePerson.age( source.getAge() );
* simpleImmutablePerson.address( source.getAddress() );
* simpleImmutablePerson.job( "programmer" );
* // ...
* }
* </code></pre>
*
* @author Filip Hrisafov
*
* @since 1.3
@ -29,4 +55,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

@ -7,6 +7,54 @@ package org.mapstruct;
/**
* Strategy for propagating the value of collection-typed properties from source to target.
* <p>
* In the table below, the dash {@code -} indicates a property name.
* Next, the trailing {@code s} indicates the plural form.
* The table explains the options and how they are applied to the presence / absence of a
* {@code set-s}, {@code add-} and / or {@code get-s} method on the target object.
* <table>
* <caption>Collection mapping strategy options</caption>
* <tr>
* <th>Option</th>
* <th>Only target set-s Available</th>
* <th>Only target add- Available</th>
* <th>Both set-s/add- Available</th>
* <th>No set-s/add- Available</th>
* <th>Existing Target ({@code @TargetType})</th>
* </tr>
* <tr>
* <td>{@link #ACCESSOR_ONLY}</td>
* <td>set-s</td>
* <td>get-s</td>
* <td>set-s</td>
* <td>get-s</td>
* <td>get-s</td>
* </tr>
* <tr>
* <td>{@link #SETTER_PREFERRED}</td>
* <td>set-s</td>
* <td>add-</td>
* <td>set-s</td>
* <td>get-s</td>
* <td>get-s</td>
* </tr>
* <tr>
* <td>{@link #ADDER_PREFERRED}</td>
* <td>set-s</td>
* <td>add-</td>
* <td>add-</td>
* <td>get-s</td>
* <td>get-s</td>
* </tr>
* <tr>
* <td>{@link #TARGET_IMMUTABLE}</td>
* <td>set-s</td>
* <td>exception</td>
* <td>set-s</td>
* <td>exception</td>
* <td>set-s</td>
* </tr>
* </table>
*
* @author Sjaak Derksen
*/

View File

@ -0,0 +1,96 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a method as a <em>presence check method</em> to check for presence in beans
* or it can be used to define additional check methods for something like source parameters.
* <p>
* By default, bean properties are checked against {@code null} or using a presence check method in the source bean.
* If a presence check method is available then it will be used instead.
* <p>
* Presence check methods have to return {@code boolean}.
* The following parameters are accepted for the presence check methods:
* <ul>
* <li>The parameter with the value of the source property.
* e.g. the value given by calling {@code getName()} for the name property of the source bean
* - only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* <li>
* {@code @}{@link TargetPropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>
* {@code @}{@link SourcePropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
* for a method to be considered as a presence check method.
*
* <pre><code class='java'>
* public class PresenceCheckUtils {
*
* &#64;Condition
* public static boolean isNotEmpty(String value) {
* return value != null &#38;&#38; !value.isEmpty();
* }
* }
*
* &#64;Mapper(uses = PresenceCheckUtils.class)
* public interface MovieMapper {
*
* MovieDto map(Movie movie);
* }
* </code></pre>
* <p>
* The following implementation of {@code MovieMapper} will be generated:
*
* <pre><code class='java'>
* public class MovieMapperImpl implements MovieMapper {
*
* &#64;Override
* public MovieDto map(Movie movie) {
* if ( movie == null ) {
* return null;
* }
*
* MovieDto movieDto = new MovieDto();
*
* if ( PresenceCheckUtils.isNotEmpty( movie.getTitle() ) ) {
* movieDto.setTitle( movie.getTitle() );
* }
*
* return movieDto;
* }
* }
* </code></pre>
* <p>
* This annotation can also be used as a meta-annotation to define the condition strategy.
*
* @author Filip Hrisafov
* @see SourceParameterCondition
* @since 1.5
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Condition {
/**
* @return the places where the condition should apply to
* @since 1.6
*/
ConditionStrategy[] appliesTo() default ConditionStrategy.PROPERTIES;
}

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;
/**
* Strategy for defining what to what a condition (check) method is applied to
*
* @author Filip Hrisafov
* @since 1.6
*/
public enum ConditionStrategy {
/**
* The condition method should be applied whether a property should be mapped.
*/
PROPERTIES,
/**
* The condition method should be applied to check if a source parameters should be mapped.
*/
SOURCE_PARAMETERS,
}

View File

@ -22,9 +22,7 @@ import java.lang.annotation.Target;
* <p>
* <b>NOTE:</b> This annotation is not supported for the component model {@code cdi}. Use CDI's own
* <a href="https://docs.jboss.org/cdi/spec/1.0/html/decorators.html">{@code @Decorator}</a> feature instead.
* <p>
* <b>NOTE:</b> The decorator feature when used with component model {@code jsr330} is considered <em>experimental</em>
* and it may change in future releases.
* </p>
* <h2>Examples</h2>
* <p>
* For the examples below, consider the following mapper declaration:
@ -105,12 +103,12 @@ import java.lang.annotation.Target;
* private PersonMapper personMapper; // injects the decorator, with the injected original mapper
* </pre>
*
* <h3>3. Component model 'jsr330'</h3>
* <h3>3. Component model 'jsr330' or 'jakarta'</h3>
* <h4>Referencing the original mapper</h4>
* <p>
* JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated
* implementation of the original mapper is annotated with
* {@code @javax.inject.Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when
* JSR 330 / Jakarta Inject doesn't specify qualifiers and only allows to specifically name the beans. Hence,
* the generated implementation of the original mapper is annotated with
* {@code @Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when
* using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your
* decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class):
*
@ -147,7 +145,7 @@ import java.lang.annotation.Target;
* @author Gunnar Morling
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.CLASS)
public @interface DecoratedWith {
/**

View File

@ -0,0 +1,156 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Configured the mapping between two value types.
* <p><strong>Example:</strong> Using a suffix for enums</p>
* <pre><code class='java'>
* public enum CheeseType {
* BRIE,
* ROQUEFORT
* }
*
* public enum CheeseTypeSuffixed {
* BRIE_TYPE,
* ROQUEFORT_TYPE
* }
*
* &#64;Mapper
* public interface CheeseMapper {
*
* &#64;EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")
* CheeseTypeSuffixed map(Cheese cheese);
*
* &#64;InheritInverseConfiguration
* Cheese map(CheeseTypeSuffixed cheese);
*
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class CheeseMapperImpl implements CheeseMapper {
*
* &#64;Override
* public CheeseTypeSuffixed map(Cheese cheese) {
* if ( cheese == null ) {
* return null;
* }
*
* CheeseTypeSuffixed cheeseTypeSuffixed;
*
* switch ( cheese ) {
* case BRIE:
* cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;
* break;
* case ROQUEFORT:
* cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;
* break;
* default:
* throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
* }
*
* return cheeseTypeSuffixed;
* }
*
* &#64;Override
* public Cheese map(CheeseTypeSuffixed cheese) {
* if ( cheese == null ) {
* return null;
* }
*
* CheeseType cheeseType;
*
* switch ( cheese ) {
* case BRIE_TYPE:
* cheeseType = CheeseType.BRIE;
* break;
* case ROQUEFORT_TYPE:
* cheeseType = CheeseType.ROQUEFORT;
* break;
* default:
* throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
* }
*
* return cheeseType;
* }
* }
* </code></pre>
*
* @author Filip Hrisafov
* @since 1.4
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnumMapping {
/**
* Specifies the name transformation strategy that should be used for implicit mapping between enums.
* Known strategies are:
* <ul>
* <li>{@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* suffix to the source enum</li>
* <li>{@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the given {@link #configuration()}
* from the end of the source enum</li>
* <li>{@link MappingConstants#PREFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* prefix to the source enum</li>
* <li>{@link MappingConstants#STRIP_PREFIX_TRANSFORMATION} - strips the given {@link #configuration()} from
* the start of the source enum</li>
* <li>
* {@link MappingConstants#CASE_TRANSFORMATION} - applies the given {@link #configuration()} case
* transformation to the source enum. Supported configurations are:
* <ul>
* <li><i>upper</i> - Performs upper case transformation to the source enum</li>
* <li><i>lower</i> - Performs lower case transformation to the source enum</li>
* <li>
* <i>capital</i> - Performs capitalisation of the first character of every word in the source enum
* and everything else to lower case. A word is split by "_".
* </li>
* </ul>
* </li>
* </ul>
*
* It is possible to use custom name transformation strategies by implementing the {@code
* EnumTransformationStrategy} SPI.
*
* @return the name transformation strategy
*/
String nameTransformationStrategy() default "";
/**
* The configuration that should be passed on the appropriate name transformation strategy.
* e.g. a suffix that should be applied to the source enum when doing name based mapping.
*
* @return the configuration to use
*/
String configuration() default "";
/**
* Exception that should be thrown by the generated code if no mapping matches.
* If no exception is configured, the exception given via {@link MapperConfig#unexpectedValueMappingException()} or
* {@link Mapper#unexpectedValueMappingException()} will be used, using {@link IllegalArgumentException} by default.
*
* <p>
* Note:
* <ul>
* <li>
* The defined exception should at least have a constructor with a {@link String} parameter.
* </li>
* <li>
* If the defined exception is a checked exception then the enum mapping methods should have that exception
* in the throws clause.
* </li>
* </ul>
*
* @return the exception that should be used in the generated code
*/
Class<? extends Exception> unexpectedValueMappingException() default IllegalArgumentException.class;
}

View File

@ -0,0 +1,67 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Configures the ignored of one bean attribute.
*
* <p>
* The name all attributes of for ignored is to be specified via {@link #targets()}.
* </p>
*
* <p>
* <strong>Example 1:</strong> Implicitly mapping fields with the same name:
* </p>
*
* <pre><code class='java'>
* // We need ignored Human.name and Human.lastName
* // we can use &#64;Ignored with parameters "name", "lastName" {@link #targets()}
* &#64;Mapper
* public interface HumanMapper {
* &#64;Ignored( targets = { "name", "lastName" } )
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates:
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* humanDto.setFullName( human.getFullName() );
* // ...
* }
* </code></pre>
*
* @author Ivashin Aleksey
*/
@Repeatable(IgnoredList.class)
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Ignored {
/**
* Whether the specified properties should be ignored by the generated mapping method.
* This can be useful when certain attributes should not be propagated from source to target or when properties in
* the target object are populated using a decorator and thus would be reported as unmapped target property by
* default.
*
* @return The target names of the configured properties that should be ignored
*/
String[] targets();
/**
* The prefix that should be applied to all the properties specified via {@link #targets()}.
*
* @return The target prefix to be applied to the defined properties
*/
String prefix() default "";
}

View File

@ -0,0 +1,54 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Configures the ignored list for several bean attributes.
* <p>
* <strong>TIP: When using Java 8 or later, you can omit the {@code @IgnoredList}
* wrapper annotation and directly specify several {@code @Ignored} annotations on one method.</strong>
*
* <p>These two examples are equal.
* </p>
* <pre><code class='java'>
* // before Java 8
* &#64;Mapper
* public interface MyMapper {
* &#64;IgnoredList({
* &#64;Ignored(targets = { "firstProperty" } ),
* &#64;Ignored(targets = { "secondProperty" } )
* })
* HumanDto toHumanDto(Human human);
* }
* </code></pre>
* <pre><code class='java'>
* // Java 8 and later
* &#64;Mapper
* public interface MyMapper {
* &#64;Ignored(targets = { "firstProperty" } ),
* &#64;Ignored(targets = { "secondProperty" } )
* HumanDto toHumanDto(Human human);
* }
* </code></pre>
*
* @author Ivashin Aleksey
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface IgnoredList {
/**
* The configuration of the bean attributes.
*
* @return The configuration of the bean attributes.
*/
Ignored[] value();
}

View File

@ -18,6 +18,10 @@ import java.lang.annotation.Target;
* If no method can be identified unambiguously as configuration source (i.e. several candidate methods with matching
* source and target type exist), the name of the method to inherit from must be specified via {@link #name()}.
* <p>
* {@link Mapping#expression()}, {@link Mapping#constant()}, {@link Mapping#defaultExpression()} and
* {@link Mapping#defaultValue()} are not inverse inherited
*
* <p>
* A typical use case is annotating an update method so it inherits all mappings from a corresponding "standard" mapping
* method:
*

View File

@ -21,7 +21,59 @@ import java.lang.annotation.Target;
* <p>
* If more than one matching inverse method exists, the name of the method to inherit the configuration from must be
* specified via {@link #name()}
* <p>
* {@link Mapping#expression()}, {@link Mapping#constant()}, {@link Mapping#defaultExpression()} and
* {@link Mapping#defaultValue()} are not inverse inherited
*
* <p>
* <strong>Examples</strong>
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface HumanMapper {
* Human toHuman(HumanDto humanDto);
* &#64;InheritInverseConfiguration
* HumanDto toHumanDto(Human human);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class HumanMapperImpl implements HumanMapper {
* &#64;Override
* public Human toHuman(HumanDto humanDto) {
* if ( humanDto == null ) {
* return null;
* }
* Human human = new Human();
* human.setName( humanDto.getName() );
* return human;
* }
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* if ( human == null ) {
* return null;
* }
* HumanDto humanDto = new HumanDto();
* humanDto.setName( human.getName() );
* return humanDto;
* }
* }
* </code></pre>
*
* <pre><code class='java'>
* &#64;Mapper
* public interface CarMapper {
*
* &#64;Mapping( target = "seatCount", source = "numberOfSeats")
* &#64;Mapping( target = "enginePower", source = "engineClass", ignore=true) // NOTE: source specified as well
* CarDto carToDto(Car car);
*
* &#64;InheritInverseConfiguration
* &#64;Mapping(target = "numberOfSeats", ignore = true)
* // no need to specify a mapping with ignore for "engineClass": specifying source above will assume
* Car carDtoToCar(CarDto carDto);
* }
* </code></pre>
* @author Sjaak Derksen
*/
@Target(ElementType.METHOD)
@ -29,8 +81,8 @@ import java.lang.annotation.Target;
public @interface InheritInverseConfiguration {
/**
* The name of the inverse mapping method to inherit the mappings from. Needs only to be specified in case more than
* one inverse method with matching source and target type exists.
* The name of the inverse mapping method to inherit the mappings from. Needs to be specified only in case more than
* one inverse method exists with a matching source and target type exists.
*
* @return The name of the inverse mapping method to inherit the mappings from.
*/

View File

@ -7,9 +7,10 @@ package org.mapstruct;
/**
* Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and
* JSR330.
* JSR330 / Jakarta.
*
* @author Kevin Grüneberg
* @author Lucas Resch
*/
public enum InjectionStrategy {
@ -17,5 +18,8 @@ public enum InjectionStrategy {
FIELD,
/** Annotations are written on the constructor **/
CONSTRUCTOR
CONSTRUCTOR,
/** A dedicated setter method is created */
SETTER
}

View File

@ -10,17 +10,43 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.text.SimpleDateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.mapstruct.control.MappingControl;
/**
* Configures the mapping between two iterable like types, e.g. {@code List<String>} and {@code List<Date>}.
*
*
* <p>Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy
* <p>Note: either {@link #dateFormat()}, {@link #elementTargetType()} or {@link #qualifiedBy() }
* must be specified</p>
*
* <p>
* <strong>Example:</strong> Convert List&lt;Float&gt; to List&lt;String&gt;
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface FloatToStringMapper {
* &#64;IterableMapping( numberFormat = "##.00" )
* List&lt;String&gt; sourceToTarget(List&lt;Float&gt; source);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class FloatToStringMapperImpl implements FloatToStringMapper {
* &#64;Override
* public List&lt;String&gt; sourceToTarget(List&lt;Float&gt; source) {
* List&lt;String&gt; list = new ArrayList&lt;String&gt;( source.size() );
* for ( Float float1 : source ) {
* list.add( new DecimalFormat( "##.00" ).format( float1 ) );
* }
* // ...
* }
* }
* </code></pre>
*
* Supported mappings are:
* <ul>
* <li>{@code Iterable<A>} to/from {@code Iterable<B>}/{@code Iterable<A>}</li>
@ -40,19 +66,38 @@ public @interface IterableMapping {
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from an iterable of
* {@code String} to an iterable {@link Date} or vice-versa. Will be ignored for all other element types.
* <p>
* If the {@link #locale()} is also specified, the format will consider the specified locale when processing
* the date. Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
* @see #locale()
*/
String dateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types.
* <p>
* If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale.
* Otherwise, the system's default locale will be used to process the number format.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
* @see #locale()
*/
String numberFormat() default "";
/**
* Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}.
* <p>
* The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc.
* <p>
* If no locale is specified, the system's default locale will be used.
*
* @return A string representing the locale to be used when formatting dates or numbers.
*/
String locale() default "";
/**
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
* mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' error.
@ -60,6 +105,7 @@ public @interface IterableMapping {
* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method.
*
* @return the qualifiers
* @see Qualifier
*/
Class<? extends Annotation>[] qualifiedBy() default { };
@ -96,4 +142,18 @@ public @interface IterableMapping {
* @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping.
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* Allows detailed control over the mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> elementMappingControl() default MappingControl.class;
}

View File

@ -0,0 +1,115 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Allows the definition of Javadoc comments in the MapStruct <code>Mapper</code> generated class.
*
* <p>The annotation provides support for the usual Javadoc comments elements by defining analogous attributes.</p>
*
*
* <p>Please, note that at least one of these attributes must be specified.</p>
*
* <p>
* For instance, the following definition;
* </p>
* <pre><code class='java'>
* &#64;Javadoc(
* value = "This is the description",
* authors = { "author1", "author2" },
* deprecated = "Use {&#64;link OtherMapper} instead",
* since = "0.1"
* )
* </code></pre>
*
* <p>
* will generate:
* </p>
*
* <pre><code class='java'>
* /**
* * This is the description
* *
* * &#64;author author1
* * &#64;author author2
* *
* * &#64;deprecated Use {&#64;link OtherMapper} instead
* * &#64;since 0.1
* *&#47;
* </code></pre>
*
* <p>
* The entire Javadoc comment block can be passed directly:
* </p>
* <pre><code class='java'>
* &#64;Javadoc("This is the description\n"
* + "\n"
* + "&#64;author author1\n"
* + "&#64;author author2\n"
* + "\n"
* + "&#64;deprecated Use {&#64;link OtherMapper} instead\n"
* + "&#64;since 0.1\n"
* )
* </code></pre>
*
* <pre><code class='java'>
* // or using Text Blocks
* &#64;Javadoc(
* """
* This is the description
*
* &#64;author author1
* &#64;author author2
*
* &#64;deprecated Use {&#64;link OtherMapper} instead
* &#64;since 0.1
* """
* )
* </code></pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Javadoc {
/**
* Main Javadoc comment text block.
*
* @return Main Javadoc comment text block.
*/
String value() default "";
/**
* List of authors of the code that it is being documented.
* <p>
* It will generate a list of the Javadoc tool comment element <code>&#64;author</code>
* with the different values and in the order provided.
*
* @return array of javadoc authors.
*/
String[] authors() default { };
/**
* Specifies that the functionality that is being documented is deprecated.
* <p>
* Corresponds to the <code>&#64;deprecated</code> Javadoc tool comment element.
*
* @return Deprecation message about the documented functionality
*/
String deprecated() default "";
/**
* Specifies the version since the functionality that is being documented is available.
* <p>
* Corresponds to the <code>&#64;since</code> Javadoc tool comment element.
*
* @return Version since the functionality is available
*/
String since() default "";
}

View File

@ -10,14 +10,42 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.text.SimpleDateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.mapstruct.control.MappingControl;
/**
* Configures the mapping between two map types, e.g. {@code Map<String, String>} and {@code Map<Long, Date>}.
* Configures the mapping between two map types, e.g. Map&lt;String, String&gt; and Map&lt;Long, Date&gt;.
*
* <p>Note: at least one element needs to be specified</p>
* <p>
* <strong>Example</strong>:
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface SimpleMapper {
* &#64;MapMapping(valueDateFormat = "dd.MM.yyyy")
* Map&lt;String, String&gt; longDateMapToStringStringMap(Map&lt;Long, Date&gt; source);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class SimpleMapperImpl implements SimpleMapper {
* &#64;Override
* public Map&lt;String, String&gt; longDateMapToStringStringMap(Map&lt;Long, Date&gt; source) } {
* Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;(); }
* for ( java.util.Map.Entry&lt;Long, Date&gt; entry : source.entrySet() ) } {
* String key = new DecimalFormat( "" ).format( entry.getKey() );
* String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
* map.put( key, value );
* }
* // ...
* }
* }
* </code></pre>
*
* <p><strong>NOTE:</strong> at least one element needs to be specified</p>
*
* @author Gunnar Morling
*/
@ -28,8 +56,12 @@ public @interface MapMapping {
/**
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type
* {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types.
* <p>
* If the {@link #locale()} is specified, the format will consider the specified locale when processing the date.
* Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
* @see #locale()
*/
String keyDateFormat() default "";
@ -37,27 +69,50 @@ public @interface MapMapping {
* A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value
* type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value
* types.
* <p>
* If the {@link #locale()} is specified, the format will consider the specified locale when processing the date.
* Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
* @see #locale()
*/
String valueDateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other key types.
* <p>
* If the {@link #locale()} is specified, the number format will be applied in the context of the given locale.
* Otherwise, the system's default locale will be used.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
* @see #locale()
*/
String keyNumberFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other value types.
* <p>
* If the {@link #locale()} is specified, the number format will be applied in the context of the given locale.
* Otherwise, the system's default locale will be used.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
* @see #locale()
*/
String valueNumberFormat() default "";
/**
* Specifies the locale to be used when processing {@link SimpleDateFormat} or {@link DecimalFormat} for key or
* value mappings in maps. The locale should be a plain tag representing the language, such as "en" for English,
* "de" for German, etc.
* <p>
* If no locale is specified, the system's default locale will be used.
*
* @return A string representing the locale to be used when formatting dates or numbers in maps.
*/
String locale() default "";
/**
* A key value qualifier can be specified to aid the selection process of a suitable mapper. This is useful in
* case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found'
@ -66,6 +121,7 @@ public @interface MapMapping {
* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method.
*
* @return the qualifiers
* @see Qualifier
*/
Class<? extends Annotation>[] keyQualifiedBy() default { };
@ -93,6 +149,7 @@ public @interface MapMapping {
* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method.
*
* @return the qualifiers
* @see Qualifier
*/
Class<? extends Annotation>[] valueQualifiedBy() default { };
@ -139,4 +196,32 @@ public @interface MapMapping {
* @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapping.
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* Allows detailed control over the key mapping process.
*
* @return the mapping control
*
* @since 1.4
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> keyMappingControl() default MappingControl.class;
/**
* Allows detailed control over the value mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> valueMappingControl() default MappingControl.class;
}

View File

@ -5,20 +5,76 @@
*/
package org.mapstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.control.MappingControl;
import org.mapstruct.factory.Mappers;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via
* MapStruct.
*
* <p>
* <strong>Example 1:</strong> Creating mapper
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface CarMapper {
* CarDto toCarDto(Car source);
* }
* </code></pre>
* <p>
* <strong>Example 2:</strong> Use additional mappers with parameters {@link #uses()}, {@link #componentModel()}
* and {@link #injectionStrategy()}
* </p>
* <pre><code class='java'>
* // we have MarkMapper (map field "mark" to field "name" to upper case)
* &#64;Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
* public class MarkMapper {
* public String mapMark(String mark) {
* return mark.toUpperCase();
* }
* }
* // we have CarMapper
* &#64;Mapper(
* componentModel = MappingConstants.ComponentModel.SPRING,
* uses = MarkMapper.class,
* injectionStrategy = InjectionStrategy.CONSTRUCTOR)
* public interface CarMapper {
* &#64;Mapping(target = "name", source = "mark")
* CarDto convertMap(CarEntity carEntity);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* &#64;Component
* public class CarMapperImpl implements CarMapper {
* private final MarkMapper markMapper;
* &#64;Autowired
* public CarMapperImpl(MarkMapper markMapper) {
* this.markMapper = markMapper;
* }
* &#64;Override
* public CarDto convertMap(CarEntity carEntity) {
* if ( carEntity == null ) {
* return null;
* }
* CarDto carDto = new CarDto();
* carDto.setName( markMapper.mapMark( carEntity.getMark() ) );
* return carDto;
* }
* }
* </code></pre>
*
* @author Gunnar Morling
* @see Javadoc
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@ -87,14 +143,19 @@ public @interface Mapper {
* can be retrieved via {@code @Autowired}</li>
* <li>
* {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and
* {@code @Singleton}, and can be retrieved via {@code @Inject}</li>
* {@code @Singleton}, and can be retrieved via {@code @Inject}.
* The annotations will either be from javax.inject or jakarta.inject,
* depending on which one is available, with javax.inject having precedence.</li>
* <li>
* {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and
* {@code @Singleton}, and can be retrieved via {@code @Inject}.</li>
* </ul>
* The method overrides an unmappedTargetPolicy set in a central configuration set
* The method overrides a componentModel set in a central configuration set
* by {@link #config() }
*
* @return The component model for the generated mapper.
*/
String componentModel() default "default";
String componentModel() default MappingConstants.ComponentModel.DEFAULT;
/**
* Specifies the name of the implementation class. The {@code <CLASS_NAME>} will be replaced by the
@ -148,6 +209,32 @@ public @interface Mapper {
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping} of
* this mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither
* strategy is configured, the strategy given via {@link MapperConfig#nullValueIterableMappingStrategy()} will be
* applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default.
*
* @since 1.5
*
* @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping} of
* this mapper.
*/
NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping} of this
* mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither strategy
* is configured, the strategy given via {@link MapperConfig#nullValueMapMappingStrategy()} will be applied, using
* {@link NullValueMappingStrategy#RETURN_NULL} by default.
*
* @since 1.5
*
* @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping} of this
* mapper.
*/
NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is
* configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} will be applied,
@ -183,6 +270,29 @@ public @interface Mapper {
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
/**
* Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
*
* Can be overridden by the one on {@link BeanMapping}, but overrides {@link MapperConfig}.
*
* @return strategy to handle missing implementation combined with {@link SubclassMappings}.
*
* @since 1.5
*/
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
/**
* Specifies the exception type to be thrown when a missing subclass implementation is detected
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
* <p>
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
*
* @return the exception class to throw when missing implementations are found.
* Defaults to {@link IllegalArgumentException}.
*/
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
/**
* Determines whether to use field or constructor injection. This is only used on annotated based component models
* such as CDI, Spring and JSR 330.
@ -201,7 +311,7 @@ public @interface Mapper {
* Can be configured by the {@link MapperConfig#disableSubMappingMethodsGeneration()} as well.
* <p>
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
* <a href="http://mapstruct.org">mapstruct.org</a> or
* <a href="https://mapstruct.org">mapstruct.org</a> or
* <a href="https://github.com/mapstruct/mapstruct">github.com/mapstruct/mapstruct</a> to share what problem you
* are facing with the automatic sub-mapping generation.
*
@ -229,4 +339,53 @@ public @interface Mapper {
* @since 1.3
*/
Builder builder() default @Builder;
/**
* Allows detailed control over the mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> mappingControl() default MappingControl.class;
/**
* Exception that should be thrown by the generated code if no mapping matches for enums.
* If no exception is configured, the exception given via {@link MapperConfig#unexpectedValueMappingException()}
* will be used, using {@link IllegalArgumentException} by default.
*
* <p>
* Note:
* <ul>
* <li>
* The defined exception should at least have a constructor with a {@link String} parameter.
* </li>
* <li>
* If the defined exception is a checked exception then the enum mapping methods should have that exception
* in the throws clause.
* </li>
* </ul>
*
* @return the exception that should be used in the generated code
*
* @since 1.4
*/
Class<? extends Exception> unexpectedValueMappingException() default IllegalArgumentException.class;
/**
* Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed.
* i.e. not be added.
*
* The method overrides the flag set in a central configuration set by {@link #config()}
* or through an annotation processor option.
*
* @return whether the addition of a timestamp should be suppressed
*
* @since 1.5
*/
boolean suppressTimestampInGenerated() default false;
}

View File

@ -5,14 +5,17 @@
*/
package org.mapstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.control.MappingControl;
import org.mapstruct.factory.Mappers;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
/**
* Marks a class or interface as configuration source for generated mappers. This allows to share common configurations
@ -30,6 +33,36 @@ import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
* types are assignable.
* </p>
*
* <p>
* <strong>Example:</strong>
* </p>
* <pre><code class='java'>
* // create config
* &#64;MapperConfig(
* uses = CustomMapperViaMapperConfig.class,
* unmappedTargetPolicy = ReportingPolicy.ERROR
* )
* public interface CentralConfig {
* }
* </code></pre>
* <pre><code class='java'>
* // use config
* &#64;Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
* public interface SourceTargetMapper {
* // ...
* }
* </code></pre>
* <pre><code class='java'>
* // result after applying CentralConfig
* &#64;Mapper(
* uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
* unmappedTargetPolicy = ReportingPolicy.ERROR
* )
* public interface SourceTargetMapper {
* // ...
* }
* </code></pre>
*
* @author Sjaak Derksen
* @see Mapper#config()
*/
@ -44,6 +77,18 @@ public @interface MapperConfig {
*/
Class<?>[] uses() default { };
/**
* Additional types for which an import statement is to be added to the generated mapper implementation class.
* This allows to refer to those types from within mapping expressions given via {@link Mapping#expression()},
* {@link Mapping#defaultExpression()} or using
* their simple name rather than their fully-qualified name.
*
* @return classes to add in the imports of the generated implementation.
*
* @since 1.4
*/
Class<?>[] imports() default { };
/**
* How unmapped properties of the source type of a mapping should be
* reported.
@ -86,12 +131,17 @@ public @interface MapperConfig {
* can be retrieved via {@code @Autowired}</li>
* <li>
* {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and
* {@code @Singleton}, and can be retrieved via {@code @Inject}</li>
* {@code @Singleton}, and can be retrieved via {@code @Inject}.
* The annotations will either be from javax.inject or jakarta.inject,
* depending on which one is available, with javax.inject having precedence.</li>
* <li>
* {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and
* {@code @Singleton}, and can be retrieved via {@code @Inject}.</li>
* </ul>
*
* @return The component model for the generated mapper.
*/
String componentModel() default "default";
String componentModel() default MappingConstants.ComponentModel.DEFAULT;
/**
* Specifies the name of the implementation class. The {@code <CLASS_NAME>} will be replaced by the
@ -132,6 +182,28 @@ public @interface MapperConfig {
*/
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping}.
* If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using
* {@link NullValueMappingStrategy#RETURN_NULL} by default.
*
* @since 1.5
*
* @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping}.
*/
NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping}.
* If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using
* {@link NullValueMappingStrategy#RETURN_NULL} by default.
*
* @since 1.5
*
* @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping}.
*/
NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is
* configured, {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default.
@ -166,6 +238,29 @@ public @interface MapperConfig {
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
/**
* Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
*
* Can be overridden by the one on {@link BeanMapping} or {@link Mapper}.
*
* @return strategy to handle missing implementation combined with {@link SubclassMappings}.
*
* @since 1.5
*/
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
/**
* Specifies the exception type to be thrown when a missing subclass implementation is detected
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
* <p>
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
*
* @return the exception class to throw when missing implementations are found.
* Defaults to {@link IllegalArgumentException}.
*/
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
/**
* Determines whether to use field or constructor injection. This is only used on annotated based component models
* such as CDI, Spring and JSR 330.
@ -186,7 +281,7 @@ public @interface MapperConfig {
* Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()}
* <p>
* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at
* <a href="http://mapstruct.org">mapstruct.org</a> or
* <a href="https://mapstruct.org">mapstruct.org</a> or
* <a href="https://github.com/mapstruct/mapstruct">github.com/mapstruct/mapstruct</a> to share what problem you
* are facing with the automatic sub-mapping generation.
*
@ -215,4 +310,52 @@ public @interface MapperConfig {
* @since 1.3
*/
Builder builder() default @Builder;
/**
* Allows detailed control over the mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> mappingControl() default MappingControl.class;
/**
* Exception that should be thrown by the generated code if no mapping matches for enums.
* If no exception is configured, {@link IllegalArgumentException} will be used by default.
*
* <p>
* Note:
* <ul>
* <li>
* The defined exception should at least have a constructor with a {@link String} parameter.
* </li>
* <li>
* If the defined exception is a checked exception then the enum mapping methods should have that exception
* in the throws clause.
* </li>
* </ul>
*
* @return the exception that should be used in the generated code
*
* @since 1.4
*/
Class<? extends Exception> unexpectedValueMappingException() default IllegalArgumentException.class;
/**
* Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed.
* i.e. not be added.
*
* The method overrides the flag set through an annotation processor option.
*
* @return whether the addition of a timestamp should be suppressed
*
* @since 1.5
*/
boolean suppressTimestampInGenerated() default false;
}

View File

@ -15,27 +15,133 @@ import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.mapstruct.control.MappingControl;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/**
* Configures the mapping of one bean attribute or enum constant.
* Configures the mapping of one bean attribute.
* <p>
* The name of the mapped attribute or constant is to be specified via {@link #target()}. For mapped bean attributes it
* is assumed by default that the attribute has the same name in the source bean. Alternatively, one of
* {@link #source()}, {@link #expression()} or {@link #constant()} can be specified to define the property source.
* </p>
* <p>
* In addition, the attributes {@link #dateFormat()} and {@link #qualifiedBy()} may be used to further define the
* mapping.
* </p>
*
* <p>
* <b>IMPORTANT NOTE:</b> the enum mapping capability is deprecated and replaced by {@link ValueMapping} it
* will be removed in subsequent versions.
* <strong>Example 1:</strong> Implicitly mapping fields with the same name:
* </p>
* <pre><code class='java'>
* // Both classes HumanDto and Human have property with name "fullName"
* // properties with the same name will be mapped implicitly
* &#64;Mapper
* public interface HumanMapper {
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates:
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* humanDto.setFullName( human.getFullName() );
* // ...
* }
* </code></pre>
*
* <p><strong>Example 2:</strong> Mapping properties with different names</p>
* <pre><code class='java'>
* // We need map Human.companyName to HumanDto.company
* // we can use &#64;Mapping with parameters {@link #source()} and {@link #target()}
* &#64;Mapper
* public interface HumanMapper {
* &#64;Mapping(source="companyName", target="company")
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates:
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* humanDto.setCompany( human.getCompanyName() );
* // ...
* }
* </code></pre>
* <p>
* <strong>Example 3:</strong> Mapping with expression
* <b>IMPORTANT NOTE:</b> Now it works only for Java
* </p>
* <pre><code class='java'>
* // We need map Human.name to HumanDto.countNameSymbols.
* // we can use {@link #expression()} for it
* &#64;Mapper
* public interface HumanMapper {
* &#64;Mapping(target="countNameSymbols", expression="java(human.getName().length())")
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates:
*&#64;Override
* public HumanDto toHumanDto(Human human) {
* humanDto.setCountNameSymbols( human.getName().length() );
* //...
* }
* </code></pre>
* <p>
* <strong>Example 4:</strong> Mapping to constant
* </p>
* <pre><code class='java'>
* // We need map HumanDto.name to string constant "Unknown"
* // we can use {@link #constant()} for it
* &#64;Mapper
* public interface HumanMapper {
* &#64;Mapping(target="name", constant="Unknown")
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* humanDto.setName( "Unknown" );
* // ...
* }
* </code></pre>
* <p>
* <strong>Example 5:</strong> Mapping with default value
* </p>
* <pre><code class='java'>
* // We need map Human.name to HumanDto.fullName, but if Human.name == null, then set value "Somebody"
* // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it
* &#64;Mapper
* public interface HumanMapper {
* &#64;Mapping(source="name", target="name", defaultValue="Somebody")
* HumanDto toHumanDto(Human human)
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* &#64;Override
* public HumanDto toHumanDto(Human human) {
* if ( human.getName() != null ) {
* humanDto.setFullName( human.getName() );
* }
* else {
* humanDto.setFullName( "Somebody" );
* }
* // ...
* }
* </code></pre>
*
* @author Gunnar Morling
*/
@Repeatable(Mappings.class)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface Mapping {
/**
@ -69,19 +175,38 @@ public @interface Mapping {
/**
* A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to
* {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants.
* <p>
* If the {@link #locale()} is also specified, the format will consider the specified locale when processing
* the date. Otherwise, the system's default locale will be used.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
* @see #locale()
*/
String dateFormat() default "";
/**
* A format string as processable by {@link DecimalFormat} if the annotated method maps from a
* {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types.
* <p>
* If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale.
* Otherwise, the system's default locale will be used to process the number format.
*
* @return A decimal format string as processable by {@link DecimalFormat}.
* @see #locale()
*/
String numberFormat() default "";
/**
* Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}.
* <p>
* The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc.
* <p>
* If no locale is specified, the system's default locale will be used.
*
* @return A string representing the locale to be used when formatting dates or numbers.
*/
String locale() default "";
/**
* A constant {@link String} based on which the specified target property is to be set.
* <p>
@ -109,6 +234,10 @@ public @interface Mapping {
* </li>
* </ol>
* <p>
* You can use {@link #qualifiedBy()} or {@link #qualifiedByName()} to force the use of a conversion method
* even when one would not apply. (e.g. {@code String} to {@code String})
* </p>
* <p>
* This attribute can not be used together with {@link #source()}, {@link #defaultValue()},
* {@link #defaultExpression()} or {@link #expression()}.
*
@ -136,7 +265,7 @@ public @interface Mapping {
* imported via {@link Mapper#imports()}.
* <p>
* This attribute can not be used together with {@link #source()}, {@link #defaultValue()},
* {@link #defaultExpression()} or {@link #constant()}.
* {@link #defaultExpression()}, {@link #qualifiedBy()}, {@link #qualifiedByName()} or {@link #constant()}.
*
* @return An expression specifying the value for the designated target property
*/
@ -174,9 +303,12 @@ public @interface Mapping {
/**
* Whether the property specified via {@link #target()} should be ignored by the generated mapping method or not.
* This can be useful when certain attributes should not be propagated from source or target or when properties in
* This can be useful when certain attributes should not be propagated from source to target or when properties in
* the target object are populated using a decorator and thus would be reported as unmapped target property by
* default.
* <p>
* If you have multiple properties to ignore,
* you can use the {@link Ignored} annotation instead and group them all at once.
*
* @return {@code true} if the given property should be ignored, {@code false} otherwise
*/
@ -186,8 +318,11 @@ public @interface Mapping {
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
* mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
* error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
* <p>
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
*
* @return the qualifiers
* @see Qualifier
*/
Class<? extends Annotation>[] qualifiedBy() default { };
@ -199,6 +334,8 @@ public @interface Mapping {
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
* number of qualifiers as no custom annotation types are needed.
* <p>
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
*
* @return One or more qualifier name(s)
* @see #qualifiedBy()
@ -206,6 +343,73 @@ public @interface Mapping {
*/
String[] qualifiedByName() default { };
/**
* A qualifier can be specified to aid the selection process of a suitable presence check method.
* This is useful in case multiple presence check methods qualify and thus would result in an
* 'Ambiguous presence check methods found' error.
* A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
* This is similar to the {@link #qualifiedBy()}, but it is only applied for {@link Condition} methods.
*
* @return the qualifiers
* @see Qualifier
* @see #qualifiedBy()
* @since 1.5
*/
Class<? extends Annotation>[] conditionQualifiedBy() default { };
/**
* String-based form of qualifiers for condition / presence check methods;
* When looking for a suitable presence check method for a given property, MapStruct will
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
* for each of the specified qualifier names.
*
* This is similar like {@link #qualifiedByName()} but it is only applied for {@link Condition} methods.
* <p>
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
* number of qualifiers as no custom annotation types are needed.
* </p>
*
*
* @return One or more qualifier name(s)
* @see #conditionQualifiedBy()
* @see #qualifiedByName()
* @see Named
* @since 1.5
*/
String[] conditionQualifiedByName() default { };
/**
* A conditionExpression {@link String} based on which the specified property is to be checked
* whether it is present or not.
* <p>
* Currently, Java is the only supported "expression language" and expressions must be given in form of Java
* expressions using the following format: {@code java(<EXPRESSION>)}. For instance the mapping:
* <pre><code>
* &#64;Mapping(
* target = "someProp",
* conditionExpression = "java(s.getAge() &#60; 18)"
* )
* </code></pre>
* <p>
* will cause the following target property assignment to be generated:
* <pre><code>
* if (s.getAge() &#60; 18) {
* targetBean.setSomeProp( s.getSomeProp() );
* }
* </code></pre>
* <p>
* Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be
* imported via {@link Mapper#imports()}.
* <p>
* This attribute can not be used together with {@link #expression()} or {@link #constant()}.
*
* @return An expression specifying a condition check for the designated property
*
* @since 1.5
*/
String conditionExpression() default "";
/**
* Specifies the result type of the mapping method to be used in case multiple mapping methods qualify.
*
@ -286,4 +490,17 @@ public @interface Mapping {
NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
default NullValuePropertyMappingStrategy.SET_TO_NULL;
/**
* Allows detailed control over the mapping process.
*
* @return the mapping control
*
* @since 1.4
*
* @see org.mapstruct.control.DeepClone
* @see org.mapstruct.control.NoComplexMapping
* @see org.mapstruct.control.MappingControl
*/
Class<? extends Annotation> mappingControl() default MappingControl.class;
}

View File

@ -23,12 +23,132 @@ public final class MappingConstants {
/**
* In an {@link ValueMapping} this represents any source that is not already mapped by either a defined mapping or
* by means of name based mapping.
*
* NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}.
*/
public static final String ANY_REMAINING = "<ANY_REMAINING>";
/**
* In an {@link ValueMapping} this represents any source that is not already mapped by a defined mapping.
*
* NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}.
*
*/
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";
/**
* In an {@link ValueMapping} this represents any target that will be mapped to an
* {@link java.lang.IllegalArgumentException} which will be thrown at runtime.
* <p>
* NOTE: The value is only applicable to {@link ValueMapping#target()} and not to {@link ValueMapping#source()}.
*/
public static final String THROW_EXCEPTION = "<THROW_EXCEPTION>";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that adds a suffix to the source enum.
*
* @since 1.4
*/
public static final String SUFFIX_TRANSFORMATION = "suffix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that strips a suffix from the source
* enum.
*
* @since 1.4
*/
public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that adds a prefix to the source enum.
*
* @since 1.4
*/
public static final String PREFIX_TRANSFORMATION = "prefix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that strips a prefix from the source
* enum.
*
* @since 1.4
*/
public static final String STRIP_PREFIX_TRANSFORMATION = "stripPrefix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that applies case transformation
* at the source.
*
* @since 1.5
*/
public static final String CASE_TRANSFORMATION = "case";
/**
* Specifies the component model constants to which the generated mapper should adhere.
* It can be used with the annotation {@link Mapper#componentModel()} or {@link MapperConfig#componentModel()}
*
* <p>
* <strong>Example:</strong>
* </p>
* <pre><code class='java'>
* // Spring component model
* &#64;Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
* </code></pre>
*
* @since 1.5.0
*/
public static final class ComponentModel {
private ComponentModel() {
}
/**
* The mapper uses no component model, instances are typically retrieved
* via {@link org.mapstruct.factory.Mappers#getMapper(java.lang.Class)}
*
*/
public static final String DEFAULT = "default";
/**
* The generated mapper is an application-scoped CDI bean and can be retrieved via @Inject.
* The annotations are either from {@code javax} or {@code jakarta}.
* Priority have the {@code javax} annotations.
* In case you want to only use Jakarta then use {@link #JAKARTA_CDI}.
*
* @see #JAKARTA_CDI
*/
public static final String CDI = "cdi";
/**
* The generated mapper is a Spring bean and can be retrieved via @Autowired
*
*/
public static final String SPRING = "spring";
/**
* The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject.
* The annotations are either from {@code javax.inject} or {@code jakarta.inject}.
* Priority have the {@code javax.inject} annotations.
* In case you want to only use Jakarta then use {@link #JAKARTA}.
*
* @see #JAKARTA
*/
public static final String JSR330 = "jsr330";
/**
* The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject.
* The annotations are from {@code jakarta.inject}.
* In case you want to use {@code javax.inject} then use {@link #JSR330}.
*
* @see #JSR330
*/
public static final String JAKARTA = "jakarta";
/**
* The generated mapper is an application-scoped Jakarta CDI bean and can be retrieved via @Inject.
* @see #CDI
*/
public static final String JAKARTA_CDI = "jakarta-cdi";
}
}

View File

@ -17,6 +17,43 @@ import java.lang.annotation.Target;
* <p>
* <b>NOTE:</b> The parameter passed as a mapping target <b>must</b> not be {@code null}.
*
* <p>
* <strong>Example 1:</strong> Update exist bean without return value
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface HumanMapper {
* void updateHuman(HumanDto humanDto, @MappingTarget Human human);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* &#64;Override
* public void updateHuman(HumanDto humanDto, Human human) {
* human.setName( humanDto.getName() );
* // ...
* }
* </code></pre>
* <p>
* <strong>Example 2:</strong> Update exist bean and return it
* </p>
* <pre><code class='java'>
* &#64;Mapper
* public interface HumanMapper {
* Human updateHuman(HumanDto humanDto, @MappingTarget Human human);
* }
* </code></pre>
* // generates:
* <pre><code class='java'>
* &#64;Override
* public Human updateHuman(HumanDto humanDto, Human human) {
* // ...
* human.setName( humanDto.getName() );
* return human;
* }
*</code></pre>
*
*
* @author Andreas Gudian
*/
@Target(ElementType.PARAMETER)

View File

@ -12,11 +12,37 @@ import java.lang.annotation.Target;
/**
* Configures the mappings of several bean attributes.
* <p>
* <strong>TIP: When using Java 8 or later, you can omit the @Mappings
* wrapper annotation and directly specify several @Mapping annotations on one method.</strong>
*
* <p>These two examples are equal.
* </p>
* <pre><code class='java'>
* // before Java 8
* &#64;Mapper
* public interface MyMapper {
* &#64;Mappings({
* &#64;Mapping(target = "firstProperty", source = "first"),
* &#64;Mapping(target = "secondProperty", source = "second")
* })
* HumanDto toHumanDto(Human human);
* }
* </code></pre>
* <pre><code class='java'>
* // Java 8 and later
* &#64;Mapper
* public interface MyMapper {
* &#64;Mapping(target = "firstProperty", source = "first"),
* &#64;Mapping(target = "secondProperty", source = "second")
* HumanDto toHumanDto(Human human);
* }
* </code></pre>
*
* @author Gunnar Morling
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface Mappings {
/**

View File

@ -14,7 +14,7 @@ import java.lang.annotation.Target;
* Marks mapping methods with the given qualifier name. Can be used to qualify a single method or all methods of a given
* type by specifying this annotation on the type level.
* <p>
* Will be used to to select the correct mapping methods when mapping a bean property type, element of an iterable type
* Will be used to select the correct mapping methods when mapping a bean property type, element of an iterable type
* or the key/value of a map type.
* <p>
* Example (both methods of {@code Titles} are capable to convert a string, but the ambiguity is resolved by applying

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;
/**
* To be used as a default value for enum class annotation elements.
*
* @author Ben Zegveld
* @since 1.6
*/
enum NullEnum {
}

View File

@ -8,7 +8,7 @@ package org.mapstruct;
/**
* Strategy for dealing with null source values.
*
* <b>Note:</b> This strategy is not in effect when the a specific source presence check method is defined
* <b>Note:</b> This strategy is not in effect when a specific source presence check method is defined
* in the service provider interface (SPI).
* <p>
* <b>Note</b>: some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder

View File

@ -10,7 +10,7 @@ package org.mapstruct;
* {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping}
* and {@link Mapping}.
* Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will
* overide {@link Mapper}
* override {@link Mapper}
*
* The enum <b>only applies to update methods</b>: methods that update a pre-existing target (annotated with
* {@code @}{@link MappingTarget}).

View File

@ -14,23 +14,70 @@ import java.lang.annotation.Target;
* Declares an annotation type to be a qualifier. Qualifier annotations allow unambiguously identify a suitable mapping
* method in case several methods qualify to map a bean property, iterable element etc.
* <p>
* For more info see:
* Can be used in:
* <ul>
* <li>{@link Mapping#qualifiedBy() }</li>
* <li>{@link BeanMapping#qualifiedBy() }</li>
* <li>{@link IterableMapping#qualifiedBy() }</li>
* <li>{@link MapMapping#keyQualifiedBy() }</li>
* <li>{@link MapMapping#valueQualifiedBy() }</li>
* <li>{@link SubclassMapping#qualifiedBy() }</li>
* </ul>
* Example:
* <p><strong>Example:</strong></p>
* <pre><code class='java'>
* // create qualifiers
* &#64;Qualifier
* &#64;Target(ElementType.TYPE)
* &#64;Retention(RetentionPolicy.CLASS)
* public &#64;interface TitleTranslator {}
*
* <pre>
* &#64;Qualifier
* &#64;Target(ElementType.METHOD)
* &#64;Retention(RetentionPolicy.CLASS)
* public &#64;interface EnglishToGerman {
* public @interface EnglishToGerman {}
*
* &#64;Qualifier
* &#64;Target(ElementType.METHOD)
* &#64;Retention(RetentionPolicy.CLASS)
* public @interface GermanToEnglish {}
* </code></pre>
* <pre><code class='java'>
* // we can create class with map methods
* &#64;TitleTranslator
* public class Titles {
* &#64;EnglishToGerman
* public String translateTitleEnglishToGerman(String title) {
* // some mapping logic
* }
* &#64;GermanToEnglish
* public String translateTitleGermanToEnglish(String title) {
* // some mapping logic
* }
* }
* </pre>
* </code></pre>
* <pre><code class='java'>
* // usage
* &#64;Mapper( uses = Titles.class )
* public interface MovieMapper {
* &#64;Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
* GermanRelease toGerman( OriginalRelease movies );
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class MovieMapperImpl implements MovieMapper {
* private final Titles titles = new Titles();
* &#64;Override
* public GermanRelease toGerman(OriginalRelease movies) {
* if ( movies == null ) {
* return null;
* }
* GermanRelease germanRelease = new GermanRelease();
* germanRelease.setTitle( titles.translateTitleEnglishToGerman( movies.getTitle() ) );
* return germanRelease;
* }
* }
* </code></pre>
*
* <b>NOTE:</b> Qualifiers should have {@link RetentionPolicy#CLASS}.
*

View File

@ -0,0 +1,74 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a method as a <em>check method</em> to check if a source parameter needs to be mapped.
* <p>
* By default, source parameters are checked against {@code null}, unless they are primitives.
* <p>
* Check methods have to return {@code boolean}.
* The following parameters are accepted for the presence check methods:
* <ul>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
* for a method to be considered as a source check method.
*
* <pre><code class='java'>
* public class PresenceCheckUtils {
*
* &#64;SourceParameterCondition
* public static boolean isDefined(Car car) {
* return car != null &#38;&#38; car.getId() != null;
* }
* }
*
* &#64;Mapper(uses = PresenceCheckUtils.class)
* public interface CarMapper {
*
* CarDto map(Car car);
* }
* </code></pre>
*
* The following implementation of {@code CarMapper} will be generated:
*
* <pre><code class='java'>
* public class CarMapperImpl implements CarMapper {
*
* &#64;Override
* public CarDto map(Car car) {
* if ( !PresenceCheckUtils.isDefined( car ) ) {
* return null;
* }
*
* CarDto carDto = new CarDto();
*
* carDto.setId( car.getId() );
* // ...
*
* return carDto;
* }
* }
* </code></pre>
*
* @author Filip Hrisafov
* @since 1.6
* @see Condition @Condition
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.CLASS)
@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
public @interface SourceParameterCondition {
}

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a <em>presence check method</em> parameter as a source property name parameter.
* <p>
* This parameter enables conditional filtering based on source property name at run-time.
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
* </p>
*
* @author Oliver Erhart
* @since 1.6
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
public @interface SourcePropertyName {
}

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;
/**
* Strategy for dealing with subclassMapping annotated methods.
*
* @since 1.5
* @author Ben Zegveld
*/
public enum SubclassExhaustiveStrategy {
/**
* If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
* annotated mapping then a compilation error will be thrown.
*/
COMPILE_ERROR,
/**
* If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
* annotated mapping then an {@link IllegalArgumentException} will be thrown if a call is made with a type for which
* there is no {@link SubclassMapping} available.
*/
RUNTIME_EXCEPTION;
}

View File

@ -0,0 +1,110 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.util.Experimental;
/**
* Configures the mapping to handle hierarchy of the source type.
* <p>
* The subclass to be mapped is to be specified via {@link #source()}.
* The subclass to map to is to be specified via {@link #target()}.
* </p>
* <p>
* This annotation can be combined with &#64;Mapping annotations.
* </p>
*
* <pre><code class='java'>
* &#64;Mapper
* public interface MyMapper {
* &#64;SubclassMapping (target = TargetSubclass.class, source = SourceSubclass.class)
* TargetParent mapParent(SourceParent parent);
*
* TargetSubclass mapSubclass(SourceSubclass subInstant);
* }
* </code></pre>
* Below follow examples of the implementation for the mapParent method.
* <strong>Example 1:</strong> For parents that cannot be created. (e.g. abstract classes or interfaces)
* <pre><code class='java'>
* // generates
* &#64;Override
* public TargetParent mapParent(SourceParent parent) {
* if (parent instanceof SourceSubclass) {
* return mapSubclass( (SourceSubclass) parent );
* }
* else {
* throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for "
* + parent.getClass());
* }
* }
* </code></pre>
* <strong>Example 2:</strong> For parents that can be created. (e.g. normal classes or interfaces with
* &#64;Mapper( uses = ObjectFactory.class ) )
* <pre><code class='java'>
* // generates
* &#64;Override
* public TargetParent mapParent(SourceParent parent) {
* TargetParent targetParent1;
* if (parent instanceof SourceSubclass) {
* targetParent1 = mapSubclass( (SourceSubclass) parent );
* }
* else {
* targetParent1 = new TargetParent();
* // ...
* }
* }
* </code></pre>
*
* @author Ben Zegveld
* @since 1.5
*/
@Repeatable(value = SubclassMappings.class)
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Experimental
public @interface SubclassMapping {
/**
* @return the source subclass to check for before using the default mapping as fallback.
*/
Class<?> source();
/**
* @return the target subclass to map the source to.
*/
Class<?> target();
/**
* A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
* mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
* error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
*
* @return the qualifiers
* @see Qualifier
*/
Class<? extends Annotation>[] qualifiedBy() default {};
/**
* String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
* for each of the specified qualifier names.
* <p>
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
* number of qualifiers as no custom annotation types are needed.
*
* @return One or more qualifier name(s)
* @see #qualifiedBy()
* @see Named
*/
String[] qualifiedByName() default {};
}

View File

@ -0,0 +1,58 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.util.Experimental;
/**
* Configures the SubclassMappings of several subclasses.
* <p>
* <strong>TIP: When using java 8 or later, you can omit the @SubclassMappings
* Wrapper annotation and directly specify several @SubclassMapping annotations
* on one method.</strong>
* </p>
* <p>These two examples are equal.
* </p>
* <pre><code class='java'>
* // before java 8
* &#64;Mapper
* public interface MyMapper {
* &#64;SubclassMappings({
* &#64;SubclassMapping(source = FirstSub.class, target = FirstTargetSub.class),
* &#64;SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
* })
* ParentTarget toParentTarget(Parent parent);
* }
* </code></pre>
* <pre><code class='java'>
* // java 8 and later
* &#64;Mapper
* public interface MyMapper {
* &#64;SubclassMapping(source = First.class, target = FirstTargetSub.class),
* &#64;SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
* ParentTarget toParentTarget(Parent parent);
* }
* </code></pre>
*
* @author Ben Zegveld
* @since 1.5
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
@Experimental
public @interface SubclassMappings {
/**
* @return the subclassMappings to apply.
*/
SubclassMapping[] value();
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a <em>presence check method</em> parameter as a target property name parameter.
* <p>
* This parameter enables conditional filtering based on target property name at run-time.
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
* </p>
* @author Nikola Ivačič
* @since 1.6
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
public @interface TargetPropertyName {
}

View File

@ -16,6 +16,35 @@ import java.lang.annotation.Target;
* Not more than one parameter can be declared as {@code TargetType} and that parameter needs to be of type
* {@link Class} (may be parameterized), or a super-type of it.
*
* <p>
* <strong>Example:</strong>
* </p>
* <pre><code class='java'>
* public class EntityFactory {
* public &lt;T extends BaseEntity&gt; T createEntity(@TargetType Class&lt;T&gt; entityClass) {
* return // ... custom factory logic
* }
* }
* &#64;Mapper(uses = EntityFactory.class)
* public interface CarMapper {
* CarEntity carDtoToCar(CarDto dto);
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class CarMapperImpl implements CarMapper {
* private final EntityFactory entityFactory = new EntityFactory();
* &#64;Override
* public CarEntity carDtoToCar(CarDto dto) {
* if ( dto == null ) {
* return null;
* }
* CarEntity carEntity = entityFactory.createEntity( CarEntity.class );
* return carEntity;
* }
* }
* </code></pre>
*
* @author Andreas Gudian
*/
@Target(ElementType.PARAMETER)

View File

@ -18,18 +18,16 @@ import java.lang.annotation.Target;
* <ol>
* <li>Enumeration to Enumeration</li>
* </ol>
* <p>
* <B>Example 1:</B>
* <b>Example 1:</b>
*
* <pre>
* <code>
* public enum OrderType { RETAIL, B2B, EXTRA, STANDARD, NORMAL }
* <pre><code>
* public enum OrderType { RETAIL, B2B, C2C, EXTRA, STANDARD, NORMAL }
*
* public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT }
*
* &#64;ValueMapping(source = "EXTRA", target = "SPECIAL"),
* &#64;ValueMapping(source = "STANDARD", target = "DEFAULT"),
* &#64;ValueMapping(source = "NORMAL", target = "DEFAULT")
* &#64;ValueMapping(target = "SPECIAL", source = "EXTRA"),
* &#64;ValueMapping(target = "DEFAULT", source = "STANDARD"),
* &#64;ValueMapping(target = "DEFAULT", source = "NORMAL")
* ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
* </code>
* Mapping result:
@ -45,13 +43,9 @@ import java.lang.annotation.Target;
* +---------------------+----------------------------+
* </pre>
*
* MapStruct will <B>WARN</B> on incomplete mappings. However, if for some reason no match is found an
* {@link java.lang.IllegalStateException} will be thrown.
* <p>
* <B>Example 2:</B>
* <b>Example 2:</b>
*
* <pre>
* <code>
* <pre><code>
* &#64;ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
* &#64;ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
* &#64;ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
@ -70,11 +64,27 @@ import java.lang.annotation.Target;
* +---------------------+----------------------------+
* </pre>
*
* <b>Example 3:</b>
*
* MapStruct will <B>WARN</B> on incomplete mappings. However, if for some reason no match is found, an
* {@link java.lang.IllegalStateException} will be thrown. This compile-time error can be avoided by
* using {@link MappingConstants#THROW_EXCEPTION} for {@link ValueMapping#target()}. It will result an
* {@link java.lang.IllegalArgumentException} at runtime.
* <pre><code>
* &#64;ValueMapping( source = "STANDARD", target = "DEFAULT" ),
* &#64;ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
* ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
* </code>
* Mapping result:
* {@link java.lang.IllegalArgumentException} with the error message:
* Unexpected enum constant: C2C
* </pre>
*
* @author Sjaak Derksen
*/
@Repeatable(ValueMappings.class)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface ValueMapping {
/**
* The source value constant to use for this mapping.
@ -104,6 +114,7 @@ public @interface ValueMapping {
* <ol>
* <li>enum constant name</li>
* <li>{@link MappingConstants#NULL}</li>
* <li>{@link MappingConstants#THROW_EXCEPTION}</li>
* </ol>
*
* @return The target value.

View File

@ -12,10 +12,35 @@ import java.lang.annotation.Target;
/**
* Constructs a set of value (constant) mappings.
* <p>
* <strong>TIP: When using Java 8 or later, you can omit the @ValueMappings
* wrapper annotation and directly specify several @ValueMapping annotations on one method.</strong>
*
* <p>These two examples are equal</p>
* <pre><code class='java'>
* // before Java 8
* &#64;Mapper
* public interface GenderMapper {
* &#64;ValueMappings({
* &#64;ValueMapping(target = "M", source = "MALE"),
* &#64;ValueMapping(target = "F", source = "FEMALE")
* })
* GenderDto mapToDto(Gender gender);
* }
* </code></pre>
* <pre><code class='java'>
* //Java 8 and later
* &#64;Mapper
* public interface GenderMapper {
* &#64;ValueMapping(target = "M", source = "MALE"),
* &#64;ValueMapping(target = "F", source = "FEMALE")
* GenderDto mapToDto(Gender gender);
* }
* </code></pre>
*
* @author Sjaak Derksen
*/
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ValueMappings {

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.control;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.mapstruct.util.Experimental;
/**
* Clones a source type to a target type (assuming source and target are of the same type).
*
* @author Sjaak Derksen
*
* @since 1.4
*/
@Retention(RetentionPolicy.CLASS)
@Experimental
@MappingControl( MappingControl.Use.MAPPING_METHOD )
public @interface DeepClone {
}

View File

@ -0,0 +1,152 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.control;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Controls which means of mapping are considered between the source and the target in mappings.
*
* <p>
* There are several applications of <code>MappingControl</code> conceivable. One application, "deep cloning" is
* explained below in the example.
* </p>
*
* <p>
* Another application is controlling so called "complex mappings", which are not always desirable and sometimes lead to
* unexpected behaviour and prolonged compilation time.
* </p>
*
* <p><strong>Example:</strong>Cloning of an object</p>
* <p>
* When all methods are allowed, MapStruct would make a shallow copy. It would take the <code>ShelveDTO</code> in
* the <code>FridgeDTO</code> and directly enter that as target on the target <code>FridgeDTO</code>. By disabling all
* other kinds of mappings apart from {@link MappingControl.Use#MAPPING_METHOD}, see {@link DeepClone} MapStruct is
* forced to generate mapping methods all through the object graph `FridgeDTO` and hence create a deep clone.
* </p>
* <pre><code class='java'>
* public class FridgeDTO {
*
* private ShelveDTO shelve;
*
* public ShelveDTO getShelve() {
* return shelve;
* }
*
* public void setShelve(ShelveDTO shelve) {
* this.shelve = shelve;
* }
* }
* </code></pre>
* <pre><code class='java'>
* public class ShelveDTO {
*
* private CoolBeerDTO coolBeer;
*
* public CoolBeerDTO getCoolBeer() {
* return coolBeer;
* }
*
* public void setCoolBeer(CoolBeerDTO coolBeer) {
* this.coolBeer = coolBeer;
* }
* }
* </code></pre>
* <pre><code class='java'>
* public class CoolBeerDTO {
*
* private String beerCount;
*
* public String getBeerCount() {
* return beerCount;
* }
*
* public void setBeerCount(String beerCount) {
* this.beerCount = beerCount;
* }
* }
* </code></pre>
*
* <pre><code class='java'>
* &#64;Mapper(mappingControl = DeepClone.class)
* public interface CloningMapper {
*
* CloningMapper INSTANCE = Mappers.getMapper( CloningMapper.class );
*
* FridgeDTO clone(FridgeDTO in);
*
* }
* </code></pre>
*
* @author Sjaak Derksen
*
* @since 1.4
*/
@Retention(RetentionPolicy.CLASS)
@Repeatable(MappingControls.class)
@Target( ElementType.ANNOTATION_TYPE )
@MappingControl( MappingControl.Use.DIRECT )
@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION )
@MappingControl( MappingControl.Use.MAPPING_METHOD )
@MappingControl( MappingControl.Use.COMPLEX_MAPPING )
public @interface MappingControl {
Use value();
enum Use {
/**
* Controls the mapping, allows for type conversion from source type to target type
* <p>
* Type conversions are typically supported directly in Java. The "toString()" is such an example,
* which allows for mapping for instance a {@link java.lang.Number} type to a {@link java.lang.String}.
* <p>
* Please refer to the MapStruct guide for more info.
*
* @since 1.4
*/
BUILT_IN_CONVERSION,
/**
* Controls the mapping from source to target type, allows mapping by calling:
* <ol>
* <li>A type conversion, passed into a mapping method</li>
* <li>A mapping method, passed into a type conversion</li>
* <li>A mapping method passed into another mapping method</li>
* </ol>
*
* @since 1.4
*/
COMPLEX_MAPPING,
/**
* Controls the mapping, allows for a direct mapping from source type to target type.
* <p>
* This means if source type and target type are of the same type, MapStruct will not perform
* any mappings anymore and assign the target to the source direct.
* <p>
* An exception are types from the package {@code java}, which will be mapped always directly.
*
* @since 1.4
*/
DIRECT,
/**
* Controls the mapping, allows for Direct Mapping from source type to target type.
* <p>
* The mapping method can be either a custom referred mapping method, or a MapStruct built in
* mapping method.
*
* @since 1.4
*/
MAPPING_METHOD
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.control;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Allows multiple {@link MappingControl} on a class declaration.
*
* @author Sjaak Derksen
*
* @since 1.4
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.ANNOTATION_TYPE)
public @interface MappingControls {
MappingControl[] value();
}

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.control;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.mapstruct.util.Experimental;
/**
* Disables complex mappings, mappings that require 2 mapping means (method, built-in conversion) to constitute
* a mapping from source to target.
*
* @see MappingControl.Use#COMPLEX_MAPPING
*
* @author Sjaak Derksen
*
* @since 1.4
*/
@Retention(RetentionPolicy.CLASS)
@Experimental
@MappingControl( MappingControl.Use.DIRECT )
@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION )
@MappingControl( MappingControl.Use.MAPPING_METHOD )
public @interface NoComplexMapping {
}

View File

@ -13,6 +13,6 @@
* This package contains several annotations which allow to configure how mapper interfaces are generated.
* </p>
*
* @see <a href="http://mapstruct.org/">MapStruct reference documentation</a>
* @see <a href="https://mapstruct.org/">MapStruct reference documentation</a>
*/
package org.mapstruct;

View File

@ -7,7 +7,7 @@ package org.mapstruct.factory;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.mapstruct.test.model.Foo;
import org.mapstruct.test.model.SomeClass;

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.7.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@ -34,6 +34,18 @@
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- Needed here so references to MapStruct Gem classes can be resolved during JavaDoc generation -->
<dependency>
<groupId>org.mapstruct.tools.gem</groupId>
<artifactId>gem-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
@ -54,7 +66,7 @@
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<outputDirectory>${project.build.directory}/freemarker-unpacked</outputDirectory>
<includes>META-INF/LICENSE.txt,META-INF/NOTICE.txt</includes>
<includes>META-INF/LICENSE</includes>
</artifactItem>
</artifactItems>
</configuration>
@ -88,7 +100,7 @@
<doctitle>MapStruct ${project.version}</doctitle>
<windowtitle>MapStruct ${project.version}</windowtitle>
<bottom>
<![CDATA[Copyright &copy; ${project.inceptionYear}-{currentYear} <a href="http://mapstruct.org/">MapStruct Authors</a>; All rights reserved. Released under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache Software License 2.0</a>.]]>
<![CDATA[Copyright &copy; ${project.inceptionYear}-{currentYear} <a href="https://mapstruct.org/">MapStruct Authors</a>; All rights reserved. Released under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache Software License 2.0</a>.]]>
</bottom>
<groups>
@ -96,18 +108,16 @@
<title>MapStruct API</title>
<packages>org.mapstruct*</packages>
</group>
<group>
<title>MapStruct Processor SPI</title>
<packages>org.mapstruct.ap.spi*</packages>
</group>
<group>
<title>MapStruct Processor</title>
<packages>org.mapstruct.ap*</packages>
</group>
</groups>
<doclet>org.jboss.apiviz.APIviz</doclet>
<docletArtifact>
<groupId>org.jboss.apiviz</groupId>
<artifactId>apiviz</artifactId>
<version>1.3.2.GA</version>
</docletArtifact>
<useStandardDocletOptions>true</useStandardDocletOptions>
<charset>UTF-8</charset>
<encoding>UTF-8</encoding>
@ -116,6 +126,23 @@
<version>true</version>
<author>true</author>
<keywords>true</keywords>
<!--
There is a bug in JDK 11 (https://bugs.openjdk.java.net/browse/JDK-8215291) which doesn't work correctly when searching and adds undefined.
It has been fixed since JDK 12, but not yet backported to JDK 11 (https://bugs.openjdk.java.net/browse/JDK-8244171).
One workaround is https://stackoverflow.com/a/57284322/1115491.
-->
<bottom>
<![CDATA[
<script>
if (typeof useModuleDirectories !== 'undefined') {
useModuleDirectories = false;
}
</script>
]]>
</bottom>
<additionalJOption>--allow-script-in-comments</additionalJOption>
</configuration>
<executions>
<execution>
@ -135,7 +162,7 @@
<descriptor>${basedir}/src/main/assembly/dist.xml</descriptor>
</descriptors>
<finalName>mapstruct-${project.version}</finalName>
<tarLongFileMode>gnu</tarLongFileMode>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
<executions>
<execution>
@ -156,4 +183,21 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jdk-11-or-newer</id>
<activation>
<jdk>[11</jdk>
</activation>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View File

@ -42,11 +42,7 @@
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>target/freemarker-unpacked/META-INF/NOTICE.txt</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>target/freemarker-unpacked/META-INF/LICENSE.txt</source>
<source>target/freemarker-unpacked/META-INF/LICENSE</source>
<outputDirectory>etc/freemarker</outputDirectory>
</file>
<file>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.7.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@ -21,9 +21,9 @@
<name>MapStruct Documentation</name>
<properties>
<asciidoctorj.pdf.version>1.5.0-alpha.11</asciidoctorj.pdf.version>
<asciidoctorj.version>1.5.4</asciidoctorj.version>
<jruby.version>1.7.21</jruby.version>
<asciidoctorj.pdf.version>1.6.0</asciidoctorj.pdf.version>
<asciidoctorj.version>2.5.1</asciidoctorj.version>
<jruby.version>9.2.17.0</jruby.version>
</properties>
<dependencies>
</dependencies>
@ -33,7 +33,7 @@
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<version>2.1.0</version>
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
@ -52,7 +52,6 @@
</dependency>
</dependencies>
<configuration>
<sourceHighlighter>coderay</sourceHighlighter>
<sourceDocumentName>mapstruct-reference-guide.asciidoc</sourceDocumentName>
<attributes>
<mapstructVersion>${project.version}</mapstructVersion>

View File

@ -0,0 +1,16 @@
[[introduction]]
== Introduction
MapStruct is a Java http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html[annotation processor] for the generation of type-safe bean mapping classes.
All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar.
Compared to writing mapping code from hand, MapStruct saves time by generating code which is tedious and error-prone to write. Following a convention over configuration approach, MapStruct uses sensible defaults but steps out of your way when it comes to configuring or implementing special behavior.
Compared to dynamic mapping frameworks, MapStruct offers the following advantages:
* Fast execution by using plain method invocations instead of reflection
* Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no accidental mapping of an order entity into a customer DTO etc.
* Clear error-reports at build time, if
** mappings are incomplete (not all target properties are mapped)
** mappings are incorrect (cannot find a proper mapping method or type conversion)

View File

@ -0,0 +1,608 @@
== Advanced mapping options
This chapter describes several advanced options which allow to fine-tune the behavior of the generated mapping code as needed.
[[default-values-and-constants]]
=== Default values and constants
Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values. When the target type is a primitive or a boxed type, the String value is taken literal. Bit / octal / decimal / hex patterns are allowed in such a case as long as they are a valid literal.
In all other cases, constant or default values are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property.
A mapping with a constant must not include a reference to a source property. The following example shows some mappings using default values and constants:
.Mapping method with default values and constants
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(uses = StringListMapper.class)
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
@Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
@Mapping(target = "stringConstant", constant = "Constant Value")
@Mapping(target = "integerConstant", constant = "14")
@Mapping(target = "longWrapperConstant", constant = "3001")
@Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
@Mapping(target = "stringListConstants", constant = "jack-jill-tom")
Target sourceToTarget(Source s);
}
----
====
If `s.getStringProp() == null`, then the target property `stringProperty` will be set to `"undefined"` instead of applying the value from `s.getStringProp()`. If `s.getLongProperty() == null`, then the target property `longProperty` will be set to `-1`.
The String `"Constant Value"` is set as is to the target property `stringConstant`. The value `"3001"` is type-converted to the `Long` (wrapper) class of target property `longWrapperConstant`. Date properties also require a date format. The constant `"jack-jill-tom"` demonstrates how the hand-written class `StringListMapper` is invoked to map the dash-separated list into a `List<String>`.
[[expressions]]
=== Expressions
By means of Expressions it will be possible to include constructs from a number of languages.
Currently only Java is supported as a language. This feature is e.g. useful to invoke constructors. The entire source object is available for usage in the expression. Care should be taken to insert only valid Java code: MapStruct will not validate the expression at generation-time, but errors will show up in the generated classes during compilation.
The example below demonstrates how two source properties can be mapped to one target:
.Mapping method using an expression
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
----
====
The example demonstrates how the source properties `time` and `format` are composed into one target property `TimeAndFormat`. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `TimeAndFormat` class (unless it's used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining `imports` on the `@Mapper` annotation.
.Declaring an import
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
imports org.sample.TimeAndFormat;
@Mapper( imports = TimeAndFormat.class )
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "timeAndFormat",
expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
----
====
[[default-expressions]]
=== Default Expressions
Default expressions are a combination of default values and expressions. They will only be used when the source attribute is `null`.
The same warnings and restrictions apply to default expressions that apply to expressions. Only Java is supported, and MapStruct will not validate the expression at generation-time.
The example below demonstrates how a default expression can be used to set a value when the source attribute is not present (e.g. is `null`):
.Mapping method using a default expression
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
imports java.util.UUID;
@Mapper( imports = UUID.class )
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target="id", source="sourceId", defaultExpression = "java( UUID.randomUUID().toString() )")
Target sourceToTarget(Source s);
}
----
====
The example demonstrates how to use defaultExpression to set an `ID` field if the source field is null, this could be used to take the existing `sourceId` from the source object if it is set, or create a new `Id` if it isn't. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `UUID` class (unless its used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining imports on the @Mapper annotation (see <<expressions>>).
[[sub-class-mappings]]
=== Subclass Mapping
When both input and result types have an inheritance relation, you would want the correct specialization be mapped to the matching specialization.
Suppose an `Apple` and a `Banana`, which are both specializations of `Fruit`.
.Specifying the sub class mappings of a fruit mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FruitMapper {
@SubclassMapping( source = AppleDto.class, target = Apple.class )
@SubclassMapping( source = BananaDto.class, target = Banana.class )
Fruit map( FruitDto source );
}
----
====
If you would just use a normal mapping both the `AppleDto` and the `BananaDto` would be made into a `Fruit` object, instead of an `Apple` and a `Banana` object.
By using the subclass mapping an `AppleDtoToApple` mapping will be used for `AppleDto` objects, and an `BananaDtoToBanana` mapping will be used for `BananaDto` objects.
If you try to map a `GrapeDto` it would still turn it into a `Fruit`.
In the case that the `Fruit` is an abstract class or an interface, you would get a compile error.
To allow mappings for abstract classes or interfaces you need to set the `subclassExhaustiveStrategy` to `RUNTIME_EXCEPTION`, you can do this at the `@MapperConfig`, `@Mapper` or `@BeanMapping` annotations. If you then pass a `GrapeDto` an `IllegalArgumentException` will be thrown because it is unknown how to map a `GrapeDto`.
Adding the missing (`@SubclassMapping`) for it will fix that.
<<selection-based-on-qualifiers>> can be used to further control which methods may be chosen to map a specific subclass. For that, you will need to use one of `SubclassMapping#qualifiedByName` or `SubclassMapping#qualifiedBy`.
[TIP]
====
If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings.
====
[NOTE]
====
Combining `@SubclassMapping` with update methods is not supported.
If you try to use subclass mappings there will be a compile error.
The same issue exists for the `@Context` and `@TargetType` parameters.
====
[[determining-result-type]]
=== Determining the result type
When result types have an inheritance relation, selecting either mapping method (`@Mapping`) or a factory method (`@BeanMapping`) can become ambiguous. Suppose an Apple and a Banana, which are both specializations of Fruit.
.Specifying the result type of a bean mapping method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper( uses = FruitFactory.class )
public interface FruitMapper {
@BeanMapping( resultType = Apple.class )
Fruit map( FruitDto source );
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class FruitFactory {
public Apple createApple() {
return new Apple( "Apple" );
}
public Banana createBanana() {
return new Banana( "Banana" );
}
}
----
====
So, which `Fruit` must be factorized in the mapping method `Fruit map(FruitDto source);`? A `Banana` or an `Apple`? Here's where the `@BeanMapping#resultType` comes in handy. It controls the factory method to select, or in absence of a factory method, the return type to create.
[TIP]
====
The same mechanism is present on mapping: `@Mapping#resultType` and works like you expect it would: it selects the mapping method with the desired result type when present.
====
[TIP]
====
The mechanism is also present on iterable mapping and map mapping. `@IterableMapping#elementTargetType` is used to select the mapping method with the desired element in the resulting `Iterable`. For the `@MapMapping` a similar purpose is served by means of `#MapMapping#keyTargetType` and `MapMapping#valueTargetType`.
====
[[mapping-result-for-null-arguments]]
=== Controlling mapping result for 'null' arguments
MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned.
However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MapperConfig`, 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.
* *Iterables / Arrays*: an empty iterable will be returned.
* *Maps*: an empty map will be returned.
The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MapperConfig#nullValueMappingStrategy`.
[[mapping-result-for-null-collection-or-map-arguments]]
=== Controlling mapping result for 'null' collection or map arguments
With <<mapping-result-for-null-arguments>> it is possible to control how the return type should be constructed when the source argument of the mapping method is `null`.
That is applied for all mapping methods (bean, iterable or map mapping methods).
However, MapStruct also offers a more dedicated way to control how collections / maps should be mapped.
e.g. return default (empty) collections / maps, but return `null` for beans.
For collections (iterables) this can be controlled through:
* `MapperConfig#nullValueIterableMappingStrategy`
* `Mapper#nullValueIterableMappingStrategy`
* `IterableMapping#nullValueMappingStrategy`
For maps this can be controlled through:
* `MapperConfig#nullValueMapMappingStrategy`
* `Mapper#nullValueMapMappingStrategy`
* `MapMapping#nullValueMappingStrategy`
How the value of the `NullValueMappingStrategy` is applied is the same as in <<mapping-result-for-null-arguments>>
[[mapping-result-for-null-properties]]
=== Controlling mapping result for 'null' properties in bean mappings (update mapping methods only).
MapStruct offers control over the property to set in an `@MappingTarget` annotated target bean when the source property equals `null` or the presence check method results in 'absent'.
By default the target property will be set to null.
However:
1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result can be altered to return *default* values.
For `List` MapStruct generates an `ArrayList`, for `Map` a `LinkedHashMap`, for arrays an empty array, for `String` `""` and for primitive / boxed types a representation of `false` or `0`.
For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`.
2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target.
The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MapperConfig#nullValuePropertyMappingStrategy`.
[NOTE]
====
Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (see `CollectionMappingStrategy`), MapStruct will always generate a source property
null check, regardless of the value of the `NullValuePropertyMappingStrategy`, to avoid addition of `null` to the target collection or map. Since the target is assumed to be initialised this strategy will not be applied.
====
[TIP]
====
`NullValuePropertyMappingStrategy` also applies when the presence checker returns `not present`.
====
[[checking-source-property-for-null-arguments]]
=== Controlling checking result for 'null' properties in bean mapping
MapStruct offers control over when to generate a `null` check. By default (`nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION`) a `null` check will be generated for:
* direct setting of source value to target value when target is primitive and source is not.
* applying type conversion and then:
.. calling the setter on the target.
.. calling another type conversion and subsequently calling the setter on the target.
.. calling a mapping method and subsequently calling the setter on the target.
First calling a mapping method on the source property is not protected by a null check. Therefore generated mapping methods will do a null check prior to carrying out mapping on a source property. Handwritten mapping methods must take care of null value checking. They have the possibility to add 'meaning' to `null`. For instance: mapping `null` to a default value.
The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean.
The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MapperConfig#nullValueCheckStrategy`.
[[source-presence-check]]
=== Source presence checking
Some frameworks generate bean properties that have a source presence checker. Often this is in the form of a method `hasXYZ`, `XYZ` being a property on the source bean in a bean mapping method. MapStruct will call this `hasXYZ` instead of performing a `null` check when it finds such `hasXYZ` method.
[TIP]
====
The source presence checker name can be changed in the MapStruct service provider interface (SPI). It can also be deactivated in this way.
====
[NOTE]
====
Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (see `CollectionMappingStrategy`), MapStruct will always generate a source property
null check, regardless the value of the `NullValueCheckStrategy` to avoid addition of `null` to the target collection or map.
====
[[conditional-mapping]]
=== Conditional Mapping
Conditional Mapping is a type of <<source-presence-check>>.
The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not.
Conditional mapping can also be used to check if a source parameter should be mapped or not.
A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)`
e.g. if you only want to map a String property when it is not `null` and not empty then you can do something like:
.Mapper using custom condition check method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car);
@Condition
default boolean isNotEmpty(String value) {
return value != null && !value.isEmpty();
}
}
----
====
The generated mapper will look like:
.Custom condition check in generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
if ( isNotEmpty( car.getOwner() ) ) {
carDto.setOwner( car.getOwner() );
}
// Mapping of other properties
return carDto;
}
}
----
====
When using this in combination with an update mapping method it will replace the `null-check` there, for example:
.Update mapper using custom condition check method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
@Condition
default boolean isNotEmpty(String value) {
return value != null && !value.isEmpty();
}
}
----
====
The generated update mapper will look like:
.Custom condition check in generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car, CarDto carDto) {
if ( car == null ) {
return carDto;
}
if ( isNotEmpty( car.getOwner() ) ) {
carDto.setOwner( car.getOwner() );
} else {
carDto.setOwner( null );
}
// Mapping of other properties
return carDto;
}
}
----
====
Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method:
.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(target = "owner", source = "ownerName")
CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
@Condition
default boolean isNotEmpty(
String value,
@TargetPropertyName String targetPropertyName,
@SourcePropertyName String sourcePropertyName
) {
if ( targetPropertyName.equals( "owner" )
&& sourcePropertyName.equals( "ownerName" ) ) {
return value != null
&& !value.isEmpty()
&& !value.equals( value.toLowerCase() );
}
return value != null && !value.isEmpty();
}
}
----
====
The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like:
.Custom condition check in generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car, CarDto carDto) {
if ( car == null ) {
return carDto;
}
if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) {
carDto.setOwner( car.getOwner() );
} else {
carDto.setOwner( null );
}
// Mapping of other properties
return carDto;
}
}
----
====
[IMPORTANT]
====
If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself.
====
[NOTE]
====
Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input.
`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods.
====
<<selection-based-on-qualifiers>> is also valid for `@Condition` methods.
In order to use a more specific condition method you will need to use one of `Mapping#conditionQualifiedByName` or `Mapping#conditionQualifiedBy`.
If we want to only map cars that have an id provided then we can do something like:
.Mapper using custom condition source parameter check method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car);
@SourceParameterCondition
default boolean hasCar(Car car) {
return car != null && car.getId() != null;
}
}
----
====
The generated mapper will look like:
.Custom condition source parameter check generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( !hasCar( car ) ) {
return null;
}
CarDto carDto = new CarDto();
carDto.setOwner( car.getOwner() );
// Mapping of other properties
return carDto;
}
}
----
====
[[exceptions]]
=== Exceptions
Calling applications may require handling of exceptions when calling a mapping method. These exceptions could be thrown by hand-written logic and by the generated built-in mapping methods or type-conversions of MapStruct. When the calling application requires handling of exceptions, a throws clause can be defined in the mapping method:
.Mapper using custom method declaring checked exception
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(uses = HandWritten.class)
public interface CarMapper {
CarDto carToCarDto(Car car) throws GearException;
}
----
====
The hand written logic might look like this:
.Custom mapping method declaring checked exception
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class HandWritten {
private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"};
public String toGear(Integer gear) throws GearException, FatalException {
if ( gear == null ) {
throw new FatalException("null is not a valid gear");
}
if ( gear < 0 && gear > GEAR.length ) {
throw new GearException("invalid gear");
}
return GEAR[gear];
}
}
----
====
MapStruct now, wraps the `FatalException` in a `try-catch` block and rethrows an unchecked `RuntimeException`. MapStruct delegates handling of the `GearException` to the application logic because it is defined as throws clause in the `carToCarDto` method:
.try-catch block in generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
@Override
public CarDto carToCarDto(Car car) throws GearException {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
try {
carDto.setGear( handWritten.toGear( car.getGear() ) );
}
catch ( FatalException e ) {
throw new RuntimeException( e );
}
return carDto;
}
----
====
Some **notes** on null checks. MapStruct does provide null checking only when required: when applying type-conversions or constructing a new type by invoking its constructor. This means that the user is responsible in hand-written code for returning valid non-null objects. Also null objects can be handed to hand-written code, since MapStruct does not want to make assumptions on the meaning assigned by the user to a null object. Hand-written code has to deal with this.

View File

@ -0,0 +1,167 @@
== Reusing mapping configurations
This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types.
[[mapping-configuration-inheritance]]
=== Mapping configuration inheritance
Method-level configuration annotations such as `@Mapping`, `@BeanMapping`, `@IterableMapping`, etc., can be *inherited* from one mapping method to a *similar* method using the annotation `@InheritConfiguration`:
.Update method inheriting its configuration
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(target = "numberOfSeats", source = "seatCount")
Car carDtoToCar(CarDto car);
@InheritConfiguration
void carDtoIntoCar(CarDto carDto, @MappingTarget Car car);
}
----
====
The example above declares a mapping method `carDtoToCar()` with a configuration to define how the property `numberOfSeats` in the type `Car` shall be mapped. The update method that performs the mapping on an existing instance of `Car` needs the same configuration to successfully map all properties. Declaring `@InheritConfiguration` on the method lets MapStruct search for inheritance candidates to apply the annotations of the method that is inherited from.
One method *A* can inherit the configuration from another method *B* if all types of *A* (source types and result type) are assignable to the corresponding types of *B*.
Methods that are considered for inheritance need to be defined in the current mapper, a super class/interface, or in the shared configuration interface (as described in <<shared-configurations>>).
In case more than one method is applicable as source for the inheritance, the method name must be specified within the annotation: `@InheritConfiguration( name = "carDtoToCar" )`.
A method can use `@InheritConfiguration` and override or amend the configuration by additionally applying `@Mapping`, `@BeanMapping`, etc.
[NOTE]
====
`@InheritConfiguration` cannot refer to methods in a used mapper.
====
[[inverse-mappings]]
=== Inverse mappings
In case of bi-directional mappings, e.g. from entity to DTO and from DTO to entity, the mapping rules for the forward method and the reverse method are often similar and can simply be inversed by switching `source` and `target`.
Use the annotation `@InheritInverseConfiguration` to indicate that a method shall inherit the inverse configuration of the corresponding reverse method.
In the example below, there is no need to write the inverse mapping manually. Think of a case where there are several mappings, so writing the inverse ones can be cumbersome and error prone.
.Inverse mapping method inheriting its configuration and ignoring some of them
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDto carToDto(Car car);
@InheritInverseConfiguration
@Mapping(target = "numberOfSeats", ignore = true)
Car carDtoToCar(CarDto carDto);
}
----
====
Here the `carDtoToCar()` method is the reverse mapping method for `carToDto()`. Note that any attribute mappings from `carToDto()` will be applied to the corresponding reverse mapping method as well. They are automatically reversed and copied to the method with the `@InheritInverseConfiguration` annotation.
Specific mappings from the inversed method can (optionally) be overridden by `ignore`, `expression` or `constant` in the mapping, e.g. like this: `@Mapping(target = "numberOfSeats", ignore=true)`.
A method *A* is considered a *reverse* method of a method *B*, if the result type of *A* is the *same* as the single source type of *B* and if the single source type of *A* is the *same* as the result type of *B*.
Methods that are considered for inverse inheritance need to be defined in the current mapper, a super class/interface.
If multiple methods qualify, the method from which to inherit the configuration needs to be specified using the `name` property like this: `@InheritInverseConfiguration(name = "carToDto")`.
`@InheritConfiguration` takes, in case of conflict precedence over `@InheritInverseConfiguration`.
Configurations are inherited transitively. So if method `C` defines a mapping `@Mapping( target = "x", ignore = true)`, `B` defines a mapping `@Mapping( target = "y", ignore = true)`, then if `A` inherits from `B` inherits from `C`, `A` will inherit mappings for both property `x` and `y`.
`@Mapping#expression`, `@Mapping#defaultExpression`, `@Mapping#defaultValue` and `@Mapping#constant` are excluded (silently ignored) in `@InheritInverseConfiguration`.
`@Mapping#ignore` is only applied when `@Mapping#source` is also present in `@InheritInverseConfiguration`.
Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2 release. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping.
[NOTE]
====
`@InheritInverseConfiguration` cannot refer to methods in a used mapper.
====
[[shared-configurations]]
=== Shared configurations
MapStruct offers the possibility to define a shared configuration by pointing to a central interface annotated with `@MapperConfig`. For a mapper to use the shared configuration, the configuration interface needs to be defined in the `@Mapper#config` property.
The `@MapperConfig` annotation has the same attributes as the `@Mapper` annotation. Any attributes not given via `@Mapper` will be inherited from the shared configuration. Attributes specified in `@Mapper` take precedence over the attributes specified via the referenced configuration class. List properties such as `uses` are simply combined:
.Mapper configuration class and mapper using it
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@MapperConfig(
uses = CustomMapperViaMapperConfig.class,
unmappedTargetPolicy = ReportingPolicy.ERROR
)
public interface CentralConfig {
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
// Effective configuration:
// @Mapper(
// uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
// unmappedTargetPolicy = ReportingPolicy.ERROR
// )
public interface SourceTargetMapper {
...
}
----
====
The interface holding the `@MapperConfig` annotation may also declare *prototypes* of mapping methods that can be used to inherit method-level mapping annotations from. Such prototype methods are not meant to be implemented or used as part of the mapper API.
.Mapper configuration class with prototype methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@MapperConfig(
uses = CustomMapperViaMapperConfig.class,
unmappedTargetPolicy = ReportingPolicy.ERROR,
mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
)
public interface CentralConfig {
// Not intended to be generated, but to carry inheritable mapping annotations:
@Mapping(target = "primaryKey", source = "technicalKey")
BaseEntity anyDtoToEntity(BaseDto dto);
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } )
public interface SourceTargetMapper {
@Mapping(target = "numberOfSeats", source = "seatCount")
// additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto:
// @Mapping(target = "primaryKey", source = "technicalKey")
Car toCar(CarDto car)
}
----
====
The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingInheritanceStrategy()` configure when the method-level mapping configuration annotations are inherited from prototype methods in the interface to methods in the mapper:
* `EXPLICIT` (default): the configuration will only be inherited, if the target mapping method is annotated with `@InheritConfiguration` and the source and target types are assignable to the corresponding types of the prototype method, all as described in <<mapping-configuration-inheritance>>.
* `AUTO_INHERIT_FROM_CONFIG`: the configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritConfiguration(name = ...)` which will cause `AUTO_INHERIT_FROM_CONFIG` to be ignored.
* `AUTO_INHERIT_REVERSE_FROM_CONFIG`: the inverse configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritInverseConfiguration(name = ...)` which will cause ``AUTO_INHERIT_REVERSE_FROM_CONFIG` to be ignored.
* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`.

View File

@ -0,0 +1,271 @@
== Customizing mappings
Sometimes it's needed to apply custom logic before or after certain mapping methods. MapStruct provides two ways for doing so: decorators which allow for a type-safe customization of specific mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a generic customization of mapping methods with given source or target types.
[[customizing-mappers-using-decorators]]
=== Mapping customization with decorators
In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can't be set by a generated method implementation. MapStruct supports this requirement using decorators.
[TIP]
When working with the component model `cdi`, use https://docs.jboss.org/cdi/spec/1.0/html/decorators.html[CDI decorators] with MapStruct mappers instead of the `@DecoratedWith` annotation described here.
To apply a decorator to a mapper class, specify it using the `@DecoratedWith` annotation.
.Applying a decorator
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}
----
====
The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class which allows to only implement those methods of the mapper interface which you want to customize. For all non-implemented methods, a simple delegation to the original mapper will be generated using the default generation routine.
The `PersonMapperDecorator` shown below customizes the `personToPersonDto()`. It sets an additional attribute which is not present in the source type of the mapping. The `addressToAddressDto()` method is not customized.
.Implementing a decorator
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public abstract class PersonMapperDecorator implements PersonMapper {
private final PersonMapper delegate;
public PersonMapperDecorator(PersonMapper delegate) {
this.delegate = delegate;
}
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setFullName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
----
====
The example shows how you can optionally inject a delegate with the generated default implementation and use this delegate in your customized decorator methods.
For a mapper with `componentModel = "default"`, define a constructor with a single parameter which accepts the type of the decorated mapper.
When working with the component models `spring` or `jsr330`, this needs to be handled differently.
[[decorators-with-spring]]
==== Decorators with the Spring component model
When using `@DecoratedWith` on a mapper with component model `spring`, the generated implementation of the original mapper is annotated with the Spring annotation `@Qualifier("delegate")`. To autowire that bean in your decorator, add that qualifier annotation as well:
.Spring-based decorator
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public abstract class PersonMapperDecorator implements PersonMapper {
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
----
====
The generated class that extends the decorator is annotated with Spring's `@Primary` annotation. To autowire the decorated mapper in the application, nothing special needs to be done:
.Using a decorated mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Autowired
private PersonMapper personMapper; // injects the decorator, with the injected original mapper
----
====
[[decorators-with-jsr-330]]
==== Decorators with the JSR 330 component model
JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated implementation of the original mapper is annotated with `@Named("fully-qualified-name-of-generated-implementation")` (please note that when using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class):
.JSR 330 based decorator
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public abstract class PersonMapperDecorator implements PersonMapper {
@Inject
@Named("org.examples.PersonMapperImpl_")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
----
====
Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for decorated mappers, the parameterless `@Named` annotation must be added to select the decorator to be injected:
.Using a decorated mapper with JSR 330
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Inject
@Named
private PersonMapper personMapper; // injects the decorator, with the injected original mapper
----
====
[[customizing-mappings-with-before-and-after]]
=== Mapping customization with before-mapping and after-mapping methods
Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can use *callback methods* that are invoked before the mapping starts or after the mapping finished.
Callback methods can be implemented in the abstract mapper itself, in a type reference in `Mapper#uses`, or in a type used as `@Context` parameter.
.Mapper with @BeforeMapping and @AfterMapping hooks
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public abstract class VehicleMapper {
@BeforeMapping
protected void flushEntity(AbstractVehicle vehicle) {
// I would call my entity manager's flush() method here to make sure my entity
// is populated with the right @Version before I let it map into the DTO
}
@AfterMapping
protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
}
public abstract CarDto toCarDto(Car car);
}
// Generates something like this:
public class VehicleMapperImpl extends VehicleMapper {
public CarDto toCarDto(Car car) {
flushEntity( car );
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
// attributes mapping ...
fillTank( car, carDto );
return carDto;
}
}
----
====
If the `@BeforeMapping` / `@AfterMapping` method has parameters, the method invocation is only generated if the return type of the method (if non-`void`) is assignable to the return type of the mapping method and all parameters can be *assigned* by the source or target parameters of the mapping method:
* A parameter annotated with `@MappingTarget` is populated with the target instance of the mapping.
* A parameter annotated with `@TargetType` is populated with the target type of the mapping.
* Parameters annotated with `@Context` are populated with the context parameters of the mapping method.
* Any other parameter is populated with a source parameter of the mapping.
For non-`void` methods, the return value of the method invocation is returned as the result of the mapping method if it is not `null`.
As with mapping methods, it is possible to specify type parameters for before/after-mapping methods.
.Mapper with @AfterMapping hook that returns a non-null value
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public abstract class VehicleMapper {
@PersistenceContext
private EntityManager entityManager;
@AfterMapping
protected <T> T attachEntity(@MappingTarget T entity) {
return entityManager.merge(entity);
}
public abstract CarDto toCarDto(Car car);
}
// Generates something like this:
public class VehicleMapperImpl extends VehicleMapper {
public CarDto toCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
// attributes mapping ...
CarDto target = attachEntity( carDto );
if ( target != null ) {
return target;
}
return carDto;
}
}
----
====
All before/after-mapping methods that *can* be applied to a mapping method *will* be used. <<selection-based-on-qualifiers>> can be used to further control which methods may be chosen and which not. For that, the qualifier annotation needs to be applied to the before/after-method and referenced in `BeanMapping#qualifiedBy` or `IterableMapping#qualifiedBy`.
The order of the method invocation is determined primarily by their variant:
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:
1. Methods declared on `@Context` parameters, ordered by the parameter order.
2. Methods implemented in the mapper itself.
3. Methods from types referenced in `Mapper#uses()`, in the order of the type declaration in the annotation.
4. Methods declared in one type are used after methods declared in their super-type.
*Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation.
[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`)
====

View File

@ -0,0 +1,424 @@
[[using-spi]]
== Using the MapStruct SPI
To use a custom SPI implementation, it must be located in a separate JAR file together with a file named after the SPI (e.g. `org.mapstruct.ap.spi.AccessorNamingStrategy`) in `META-INF/services/` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
[NOTE]
====
It might also be necessary to add the jar to your IDE's annotation processor factory path. Otherwise you might get an error stating that it cannot be found, while a run using your build tool does succeed.
====
=== Custom Accessor Naming Strategy
SPI name: `org.mapstruct.ap.spi.AccessorNamingStrategy`
MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provider Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below.
.Source object GolfPlayer with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class GolfPlayer {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public GolfPlayer withHandicap(double handicap) {
this.handicap = handicap;
return this;
}
public String name() {
return name;
}
public GolfPlayer withName(String name) {
this.name = name;
return this;
}
}
----
====
.Source object GolfPlayerDto with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class GolfPlayerDto {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public GolfPlayerDto withHandicap(double handicap) {
this.handicap = handicap;
return this;
}
public String name() {
return name;
}
public GolfPlayerDto withName(String name) {
this.name = name;
return this;
}
}
----
====
We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this:
.Source object with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface GolfPlayerMapper {
GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );
GolfPlayerDto toDto(GolfPlayer player);
GolfPlayer toPlayer(GolfPlayerDto player);
}
----
====
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`:
.CustomAccessorNamingStrategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
/**
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the
* form of {@code withProperty(value)}.
*/
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
@Override
public boolean isGetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID;
}
@Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "with" ) && methodName.length() > 4;
}
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName );
}
}
----
====
The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.
[TIP]
Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples).
[[mapping-exclusion-provider]]
=== Mapping Exclusion Provider
SPI name: `org.mapstruct.ap.spi.MappingExclusionProvider`
MapStruct offers the possibility to override the `MappingExclusionProvider` via the Service Provider Interface (SPI).
A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type,
i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type.
[NOTE]
====
The `DefaultMappingExclusionProvider` will exclude all types under the `java` or `javax` packages.
This means that MapStruct will not try to generate an automatic sub-mapping method between some custom type and some type declared in the Java class library.
====
.Source object
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/Source.java[tag=documentation]
----
====
.Target object
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/Target.java[tag=documentation]
----
====
.Mapper definition
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/ErroneousCustomExclusionMapper.java[tag=documentation]
----
====
We want to exclude the `NestedTarget` from the automatic sub-mapping method generation.
.CustomMappingExclusionProvider
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusionProvider.java[tag=documentation]
----
====
[[custom-builder-provider]]
=== Custom Builder Provider
SPI name: org.mapstruct.ap.spi.BuilderProvider
MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom builder strategy.
.Custom Builder Provider which disables Builder support
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
----
====
[[custom-enum-naming-strategy]]
=== Custom Enum Naming Strategy
SPI name: `org.mapstruct.ap.spi.EnumMappingStrategy`
MapStruct offers the possibility to override the `EnumMappingStrategy` via the Service Provider Interface (SPI).
This can be used when you have certain enums that follow some conventions within your organization.
For example all enums which implement an interface named `CustomEnumMarker` are prefixed with `CUSTOM_`
and the default value for them when mapping from `null` is `UNSPECIFIED`
.Normal Enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CheeseType {
BRIE,
ROQUEFORT;
}
----
====
.Custom marker enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CustomCheeseType implements CustomEnumMarker {
UNSPECIFIED,
CUSTOM_BRIE,
CUSTOM_ROQUEFORT;
}
----
====
We want `CheeseType` and `CustomCheeseType` to be mapped without the need to manually define the value mappings:
.Custom enum mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CheeseTypeMapper {
CheeseType map(CustomCheeseType cheese);
CustomCheeseType map(CheeseType cheese);
}
----
====
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.EnumMappingStrategy` as in the following example.
Heres an implemented `org.mapstruct.ap.spi.EnumMappingStrategy`:
.Custom enum naming strategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy {
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomEnum( enumType ) ) {
return "UNSPECIFIED";
}
return super.getDefaultNullEnumConstant( enumType );
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
return super.getEnumConstant( enumType, enumConstant );
}
protected String getCustomEnumConstant(String enumConstant) {
if ( "UNSPECIFIED".equals( enumConstant ) ) {
return MappingConstantsGem.NULL;
}
return enumConstant.replace( "CUSTOM_", "" );
}
protected boolean isCustomEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) {
return true;
}
}
return false;
}
}
----
====
The generated code then for the `CheeseMapper` looks like:
.Generated CheeseTypeMapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CheeseTypeMapperImpl implements CheeseTypeMapper {
@Override
public CheeseType map(CustomCheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case UNRECOGNIZED: cheeseType = null;
break;
case CUSTOM_BRIE: cheeseType = CheeseType.BRIE;
break;
case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
@Override
public CustomCheeseType map(CheeseType cheese) {
if ( cheese == null ) {
return CustomCheeseType.UNSPECIFIED;
}
CustomCheeseType customCheeseType;
switch ( cheese ) {
case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE;
break;
case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return customCheeseType;
}
}
----
====
[[custom-enum-transformation-strategy]]
=== Custom Enum Transformation Strategy
SPI name: `org.mapstruct.ap.spi.EnumTransformationStrategy`
MapStruct offers the possibility to other transformations strategies by implementing `EnumTransformationStrategy` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom transformation strategy.
.Custom Enum Transformation Strategy which lower-cases the value and applies a suffix
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
====
[[additional-supported-options-provider]]
=== Additional Supported Options Provider
SPI name: `org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider`
MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessor
to the individual SPIs, by implementing `AdditionalSupportedOptionsProvider` via the Service Provider Interface (SPI).
.Custom Additional Supported Options Provider that declares `myorg.custom.defaultNullEnumConstant` as an option to pass through
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java[tag=documentation]
----
====
The value of this option is provided by including an `arg` to the `compilerArgs` tag when defining your custom SPI
implementation.
.Example maven configuration with additional options
====
[source, maven, linenums]
[subs="verbatim,attributes"]
----
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.myorg</groupId>
<artifactId>custom-spi-impl</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amyorg.custom.defaultNullEnumConstant=MISSING</arg>
</compilerArgs>
</configuration>
----
====
Your custom SPI implementations can then access this configured value via `MapStructProcessingEnvironment#getOptions()`.
.Accessing your custom options
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/additionalsupportedoptions/UnknownEnumMappingStrategy.java[tag=documentation]
----
====

View File

@ -0,0 +1,192 @@
[[third-party-api-integration]]
== Third-party API integration
[[non-shipped-annotations]]
=== Non-shipped annotations
There are various use-cases you must resolve ambiguity for MapStruct to use a correct piece of code.
However, the primary goal of MapStruct is to focus on bean mapping without polluting the entity code.
For that reason, MapStruct is flexible enough to interact with already defined annotations from third-party libraries.
The requirement to enable this behavior is to match the _name_ of such annotation.
Hence, we say that annotation can be _from any package_.
The annotations _named_ `@ConstructorProperties` and `@Default` are currently examples of this kind of annotation.
[WARNING]
====
If such named third-party annotation exists, it does not guarantee its `@Target` matches with the intended placement.
Be aware of placing a third-party annotation just for sake of mapping is not recommended as long as it might lead to unwanted side effects caused by that library.
====
A very common case is that no third-party dependency imported to your project provides such annotation or is inappropriate for use as already described.
In such cases create your own annotation, for example:
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
package foo.support.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.CLASS)
public @interface Default {
}
----
====
[[lombok]]
=== Lombok
MapStruct works together with https://projectlombok.org/[Project Lombok] as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14.
MapStruct takes advantage of generated getters, setters, and constructors and uses them to generate the mapper implementations.
Be reminded that the generated code by Lombok might not always be compatible with the expectations from the individual mappings.
In such a case, either Mapstruct mapping must be changed or Lombok must be configured accordingly using https://projectlombok.org/features/configuration[`lombok.config`] for mutual synergy.
[WARNING]
====
Lombok 1.18.16 introduces a breaking change (https://projectlombok.org/changelog[changelog]).
The additional annotation processor `lombok-mapstruct-binding` (https://mvnrepository.com/artifact/org.projectlombok/lombok-mapstruct-binding[Maven]) must be added otherwise MapStruct stops working with Lombok.
This resolves the compilation issues of Lombok and MapStruct modules.
[source, xml]
----
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
----
====
==== Set up
The set up using Maven or Gradle does not differ from what is described in <<setup>>. Additionally, you need to provide Lombok dependencies.
.Maven configuration
====
[source, xml, linenums]
[subs="verbatim,attributes"]
----
<properties>
<org.mapstruct.version>{mapstructVersion}</org.mapstruct.version>
<org.projectlombok.version>1.18.16</org.projectlombok.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- lombok dependency should not end up on classpath -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<!-- additional annotation processor required as of Lombok 1.18.16 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
----
====
.Gradle configuration (3.4 and later)
====
[source, groovy, linenums]
[subs="verbatim,attributes"]
----
dependencies {
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
compileOnly "org.projectlombok:lombok:1.18.16"
annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
annotationProcessor "org.projectlombok:lombok:1.18.16"
}
----
====
The usage combines what you already know from <<defining-mapper>> and Lombok.
.Usage of MapStruct with Lombok
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Data
public class Source {
private String test;
}
public class Target {
private Long testing;
public Long getTesting() {
return testing;
}
public void setTesting( Long testing ) {
this.testing = testing;
}
}
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
@Mapping( source = "test", target = "testing" )
Target toTarget( Source s );
}
----
====
A working example can be found on the GitHub project https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-lombok[mapstruct-lombok].

View File

@ -0,0 +1,325 @@
[[setup]]
== Set up
MapStruct is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE.
It comprises the following artifacts:
* _org.mapstruct:mapstruct_: contains the required annotations such as `@Mapping`
* _org.mapstruct:mapstruct-processor_: contains the annotation processor which generates mapper implementations
=== Apache Maven
For Maven based projects add the following to your POM file in order to use MapStruct:
.Maven configuration
====
[source, xml, linenums]
[subs="verbatim,attributes"]
----
...
<properties>
<org.mapstruct.version>{mapstructVersion}</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...
----
====
[TIP]
====
If you are working with the Eclipse IDE, make sure to have a current version of the http://www.eclipse.org/m2e/[M2E plug-in].
When importing a Maven project configured as shown above, it will set up the MapStruct annotation processor so it runs right in the IDE, whenever you save a mapper type.
Neat, isn't it?
To double check that everything is working as expected, go to your project's properties and select "Java Compiler" -> "Annotation Processing" -> "Factory Path".
The MapStruct processor JAR should be listed and enabled there.
Any processor options configured via the compiler plug-in (see below) should be listed under "Java Compiler" -> "Annotation Processing".
If the processor is not kicking in, check that the configuration of annotation processors through M2E is enabled.
To do so, go to "Preferences" -> "Maven" -> "Annotation Processing" and select "Automatically configure JDT APT".
Alternatively, specify the following in the `properties` section of your POM file: `<m2e.apt.activation>jdt_apt</m2e.apt.activation>`.
Also make sure that your project is using Java 1.8 or later (project properties -> "Java Compiler" -> "Compile Compliance Level").
It will not work with older versions.
====
=== Gradle
Add the following to your Gradle build file in order to enable MapStruct:
.Gradle configuration
====
[source, groovy, linenums]
[subs="verbatim,attributes"]
----
...
plugins {
...
id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}
dependencies {
...
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
// If you are using mapstruct in test code
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
}
...
----
====
You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-gradle[mapstruct-examples] project on GitHub.
=== Apache Ant
Add the `javac` task configured as follows to your _build.xml_ file in order to enable MapStruct in your Ant-based project. Adjust the paths as required for your project layout.
.Ant configuration
====
[source, xml, linenums]
[subs="verbatim,attributes"]
----
...
<javac
srcdir="src/main/java"
destdir="target/classes"
classpath="path/to/mapstruct-{mapstructVersion}.jar">
<compilerarg line="-processorpath path/to/mapstruct-processor-{mapstructVersion}.jar"/>
<compilerarg line="-s target/generated-sources"/>
</javac>
...
----
====
You can find a complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-on-ant[mapstruct-examples] project on GitHub.
[[configuration-options]]
=== Configuration options
The MapStruct code generator can be configured using _annotation processor options_.
When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using `compilerArgs` within the configuration of the Maven processor plug-in like this:
.Maven configuration
====
[source, xml, linenums]
[subs="verbatim,attributes"]
----
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings -->
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>
-Amapstruct.suppressGeneratorTimestamp=true
</arg>
<arg>
-Amapstruct.suppressGeneratorVersionInfoComment=true
</arg>
<arg>
-Amapstruct.verbose=true
</arg>
</compilerArgs>
</configuration>
</plugin>
...
----
====
.Gradle configuration
====
[source, groovy, linenums]
[subs="verbatim,attributes"]
----
...
compileJava {
options.compilerArgs += [
'-Amapstruct.suppressGeneratorTimestamp=true',
'-Amapstruct.suppressGeneratorVersionInfoComment=true',
'-Amapstruct.verbose=true'
]
}
...
----
====
The following options exist:
.MapStruct processor options
[cols="1,2a,1"]
|===
|Option|Purpose|Default
|`mapstruct.
suppressGeneratorTimestamp`
|If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed.
|`false`
|`mapstruct.verbose`
|If set to `true`, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also `showWarnings` needs to be added due to a problem in the maven-compiler-plugin configuration.
|`false`
|`mapstruct.
suppressGeneratorVersionInfoComment`
|If set to `true`, the creation of the `comment` attribute in the `@Generated` annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing.
|`false`
|`mapstruct.defaultComponentModel`
|The name of the component model (see <<retrieving-mapper>>) based on which mappers should be generated.
Supported values are:
* `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)`
* `cdi`: the generated mapper is an application-scoped (from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority) CDI bean and can be retrieved via `@Inject`
* `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired`
* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring
* `jakarta`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from jakarta.inject), e.g. using Spring
* `jakarta-cdi`: the generated mapper is an application-scoped (from jakarta.enterprise.context) CDI bean and can be retrieved via `@Inject`
If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence.
|`default`
|`mapstruct.defaultInjectionStrategy`
| The type of the injection in mapper via parameter `uses`. This is only used on annotated based component models
such as CDI, Spring and JSR 330.
Supported values are:
* `field`: dependencies will be injected in fields
* `constructor`: will be generated constructor. Dependencies will be injected via constructor.
When CDI `componentModel` a default constructor will also be generated.
If a injection strategy is given for a specific mapper via `@Mapper#injectionStrategy()`, the value from the annotation takes precedence over the option.
|`field`
|`mapstruct.unmappedTargetPolicy`
|The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value.
Supported values are:
* `ERROR`: any unmapped target property will cause the mapping code generation to fail
* `WARN`: any unmapped target property will cause a warning at build time
* `IGNORE`: unmapped target properties are ignored
If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence.
If a policy is given for a specific bean mapping via `@BeanMapping#unmappedTargetPolicy()`, it takes precedence over both `@Mapper#unmappedTargetPolicy()` and the option.
|`WARN`
|`mapstruct.unmappedSourcePolicy`
|The default reporting policy to be applied in case an attribute of the source object of a mapping method is not populated with a target value.
Supported values are:
* `ERROR`: any unmapped source property will cause the mapping code generation to fail
* `WARN`: any unmapped source property will cause a warning at build time
* `IGNORE`: unmapped source properties are ignored
If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence.
If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option.
|`IGNORE`
|`mapstruct.
disableBuilders`
|If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers.
|`false`
|`mapstruct.nullValueIterableMappingStrategy`
|The strategy to be applied when `null` is passed as a source value to an iterable mapping.
Supported values are:
* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
* `RETURN_DEFAULT`: if `null` is passed then a default value (empty collection) will be returned
If a strategy is given for a specific mapper via `@Mapper#nullValueIterableMappingStrategy()`, the value from the annotation takes precedence.
If a strategy is given for a specific iterable mapping via `@IterableMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueIterableMappingStrategy()` and the option.
|`RETURN_NULL`
|`mapstruct.nullValueMapMappingStrategy`
|The strategy to be applied when `null` is passed as a source value to a map mapping.
Supported values are:
* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
* `RETURN_DEFAULT`: if `null` is passed then a default value (empty map) will be returned
If a strategy is given for a specific mapper via `@Mapper#nullValueMapMappingStrategy()`, the value from the annotation takes precedence.
If a strategy is given for a specific map mapping via `@MapMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueMapMappingStrategy()` and the option.
|`RETURN_NULL`
|===
=== Using MapStruct with the Java Module System
MapStruct can be used with Java 9 and higher versions.
To allow usage of the `@Generated` annotation `java.annotation.processing.Generated` (part of the `java.compiler` module) can be enabled.
=== IDE Integration
There are optional MapStruct plugins for IntelliJ and Eclipse that allow you to have additional completion support (and more) in the annotations.
==== IntelliJ
The https://plugins.jetbrains.com/plugin/10036-mapstruct-support[MapStruct IntelliJ] plugin offers assistance in projects that use MapStruct.
Some features include:
* Code completion in `target`, `source`, `expression`
* Go To Declaration for properties in `target` and `source`
* Find Usages of properties in `target` and `source`
* Refactoring support
* Errors and Quick Fixes
==== Eclipse
The https://marketplace.eclipse.org/content/mapstruct-eclipse-plugin[MapStruct Eclipse] Plugin offers assistance in projects that use MapStruct.
Some features include:
* Code completion in `target` and `source`
* Quick Fixes

View File

@ -0,0 +1,866 @@
[[defining-mapper]]
== Defining a mapper
In this section you'll learn how to define a bean mapper with MapStruct and which options you have to do so.
[[basic-mappings]]
=== Basic mappings
To create a mapper simply define a Java interface with the required mapping method(s) and annotate it with the `org.mapstruct.Mapper` annotation:
.Java interface to define a mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(target = "manufacturer", source = "make")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDto carToCarDto(Car car);
@Mapping(target = "fullName", source = "name")
PersonDto personToPersonDto(Person person);
}
----
====
The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time.
In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`):
* When a property has the same name as its target entity counterpart, it will be mapped implicitly.
* When a property has a different name in the target entity, its name can be specified via the `@Mapping` annotation.
[TIP]
====
The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans specification] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`.
====
[TIP]
====
By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings (including nested ones) have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties.
This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`.
====
[TIP]
====
Fluent setters are also supported.
Fluent setters are setters that return the same type as the type being modified.
E.g.
```
public Builder seatCount(int seatCount) {
this.seatCount = seatCount;
return this;
}
```
====
To get a better understanding of what MapStruct does have a look at the following implementation of the `carToCarDto()` method as generated by MapStruct:
.Code generated by MapStruct
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
if ( car.getFeatures() != null ) {
carDto.setFeatures( new ArrayList<String>( car.getFeatures() ) );
}
carDto.setManufacturer( car.getMake() );
carDto.setSeatCount( car.getNumberOfSeats() );
carDto.setDriver( personToPersonDto( car.getDriver() ) );
carDto.setPrice( String.valueOf( car.getPrice() ) );
if ( car.getCategory() != null ) {
carDto.setCategory( car.getCategory().toString() );
}
carDto.setEngine( engineToEngineDto( car.getEngine() ) );
return carDto;
}
@Override
public PersonDto personToPersonDto(Person person) {
//...
}
private EngineDto engineToEngineDto(Engine engine) {
if ( engine == null ) {
return null;
}
EngineDto engineDto = new EngineDto();
engineDto.setHorsePower(engine.getHorsePower());
engineDto.setFuel(engine.getFuel());
return engineDto;
}
}
----
====
The general philosophy of MapStruct is to generate code which looks as much as possible as if you had written it yourself from hand. In particular this means that the values are copied from source to target by plain getter/setter invocations instead of reflection or similar.
As the example shows the generated code takes into account any name mappings specified via `@Mapping`.
If the type of a mapped attribute is different in source and target entity,
MapStruct will either apply an automatic conversion (as e.g. for the _price_ property, see also <<implicit-type-conversions>>)
or optionally invoke / create another mapping method (as e.g. for the _driver_ / _engine_ property, see also <<mapping-object-references>>).
MapStruct will only create a new mapping method if and only if the source and target property are properties of a Bean and they themselves are Beans or simple properties.
i.e. they are not `Collection` or `Map` type properties.
Collection-typed attributes with the same element type will be copied by creating a new instance of the target collection type containing the elements from the source property. For collection-typed attributes with different element types each element will be mapped individually and added to the target collection (see <<mapping-collections>>).
MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types.
[[mapping-composition]]
=== Mapping Composition
MapStruct supports the use of meta annotations. The `@Mapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. This allows `@Mapping` to be used on other (user defined) annotations for re-use purposes. For example:
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
@Mapping(target = "name", source = "groupName")
public @interface ToEntity { }
----
====
Can be used to characterise an `Entity` without the need to have a common base type. For instance, `ShelveEntity` and `BoxEntity` do not share a common base type in the `StorageMapper` below.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface StorageMapper {
StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );
@ToEntity
@Mapping( target = "weightLimit", source = "maxWeight")
ShelveEntity map(ShelveDto source);
@ToEntity
@Mapping( target = "label", source = "designation")
BoxEntity map(BoxDto source);
}
----
====
Still, they do have some properties in common. The `@ToEntity` assumes both target beans `ShelveEntity` and `BoxEntity` have properties: `"id"`, `"creationDate"` and `"name"`. It furthermore assumes that the source beans `ShelveDto` and `BoxDto` always have a property `"groupName"`. This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck.
Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly.
Therefore, the user should use this feature with care, especially when uncertain when a property is always present.
A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use `@InheritConfiguration` to achieve the same result (see <<mapping-configuration-inheritance>>).
[[adding-custom-methods]]
=== Adding custom methods to mappers
In some cases it can be required to manually implement a specific mapping from one type to another which can't be generated by MapStruct. One way to handle this is to implement the custom method on another class which then is used by mappers generated by MapStruct (see <<invoking-other-mappers>>).
Alternatively, when using Java 8 or later, you can implement custom methods directly in a mapper interface as default methods. The generated code will invoke the default methods if the argument and return types match.
As an example let's assume the mapping from `Person` to `PersonDto` requires some special logic which can't be generated by MapStruct. You could then define the mapper from the previous example like this:
.Mapper which defines a custom mapping with a default method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
----
====
The class generated by MapStruct implements the method `carToCarDto()`. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
A mapper could also be defined in the form of an abstract class instead of an interface and implement the custom methods directly in the mapper class. In this case MapStruct will generate an extension of the abstract class with implementations of all abstract methods. An advantage of this approach over declaring default methods is that additional fields could be declared in the mapper class.
The previous example where the mapping from `Person` to `PersonDto` requires some special logic could then be defined like this:
.Mapper defined by an abstract class
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public abstract class CarMapper {
@Mapping(...)
...
public abstract CarDto carToCarDto(Car car);
public PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
----
====
MapStruct will generate a sub-class of `CarMapper` with an implementation of the `carToCarDto()` method as it is declared abstract. The generated code in `carToCarDto()` will invoke the manually implemented `personToPersonDto()` method when mapping the `driver` attribute.
[[mappings-with-several-source-parameters]]
=== Mapping methods with several source parameters
MapStruct also supports mapping methods with several source parameters. This is useful e.g. in order to combine several entities into one data transfer object. The following shows an example:
.Mapping method with several source parameters
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface AddressMapper {
@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "address.houseNo")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
----
====
The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name.
In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically.
[WARNING]
====
Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation.
====
[TIP]
====
Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated.
====
MapStruct also offers the possibility to directly refer to a source parameter.
.Mapping method directly referring to a source parameter
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface AddressMapper {
@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "hn")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}
----
====
In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`.
[[mapping-nested-bean-properties-to-current-target]]
=== Mapping nested bean properties to current target
If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
This will tell MapStruct to map every property from source bean to target object. The following shows an example:
.use of "target this" annotation "."
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CustomerMapper {
@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
----
====
The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
The same goes for `Customer.account`.
When there are conflicts, these can be resolved by explicitly defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`).
[[updating-bean-instances]]
=== Updating existing bean instances
In some cases you need mappings which don't create a new instance of the target type but instead update an existing instance of that type. This sort of mapping can be realized by adding a parameter for the target object and marking this parameter with `@MappingTarget`. The following shows an example:
.Update method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}
----
====
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.
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
MapStruct also supports mappings of `public` fields that have no getters/setters. MapStruct will
use the fields as read/write accessor if it cannot find suitable getter/setter methods for the property.
A field is considered as a read accessor if it is `public` or `public final`. If a field is `static` it is not
considered as a read accessor.
A field is considered as a write accessor only if it is `public`. If a field is `final` and/or `static` it is not
considered as a write accessor.
Small example:
.Example classes for mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Customer {
private Long id;
private String name;
//getters and setter omitted for brevity
}
public class CustomerDto {
public Long id;
public String customerName;
}
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
@Mapping(target = "name", source = "customerName")
Customer toCustomer(CustomerDto customerDto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}
----
====
For the configuration from above, the generated mapper looks like:
.Generated mapper for example classes
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(CustomerDto customerDto) {
// ...
customer.setId( customerDto.id );
customer.setName( customerDto.customerName );
// ...
}
@Override
public CustomerDto fromCustomer(Customer customer) {
// ...
customerDto.id = customer.getId();
customerDto.customerName = customer.getName();
// ...
}
}
----
====
You can find the complete example in the
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping]
project on GitHub.
[[mapping-with-builders]]
=== Using builders
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, then that builder will be used for the mappings.
The default implementation of the `BuilderProvider` assumes the following:
* The type has either
** A parameterless public static builder creation method that returns a builder.
e.g. `Person` has a public static method that returns `PersonBuilder`.
** A public static inner class with the name having the suffix "Builder", and a public no-args constructor
e.g. `Person` has an inner class `PersonBuilder` with a public no-args constructor.
* The builder type has a parameterless public method (build method) that returns the type being built.
In our example `PersonBuilder` has a method returning `Person`.
* In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists
then this would be used, otherwise a compilation error would be created.
* A specific build method can be defined by using `@Builder` within: `@BeanMapping`, `@Mapper` or `@MapperConfig`
* In case there are multiple builder creation methods that satisfy the above conditions then a `MoreThanOneBuilderCreationMethodException`
will be thrown from the `DefaultBuilderProvider` SPI.
In case of a `MoreThanOneBuilderCreationMethodException` MapStruct will write a warning in the compilation and not use any builder.
If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type).
To finish the mapping MapStruct generates code that will invoke the build method of the builder.
[NOTE]
======
Builder detection can be switched off by means of `@Builder#disableBuilder`. MapStruct will fall back on regular getters / setters in case builders are disabled.
======
[NOTE]
======
The <<object-factories>> are also considered for the builder type.
E.g. If an object factory exists for our `PersonBuilder` then this factory would be used instead of the builder creation method.
======
[NOTE]
======
Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See <<customizing-mappings-with-before-and-after>> for more information.
======
.Person with Builder example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Person {
private final String name;
protected Person(Person.Builder builder) {
this.name = builder.name;
}
public static Person.Builder builder() {
return new Person.Builder();
}
public static class Builder {
private String name;
public Builder name(String name) {
this.name = name;
return this;
}
public Person create() {
return new Person( this );
}
}
}
----
====
.Person Mapper definition
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public interface PersonMapper {
Person map(PersonDto dto);
}
----
====
.Generated mapper with builder
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {
public Person map(PersonDto dto) {
if (dto == null) {
return null;
}
Person.Builder builder = Person.builder();
builder.name( dto.getName() );
return builder.create();
}
}
----
====
Supported builder frameworks:
* https://projectlombok.org/[Lombok] - It is required to have the Lombok classes in a separate module.
See for more information at https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] and to set up Lombok with MapStruct, refer to <<lombok>>.
* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue]
* https://immutables.github.io/[Immutables] - When Immutables are present on the annotation processor path then the `ImmutablesAccessorNamingStrategy` and `ImmutablesBuilderProvider` would be used by default
* https://github.com/google/FreeBuilder[FreeBuilder] - When FreeBuilder is present on the annotation processor path then the `FreeBuilderAccessorNamingStrategy` would be used by default.
When using FreeBuilder then the JavaBean convention should be followed, otherwise MapStruct won't recognize the fluent getters.
* It also works for custom builders (handwritten ones) if the implementation supports the defined rules for the default `BuilderProvider`.
Otherwise, you would need to write a custom `BuilderProvider`
[TIP]
====
In case you want to disable using builders then you can pass the MapStruct processor option `mapstruct.disableBuilders` to the compiler. e.g. `-Amapstruct.disableBuilders=true`.
====
[[mapping-with-constructors]]
=== Using Constructors
MapStruct supports using constructors for mapping target types.
When doing a mapping MapStruct checks if there is a builder for the type being mapped.
If there is no builder, then MapStruct looks for a single accessible constructor.
When there are multiple constructors then the following is done to pick the one which should be used:
* If a constructor is annotated with an annotation _named_ `@Default` (from any package, see <<non-shipped-annotations>>) it will be used.
* If a single public constructor exists then it will be used to construct the object, and the other non public constructors will be ignored.
* If a parameterless constructor exists then it will be used to construct the object, and the other constructors will be ignored.
* If there are multiple eligible constructors then there will be a compilation error due to ambiguous constructors. In order to break the ambiguity an annotation _named_ `@Default` (from any package, see <<non-shipped-annotations>>) can used.
.Deciding which constructor to use
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Vehicle {
protected Vehicle() { }
// MapStruct will use this constructor, because it is a single public constructor
public Vehicle(String color) { }
}
public class Car {
// MapStruct will use this constructor, because it is a parameterless empty constructor
public Car() { }
public Car(String make, String color) { }
}
public class Truck {
public Truck() { }
// MapStruct will use this constructor, because it is annotated with @Default
@Default
public Truck(String make, String color) { }
}
public class Van {
// There will be a compilation error when using this class because MapStruct cannot pick a constructor
public Van(String make) { }
public Van(String make, String color) { }
}
----
====
When using a constructor then the names of the parameters of the constructor will be used and matched to the target properties.
When the constructor has an annotation _named_ `@ConstructorProperties` (from any package, see <<non-shipped-annotations>>) then this annotation will be used to get the names of the parameters.
[NOTE]
====
When an object factory method or a method annotated with `@ObjectFactory` exists, it will take precedence over any constructor defined in the target.
The target object constructor will not be used in that case.
====
.Person with constructor parameters
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Person {
private final String name;
private final String surname;
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
}
----
====
.Person With Constructor Mapper definition
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public interface PersonMapper {
Person map(PersonDto dto);
}
----
====
.Generated mapper with constructor
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class PersonMapperImpl implements PersonMapper {
public Person map(PersonDto dto) {
if (dto == null) {
return null;
}
String name;
String surname;
name = dto.getName();
surname = dto.getSurname();
Person person = new Person( name, surname );
return person;
}
}
----
====
[[mapping-map-to-bean]]
=== Mapping Map to Bean
There are situations when a mapping from a `Map<String, ???>` into a specific bean is needed.
MapStruct offers a transparent way of doing such a mapping by using the target bean properties (or defined through `Mapping#source`) to extract the values from the map.
Such a mapping looks like:
.Example classes for mapping map to bean
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Customer {
private Long id;
private String name;
//getters and setter omitted for brevity
}
@Mapper
public interface CustomerMapper {
@Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}
----
====
.Generated mapper for mapping map to bean
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(Map<String, String> map) {
// ...
if ( map.containsKey( "id" ) ) {
customer.setId( Integer.parseInt( map.get( "id" ) ) );
}
if ( map.containsKey( "customerName" ) ) {
customer.setName( map.get( "customerName" ) );
}
// ...
}
}
----
====
[NOTE]
====
All existing rules about mapping between different types and using other mappers defined with `Mapper#uses` or custom methods in the mappers are applied.
i.e. You can map from `Map<String, Integer>` where for each property a conversion from `Integer` into the respective property will be needed.
====
[WARNING]
====
When a raw map or a map that does not have a String as a key is used, then a warning will be generated.
The warning is not generated if the map itself is mapped into some other target property directly as is.
====
[[adding-annotations]]
=== Adding annotations
Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers.
Using the `@AnnotateWith` annotation you can generate an annotation at the specified location.
For example Apache Camel has a `@Converter` annotation which you can apply to generated mappers using the `@AnnotateWith` annotation.
.AnnotateWith source example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@AnnotateWith(
value = Converter.class,
elements = @AnnotateWith.Element( name = "generateBulkLoader", booleans = true )
)
public interface MyConverter {
@AnnotateWith( Converter.class )
DomainObject map( DtoObject dto );
}
----
====
.AnnotateWith generated mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Converter( generateBulkLoader = true )
public class MyConverterImpl implements MyConverter {
@Converter
public DomainObject map( DtoObject dto ) {
// default mapping behaviour
}
}
----
====
[[javadoc]]
=== Adding Javadoc comments
MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the
`org.mapstruct.Javadoc` annotation.
This functionality could be relevant especially in situations where certain Javadoc standards need to be met or
to deal with Javadoc validation constraints.
The `@Javadoc` annotation defines attributes for the different Javadoc elements.
Consider the following example:
.Javadoc annotation example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
value = "This is the description",
authors = { "author1", "author2" },
deprecated = "Use {@link OtherMapper} instead",
since = "0.1"
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====
.Javadoc annotated generated mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
/**
* This is the description
*
* @author author1
* @author author2
*
* @deprecated Use {@link OtherMapper} instead
* @since 0.1
*/
public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper {
//...
}
----
====
The entire Javadoc comment block can be provided directly as well.
.Javadoc annotation example with the entire Javadoc comment block provided directly
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
"This is the description\n"
+ "\n"
+ "@author author1\n"
+ "@author author2\n"
+ "\n"
+ "@deprecated Use {@link OtherMapper} instead\n"
+ "@since 0.1\n"
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====
Or using Text blocks:
.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
"""
This is the description
@author author1
@author author2
@deprecated Use {@link OtherMapper} instead
@since 0.1
"""
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====

View File

@ -0,0 +1,133 @@
[[retrieving-mapper]]
== Retrieving a mapper
[[mappers-factory]]
=== The Mappers factory (no dependency injection)
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
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
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 (interface)
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
}
----
====
.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
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );
----
====
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 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 and constants are defined in a class `MappingConstants.ComponentModel`. 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:
.A mapper using the CDI component model
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(componentModel = MappingConstants.ComponentModel.CDI)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
----
====
The generated mapper implementation will be marked with the `@ApplicationScoped` annotation and thus can be injected into fields, constructor arguments etc. using the `@Inject` annotation:
.Obtaining a mapper via dependency injection
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Inject
private CarMapper mapper;
----
====
A mapper which uses other mapper classes (see <<invoking-other-mappers>>) will obtain these mappers using the configured component model. So if `CarMapper` from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well.
[[injection-strategy]]
=== Injection strategy
When using <<using-dependency-injection,dependency injection>>, you can choose between constructor, field, or setter injection.
This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation.
.Using constructor injection
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
----
====
The generated mapper will inject classes defined in the **uses** attribute if MapStruct has detected that it needs to use an instance of it for a mapping.
When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't.
When `InjectionStrategy#FIELD` is used, the annotation is on the field itself.
When `InjectionStrategy#SETTER` is used the annotation is on a generated setter method.
For now, the default injection strategy is field injection, but it can be configured with <<configuration-options>>.
It is recommended to use constructor injection to simplify testing.
When you define mappers in Spring with circular dependencies compilation may fail.
In that case utilize the `InjectionStrategy#SETTER` strategy.
[TIP]
====
For abstract classes or decorators setter injection should be used.
====

View File

@ -0,0 +1,823 @@
[[datatype-conversions]]
== Data type conversions
Not always a mapped attribute has the same type in the source and target objects. For instance an attribute may be of type `int` in the source bean but of type `Long` in the target bean.
Another example are references to other objects which should be mapped to the corresponding types in the target model. E.g. the class `Car` might have a property `driver` of the type `Person` which needs to be converted into a `PersonDto` object when mapping a `Car` object.
In this section you'll learn how MapStruct deals with such data type conversions.
[[implicit-type-conversions]]
=== Implicit type conversions
MapStruct takes care of type conversions automatically in many cases. If for instance an attribute is of type `int` in the source bean but of type `String` in the target bean, the generated code will transparently perform a conversion by calling `String#valueOf(int)` and `Integer#parseInt(String)`, respectively.
Currently the following conversions are applied automatically:
* Between all Java primitive data types and their corresponding wrapper types, e.g. between `int` and `Integer`, `boolean` and `Boolean` etc. The generated code is `null` aware, i.e. when converting a wrapper type into the corresponding primitive type a `null` check will be performed.
* Between all Java primitive number types and the wrapper types, e.g. between `int` and `long` or `byte` and `Integer`.
[WARNING]
====
Converting from larger data types to smaller ones (e.g. from `long` to `int`) can cause a value or precision loss. The `Mapper` and `MapperConfig` annotations have a method `typeConversionPolicy` to control warnings / errors. Due to backward compatibility reasons the default value is `ReportingPolicy.IGNORE`.
====
* Between all Java primitive types (including their wrappers) and `String`, e.g. between `int` and `String` or `Boolean` and `String`. A format string as understood by `java.text.DecimalFormat` can be specified.
.Conversion from int to String
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(source = "price", numberFormat = "$#.00")
CarDto carToCarDto(Car car);
@IterableMapping(numberFormat = "$#.00")
List<String> prices(List<Integer> prices);
}
----
====
* Between `enum` types and `String`.
* Between `enum` types and `Integer`, according to `enum.ordinal()`.
** When converting from an `Integer`, the value needs to be less than the number of values of the enum, otherwise an `ArrayOutOfBoundsException` is thrown.
* Between big number types (`java.math.BigInteger`, `java.math.BigDecimal`) and Java primitive types (including their wrappers) as well as String. A format string as understood by `java.text.DecimalFormat` can be specified.
.Conversion from BigDecimal to String
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(source = "power", numberFormat = "#.##E0")
CarDto carToCarDto(Car car);
}
----
====
* Between `JAXBElement<T>` and `T`, `List<JAXBElement<T>>` and `List<T>`
* Between `java.util.Calendar`/`java.util.Date` and JAXB's `XMLGregorianCalendar`
* Between `java.util.Date`/`XMLGregorianCalendar` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option as this:
.Conversion from Date to String
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
@Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
CarDto carToCarDto(Car car);
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
}
----
====
* Between Jodas `org.joda.time.DateTime`, `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate`, `org.joda.time.LocalTime` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above).
* Between Jodas `org.joda.time.DateTime` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Calendar`.
* Between Jodas `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Date`.
* Between `java.time.LocalDate`, `java.time.LocalDateTime` and `javax.xml.datatype.XMLGregorianCalendar`.
* Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above).
* Between `java.time.Instant`, `java.time.Duration`, `java.time.Period` from Java 8 Date-Time package and `String` using the `parse` method in each class to map from `String` and using `toString` to map into `String`.
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Date` where, when mapping a `ZonedDateTime` from a given `Date`, the system default timezone is used.
* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone.
* Between `java.time.LocalDate` from Java 8 Date-Time package and `java.util.Date` / `java.sql.Date` where timezone UTC is used as the timezone.
* Between `java.time.Instant` from Java 8 Date-Time package and `java.util.Date`.
* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.time.LocalDate` from the same package.
* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`.
* Between `java.sql.Date` and `java.util.Date`
* Between `java.sql.Time` and `java.util.Date`
* Between `java.sql.Timestamp` and `java.util.Date`
* When converting from a `String`, omitting `Mapping#dateFormat`, it leads to usage of the default pattern and date format symbols for the default locale. An exception to this rule is `XmlGregorianCalendar` which results in parsing the `String` according to http://www.w3.org/TR/xmlschema-2/#dateTime[XML Schema 1.0 Part 2, Section 3.2.7-14.1, Lexical Representation].
* Between `java.util.Currency` and `String`.
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/ISO_4217[ISO-4217] alphabetic code otherwise an `IllegalArgumentException` is thrown.
* Between `java.util.UUID` and `String`.
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/Universally_unique_identifier[UUID] otherwise an `IllegalArgumentException` is thrown.
* Between `String` and `StringBuilder`
* Between `java.net.URL` and `String`.
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown.
* Between `java.util.Locale` and `String`.
** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information.
[[mapping-object-references]]
=== Mapping object references
Typically an object has not only primitive attributes but also references other objects. E.g. the `Car` class could contain a reference to a `Person` object (representing the car's driver) which should be mapped to a `PersonDto` object referenced by the `CarDto` class.
In this case just define a mapping method for the referenced object type as well:
.Mapper with one mapping method using another
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
CarDto carToCarDto(Car car);
PersonDto personToPersonDto(Person person);
}
----
====
The generated code for the `carToCarDto()` method will invoke the `personToPersonDto()` method for mapping the `driver` attribute, while the generated implementation for `personToPersonDto()` performs the mapping of person objects.
That way it is possible to map arbitrary deep object graphs. When mapping from entities into data transfer objects it is often useful to cut references to other entities at a certain point. To do so, implement a custom mapping method (see the next section) which e.g. maps a referenced entity to its id in the target object.
When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object:
. If source and target attribute have the same type, the value will be simply copied *direct* from source to target. If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target attribute.
. If source and target attribute type differ, check whether there is another *mapping method* which has the type of the source attribute as parameter type and the type of the target attribute as return type. If such a method exists it will be invoked in the generated mapping implementation.
. If no such method exists MapStruct will look whether a *built-in conversion* for the source and target type of the attribute exists. If this is the case, the generated mapping code will apply this conversion.
. If no such method exists MapStruct will apply *complex* conversions:
.. mapping method, the result mapped by mapping method, like this: `target = method1( method2( source ) )`
.. built-in conversion, the result mapped by mapping method, like this: `target = method( conversion( source ) )`
.. mapping method, the result mapped by build-in conversion, like this: `target = conversion( method( source ) )`
. If no such method was found MapStruct will try to generate an automatic sub-mapping method that will do the mapping between the source and target attributes.
. If MapStruct could not create a name based mapping method an error will be raised at build time, indicating the non-mappable attribute and its path.
A mapping control (`MappingControl`) can be defined on all levels (`@MapperConfig`, `@Mapper`, `@BeanMapping`, `@Mapping`), the latter taking precedence over the former. For example: `@Mapper( mappingControl = NoComplexMapping.class )` takes precedence over `@MapperConfig( mappingControl = DeepClone.class )`. `@IterableMapping` and `@MapMapping` work similar as `@Mapping`. MappingControl is experimental from MapStruct 1.4.
`MappingControl` has an enum that corresponds to the first 4 options above: `MappingControl.Use#DIRECT`, `MappingControl.Use#MAPPING_METHOD`, `MappingControl.Use#BUILT_IN_CONVERSION` and `MappingControl.Use#COMPLEX_MAPPING` the presence of which allows the user to switch *on* a option. The absence of an enum switches *off* a mapping option. Default they are all present enabling all mapping options.
[NOTE]
====
In order to stop MapStruct from generating automatic sub-mapping methods as in 5. above, one can use `@Mapper( disableSubMappingMethodsGeneration = true )`.
====
[TIP]
====
The user has full control over the mapping by means of meta annotations. Some handy ones have been defined such as `@DeepClone` which only allows direct mappings. The result: if source and target type are the same, MapStruct will make a deep clone of the source. Sub-mappings-methods have to be allowed (default option).
====
[NOTE]
====
During the generation of automatic sub-mapping methods <<shared-configurations>> will not be taken into consideration, yet.
Follow issue https://github.com/mapstruct/mapstruct/issues/1086[#1086] for more information.
====
[NOTE]
====
Constructor properties of the target object are also considered as target properties.
You can read more about that in <<mapping-with-constructors>>
====
[[controlling-nested-bean-mappings]]
=== Controlling nested bean mappings
As explained above, MapStruct will generate a method based on the name of the source and target property. Unfortunately, in many occasions these names do not match.
The . notation in an `@Mapping` source or target type can be used to control how properties should be mapped when names do not match.
There is an elaborate https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-nested-bean-mappings[example] in our examples repository to explain how this problem can be overcome.
In the simplest scenario theres a property on a nested level that needs to be corrected.
Take for instance a property `fish` which has an identical name in `FishTankDto` and `FishTank`.
For this property MapStruct automatically generates a mapping: `FishDto fishToFishDto(Fish fish)`.
MapStruct cannot possibly be aware of the deviating properties `kind` and `type`.
Therefore this can be addressed in a mapping rule: `@Mapping(target="fish.kind", source="fish.type")`.
This tells MapStruct to deviate from looking for a name `kind` at this level and map it to `type`.
.Mapper controlling nested beans mappings I
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapper {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", ignore = true)
@Mapping(target = "ornament", source = "interior.ornament")
@Mapping(target = "material.materialType", source = "material")
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
FishTankDto map( FishTank source );
}
----
====
The same constructs can be used to ignore certain properties at a nesting level, as is demonstrated in the second `@Mapping` rule.
MapStruct can even be used to “cherry pick” properties when source and target do not share the same nesting level (the same number of properties).
This can be done in the source and in the target type. This is demonstrated in the next 2 rules: `@Mapping(target="ornament", source="interior.ornament")` and `@Mapping(target="material.materialType", source="material")`.
The latter can even be done when mappings first share a common base.
For example: all properties that share the same name of `Quality` are mapped to `QualityDto`.
Likewise, all properties of `Report` are mapped to `ReportDto`, with one exception: `organisation` in `OrganisationDto` is left empty (since there is no organization at the source level).
Only the `name` is populated with the `organisationName` from `Report`.
This is demonstrated in `@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")`
Coming back to the original example: what if `kind` and `type` would be beans themselves?
In that case MapStruct would again generate a method continuing to map.
Such is demonstrated in the next example:
.Mapper controlling nested beans mappings II
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapperWithDocument {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", expression = "java(\"Jaws\")")
@Mapping(target = "plant", ignore = true )
@Mapping(target = "ornament", ignore = true )
@Mapping(target = "material", ignore = true)
@Mapping(target = "quality.document", source = "quality.report")
@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
FishTankWithNestedDocumentDto map( FishTank source );
}
----
====
Note what happens in `@Mapping(target="quality.document", source="quality.report")`.
`DocumentDto` does not exist as such on the target side. It is mapped from `Report`.
MapStruct continues to generate mapping code here. That mapping itself can be guided towards another name.
This even works for constants and expression. Which is shown in the final example: `@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc")`.
MapStruct will perform a null check on each nested property in the source.
[TIP]
====
Instead of configuring everything via the parent method we encourage users to explicitly write their own nested methods.
This puts the configuration of the nested mapping into one place (method) where it can be reused from several methods in the upper level,
instead of re-configuring the same things on all of those upper methods.
====
[TIP]
====
When ignoring multiple properties instead of defining multiple `@Mapping` annotations, you can use the `@Ignored` annotation to group them together.
e.g. for the `FishTankMapperWithDocument` example above, you could write:
`@Ignored(targets = { "plant", "ornament", "material" })`
====
[NOTE]
====
In some cases the `ReportingPolicy` that is going to be used for the generated nested method would be `IGNORE`.
This means that it is possible for MapStruct not to report unmapped target properties in nested mappings.
====
[[invoking-custom-mapping-method]]
=== Invoking custom mapping method
Sometimes mappings are not straightforward and some fields require custom logic.
The example below demonstrates how the properties `length`, `width` and `height` in `FishTank` can be mapped to the `VolumeDto` bean, which is a member of `FishTankWithVolumeDto`. `VolumeDto` contains the properties `volume` and `description`. Custom logic is achieved by defining a method which takes `FishTank` instance as a parameter and returns a `VolumeDto`. MapStruct will take the entire parameter `source` and generate code to call the custom method `mapVolume` in order to map the `FishTank` object to the target property `volume`.
The remainder of the fields could be mapped the regular way: using mappings defined defined by means of `@Mapping` annotations.
.Manually implemented mapping method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class FishTank {
Fish fish;
String material;
Quality quality;
int length;
int width;
int height;
}
public class FishTankWithVolumeDto {
FishDto fish;
MaterialDto material;
QualityDto quality;
VolumeDto volume;
}
public class VolumeDto {
int volume;
String description;
}
@Mapper
public abstract class FishTankMapperWithVolume {
@Mapping(target = "fish.kind", source = "source.fish.type")
@Mapping(target = "material.materialType", source = "source.material")
@Mapping(target = "quality.document", source = "source.quality.report")
@Mapping(target = "volume", source = "source")
abstract FishTankWithVolumeDto map(FishTank source);
VolumeDto mapVolume(FishTank source) {
int volume = source.length * source.width * source.height;
String desc = volume < 100 ? "Small" : "Large";
return new VolumeDto(volume, desc);
}
}
----
====
Note the `@Mapping` annotation where `source` field is equal to `"source"`, indicating the parameter name `source` itself in the method `map(FishTank source)` instead of a (target) property in `FishTank`.
[[invoking-other-mappers]]
=== Invoking other mappers
In addition to methods defined on the same mapper type MapStruct can also invoke mapping methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping methods. This can be useful to structure your mapping code in several classes (e.g. with one mapper type per application module) or if you want to provide custom mapping logic which can't be generated by MapStruct.
For instance the `Car` class might contain an attribute `manufacturingDate` while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this:
.Manually implemented mapper class
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class DateMapper {
public String asString(Date date) {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.format( date ) : null;
}
public Date asDate(String date) {
try {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.parse( date ) : null;
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
}
}
----
====
In the `@Mapper` annotation at the `CarMapper` interface reference the `DateMapper` class like this:
.Referencing another mapper class
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(uses=DateMapper.class)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
----
====
When generating code for the implementation of the `carToCarDto()` method, MapStruct will look for a method which maps a `Date` object into a String, find it on the `DateMapper` class and generate an invocation of `asString()` for mapping the `manufacturingDate` attribute.
Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model for `CarMapper`, `DateMapper` would have to be a CDI bean as well. When using the default component model, any hand-written mapper classes to be referenced by MapStruct generated mappers must declare a public no-args constructor in order to be instantiable.
[[passing-target-type]]
=== Passing the mapping target type to custom mappers
When having a custom mapper hooked into the generated mapper with `@Mapper#uses()`, an additional parameter of type `Class` (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with `@TargetType` for MapStruct to generate calls that pass the `Class` instance representing the corresponding property type of the target bean.
For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances.
e.g.
.Example classes for the passing target type example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Car {
private Person owner;
// ...
}
public class Person extends BaseEntity {
// ...
}
public class Reference {
private String pk;
// ...
}
public class CarDto {
private Reference owner;
// ...
}
----
====
.Mapping method expecting mapping target type as parameter
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@ApplicationScoped // CDI component model
public class ReferenceMapper {
@PersistenceContext
private EntityManager entityManager;
public <T extends BaseEntity> T resolve(Reference reference, @TargetType Class<T> entityClass) {
return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null;
}
public Reference toReference(BaseEntity entity) {
return entity != null ? new Reference( entity.getPk() ) : null;
}
}
@Mapper(componentModel = MappingConstants.ComponentModel.CDI, uses = ReferenceMapper.class )
public interface CarMapper {
Car carDtoToCar(CarDto carDto);
}
----
====
MapStruct will then generate something like this:
.Generated code
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
@ApplicationScoped
public class CarMapperImpl implements CarMapper {
@Inject
private ReferenceMapper referenceMapper;
@Override
public Car carDtoToCar(CarDto carDto) {
if ( carDto == null ) {
return null;
}
Car car = new Car();
car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) );
// ...
return car;
}
}
----
====
[[passing-context]]
=== Passing context or state objects to custom methods
Additional _context_ or _state_ information can be passed through generated mapping methods to custom methods with `@Context` parameters. Such parameters are passed to other mapping methods, `@ObjectFactory` methods (see <<object-factories>>) or `@BeforeMapping` / `@AfterMapping` methods (see <<customizing-mappings-with-before-and-after>>) when applicable and can thus be used in custom code.
`@Context` parameters are searched for `@ObjectFactory` methods, which are called on the provided context parameter value if applicable.
`@Context` parameters are also searched for `@BeforeMapping` / `@AfterMapping` methods, which are called on the provided context parameter value if applicable.
*Note:* no `null` checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure that `null` is not passed in that case.
For generated code to call a method that is declared with `@Context` parameters, the declaration of the mapping method being generated needs to contain at least those (or assignable) `@Context` parameters as well. The generated code will not create new instances of missing `@Context` parameters nor will it pass a literal `null` instead.
.Using `@Context` parameters for passing data down to hand-written property mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public abstract CarDto toCar(Car car, @Context Locale translationLocale);
protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) {
// manually implemented logic to translate the OwnerManual with the given Locale
}
----
====
MapStruct will then generate something like this:
.Generated code
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
public CarDto toCar(Car car, Locale translationLocale) {
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), translationLocale );
// more generated mapping code
return carDto;
}
----
====
[[mapping-method-resolution]]
=== Mapping method resolution
When mapping a property from one type to another, MapStruct looks for the most specific method which maps the source type into the target type. The method may either be declared on the same mapper interface or on another mapper which is registered via `@Mapper#uses()`. The same applies for factory methods (see <<object-factories>>).
The algorithm for finding a mapping or factory method resembles Java's method resolution algorithm as much as possible. In particular, methods with a more specific source type will take precedence (e.g. if there are two methods, one which maps the searched source type, and another one which maps a super-type of the same). In case more than one most-specific method is found, an error will be raised.
[TIP]
====
When working with JAXB, e.g. when converting a `String` to a corresponding `JAXBElement<String>`, MapStruct will take the `scope` and `name` attributes of `@XmlElementDecl` annotations into account when looking for a mapping method. This makes sure that the created `JAXBElement` instances will have the right QNAME value. You can find a test which maps JAXB objects https://github.com/mapstruct/mapstruct/blob/{mapstructVersion}/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java[here].
====
[[selection-based-on-qualifiers]]
=== Mapping method selection based on qualifiers
In many occasions one requires mapping methods with the same method signature (apart from the name) that have different behavior.
MapStruct has a handy mechanism to deal with such situations: `@Qualifier` (`org.mapstruct.Qualifier`).
A qualifier is a custom annotation that the user can write, stick onto a mapping method which is included as used mapper
and can be referred to in a bean property mapping, iterable mapping or map mapping.
Multiple qualifiers can be stuck onto a method and mapping.
So, let's say there is a hand-written method to map titles with a `String` return type and `String` argument amongst many other referenced mappers with the same `String` return type - `String` argument signature:
.Several mapping methods with identical source and target types
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class Titles {
public String translateTitleEG(String title) {
// some mapping logic
}
public String translateTitleGE(String title) {
// some mapping logic
}
}
----
====
And a mapper using this handwritten mapper, in which source and target have a property 'title' that should be mapped:
.Mapper causing an ambiguous mapping method error
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper( uses = Titles.class )
public interface MovieMapper {
GermanRelease toGerman( OriginalRelease movies );
}
----
====
Without the use of qualifiers, this would result in an ambiguous mapping method error, because 2 qualifying methods are found (`translateTitleEG`, `translateTitleGE`) and MapStruct would not have a hint which one to choose.
Enter the qualifier approach:
.Declaring a qualifier type
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TitleTranslator {
}
----
====
And, some qualifiers to indicate which translator to use to map from source language to target language:
.Declaring qualifier types for mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnglishToGerman {
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
import org.mapstruct.Qualifier;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface GermanToEnglish {
}
----
====
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:
.Mapper using qualifiers
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper( uses = Titles.class )
public interface MovieMapper {
@Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } )
GermanRelease toGerman( OriginalRelease movies );
}
----
====
.Custom mapper qualifying the methods it provides
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@TitleTranslator
public class Titles {
@EnglishToGerman
public String translateTitleEG(String title) {
// some mapping logic
}
@GermanToEnglish
public String translateTitleGE(String title) {
// some mapping logic
}
}
----
====
[WARNING]
====
Please make sure the used retention policy equals retention policy `CLASS` (`@Retention(RetentionPolicy.CLASS)`).
====
[WARNING]
====
A class / method annotated with a qualifier will not qualify anymore for mappings that do not have the `qualifiedBy` element.
====
[TIP]
====
The same mechanism is also present on bean mappings: `@BeanMapping#qualifiedBy`: it selects the factory method marked with the indicated qualifier.
====
In many occasions, declaring a new annotation to aid the selection process can be too much for what you try to achieve. For those situations, MapStruct has the `@Named` annotation. This annotation is a pre-defined qualifier (annotated with `@Qualifier` itself) and can be used to name a Mapper or, more directly a mapping method by means of its value. The same example above would look like:
.Custom mapper, annotating the methods to qualify by means of `@Named`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Named("TitleTranslator")
public class Titles {
@Named("EnglishToGerman")
public String translateTitleEG(String title) {
// some mapping logic
}
@Named("GermanToEnglish")
public String translateTitleGE(String title) {
// some mapping logic
}
}
----
====
.Mapper using named
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper( uses = Titles.class )
public interface MovieMapper {
@Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
GermanRelease toGerman( OriginalRelease movies );
}
----
====
[WARNING]
====
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.
====
=== Combining qualifiers with defaults
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`.
.Mapper using defaultValue
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface MovieMapper {
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "DEFAULT" )
GermanRelease toGerman( OriginalRelease movies );
@Named("CategoryToString")
default String defaultValueForQualifier(Category cat) {
// some mapping logic
}
}
----
====
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.
.Mapper using defaultValue and default method.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface MovieMapper {
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "Unknown" )
GermanRelease toGerman( OriginalRelease movies );
@Named("CategoryToString")
default String defaultValueForQualifier(Category cat) {
// some mapping logic
}
@Named("CategoryToString")
default String defaultValueForQualifier(String value) {
return value;
}
}
----
====
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.
If the above mentioned methods do not work there is the option to use `defaultExpression` to set the default value.
.Mapper using defaultExpression
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface MovieMapper {
@Mapping( target = "category", qualifiedByName = "CategoryToString", defaultExpression = "java(\"Unknown\")" )
GermanRelease toGerman( OriginalRelease movies );
@Named("CategoryToString")
default String defaultValueForQualifier(Category cat) {
// some mapping logic
}
}
----
====

View File

@ -0,0 +1,231 @@
[[mapping-collections]]
== Mapping collections
The mapping of collection types (`List`, `Set` etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface. MapStruct supports a wide range of iterable types from the http://docs.oracle.com/javase/tutorial/collections/intro/index.html[Java Collection Framework].
The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection. If a mapping method for the collection element types is found in the given mapper or the mapper it uses, this method is invoked to perform the element conversion. Alternatively, if an implicit conversion for the source and target element types exists, this conversion routine will be invoked. The following shows an example:
.Mapper with collection mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
Set<String> integerSetToStringSet(Set<Integer> integers);
List<CarDto> carsToCarDtos(List<Car> cars);
CarDto carToCarDto(Car car);
}
----
====
The generated implementation of the `integerSetToStringSet` performs the conversion from `Integer` to `String` for each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained element as shown in the following:
.Generated collection mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
@Override
public Set<String> integerSetToStringSet(Set<Integer> integers) {
if ( integers == null ) {
return null;
}
Set<String> set = new LinkedHashSet<String>();
for ( Integer integer : integers ) {
set.add( String.valueOf( integer ) );
}
return set;
}
@Override
public List<CarDto> carsToCarDtos(List<Car> cars) {
if ( cars == null ) {
return null;
}
List<CarDto> list = new ArrayList<CarDto>();
for ( Car car : cars ) {
list.add( carToCarDto( car ) );
}
return list;
}
----
====
Note that MapStruct will look for a collection mapping method with matching parameter and return type, when mapping a collection-typed attribute of a bean, e.g. from `Car#passengers` (of type `List<Person>`) to `CarDto#passengers` (of type `List<PersonDto>`).
.Usage of collection mapping method to map a bean property
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) );
...
----
====
Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In this case the generated code for mapping such a property invokes its getter and adds all the mapped elements:
.Usage of an adding method for collection mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) );
...
----
====
[WARNING]
====
It is not allowed to declare mapping methods with an iterable source (from a java package) and a non-iterable target or the other way around. An error will be raised when detecting this situation.
====
[[mapping-maps]]
=== Mapping maps
Also map-based mapping methods are supported. The following shows an example:
.Map mapping method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public interface SourceTargetMapper {
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
----
====
Similar to iterable mappings, the generated code will iterate through the source map, convert each value and key (either by means of an implicit conversion or by invoking another mapping method) and put them into the target map:
.Generated implementation of map mapping method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
Map<Long, Date> map = new LinkedHashMap<Long, Date>();
for ( Map.Entry<String, String> entry : source.entrySet() ) {
Long key = Long.parseLong( entry.getKey() );
Date value;
try {
value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() );
}
catch( ParseException e ) {
throw new RuntimeException( e );
}
map.put( key, value );
}
return map;
}
----
====
[[collection-mapping-strategies]]
=== Collection mapping strategies
MapStruct has a `CollectionMappingStrategy`, with the possible values: `ACCESSOR_ONLY`, `SETTER_PREFERRED`, `ADDER_PREFERRED` and `TARGET_IMMUTABLE`.
In the table below, the dash `-` indicates a property name. Next, the trailing `s` indicates the plural form. The table explains the options and how they are applied to the presence/absence of a `set-s`, `add-` and / or `get-s` method on the target object:
.Collection mapping strategy options
|===
|Option|Only target set-s Available|Only target add- Available|Both set-s / add- Available|No set-s / add- Available|Existing Target(`@TargetType`)
|`ACCESSOR_ONLY`
|set-s
|get-s
|set-s
|get-s
|get-s
|`SETTER_PREFERRED`
|set-s
|add-
|set-s
|get-s
|get-s
|`ADDER_PREFERRED`
|set-s
|add-
|add-
|get-s
|get-s
|`TARGET_IMMUTABLE`
|set-s
|exception
|set-s
|exception
|set-s
|===
Some background: An `adder` method is typically used in case of http://www.eclipse.org/webtools/dali/[generated (JPA) entities], to add a single element (entity) to an underlying collection. Invoking the adder establishes a parent-child relation between parent - the bean (entity) on which the adder is invoked - and its child(ren), the elements (entities) in the collection. To find the appropriate `adder`, MapStruct will try to make a match between the generic parameter type of the underlying collection and the single argument of a candidate `adder`. When there are more candidates, the plural `setter` / `getter` name is converted to singular and will be used in addition to make a match.
The option `DEFAULT` should not be used explicitly. It is used to distinguish between an explicit user desire to override the default in a `@MapperConfig` from the implicit Mapstruct choice in a `@Mapper`. The option `DEFAULT` is synonymous to `ACCESSOR_ONLY`.
[TIP]
====
When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with initialized collections instead of Mapstruct creating the target entity by its constructor.
====
[[implementation-types-for-collection-mappings]]
=== Implementation types used for collection mappings
When an iterable or map mapping method declares an interface type as return type, one of its implementation types will be instantiated in the generated code. The following table shows the supported interface types and their corresponding implementation types as instantiated in the generated code:
.Collection mapping implementation types
|===
|Interface type|Implementation type
|`Iterable`|`ArrayList`
|`Collection`|`ArrayList`
|`List`|`ArrayList`
|`Set`|`LinkedHashSet`
|`SequencedSet`|`LinkedHashSet`
|`SortedSet`|`TreeSet`
|`NavigableSet`|`TreeSet`
|`Map`|`LinkedHashMap`
|`SequencedMap`|`LinkedHashMap`
|`SortedMap`|`TreeMap`
|`NavigableMap`|`TreeMap`
|`ConcurrentMap`|`ConcurrentHashMap`
|`ConcurrentNavigableMap`|`ConcurrentSkipListMap`
|===

View File

@ -41,8 +41,8 @@ public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
return null;
}
return integers.stream().map( integer -> String.valueOf( integer ) )
.collect( Collectors.toCollection( HashSet<String>::new ) );
return integers.map( integer -> String.valueOf( integer ) )
.collect( Collectors.toCollection( LinkedHashSet<String>::new ) );
}
@Override
@ -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

@ -0,0 +1,356 @@
[[mapping-enum-types]]
== Mapping Values
=== Mapping enum to enum types
MapStruct supports the generation of methods which map one Java enum type into another.
By default, each constant from the source enum is mapped to a constant with the same name in the target enum type. If required, a constant from the source enum may be mapped to a constant with another name with help of the `@ValueMapping` annotation. Several constants from the source enum can be mapped to the same constant in the target type.
The following shows an example:
.Enum mapping method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(target = "SPECIAL", source = "EXTRA"),
@ValueMapping(target = "DEFAULT", source = "STANDARD"),
@ValueMapping(target = "DEFAULT", source = "NORMAL")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
----
====
.Enum mapping method result
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class OrderMapperImpl implements OrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return null;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL;
break;
case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
}
return externalOrderType_;
}
}
----
====
By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated
mapping method will throw an `IllegalStateException` if for some reason an unrecognized source value occurs.
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings and only applies to the source. It comes in two flavors: `<ANY_REMAINING>` and `<ANY_UNMAPPED>`. They cannot be used at the same time.
In case of source `<ANY_REMAINING>` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `<ANY_REMAINING>` source.
MapStruct will *not* attempt such name based mapping for `<ANY_UNMAPPED>` and directly apply the target specified in the `@ValueMapping` with `<ANY_UNMAPPED>` source to the remainder.
MapStruct is able to handle `null` sources and `null` targets by means of the `<NULL>` keyword.
In addition, the constant value `<THROW_EXCEPTION>` can be used for throwing an exception for particular value mappings. This value is only applicable to `ValueMapping#target()` and not `ValueMapping#source()` since MapStruct can't map from exceptions.
[TIP]
====
Constants for `<ANY_REMAINING>`, `<ANY_UNMAPPED>`, `<THROW_EXCEPTION>` and `<NULL>` are available in the `MappingConstants` class.
====
Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `<ANY_REMAINING>` and `<ANY_UNMAPPED>` will be ignored in that case.
The following code snippets exemplify the use of the aforementioned constants.
.Enum mapping method, `<NULL>` and `<ANY_REMAINING>`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface SpecialOrderMapper {
SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
----
====
.Enum mapping method result, `<NULL>` and `<ANY_REMAINING>`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class SpecialOrderMapperImpl implements SpecialOrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return ExternalOrderType.DEFAULT;
}
ExternalOrderType externalOrderType_;
switch ( orderType ) {
case STANDARD: externalOrderType_ = null;
break;
case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL;
break;
case B2B: externalOrderType_ = ExternalOrderType.B2B;
break;
default: externalOrderType_ = ExternalOrderType.SPECIAL;
}
return externalOrderType_;
}
}
----
====
*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `<ANY_UNMAPPED>` was used instead of `<ANY_REMAINING>`.
.Enum mapping method with `<THROW_EXCEPTION>`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface SpecialOrderMapper {
SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );
@ValueMappings({
@ValueMapping( source = "STANDARD", target = "DEFAULT" ),
@ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
----
====
.Enum mapping method with `<THROW_EXCEPTION>` result
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class SpecialOrderMapperImpl implements SpecialOrderMapper {
@Override
public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {
if ( orderType == null ) {
return null;
}
ExternalOrderType externalOrderType;
switch ( orderType ) {
case STANDARD: externalOrderType = ExternalOrderType.DEFAULT;
break;
case C2C: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType );
}
return externalOrderType;
}
}
----
====
=== Mapping enum-to-String or String-to-enum
MapStruct supports enum to a String mapping along the same lines as is described in <<Mapping enum to enum types, enum-to-enum types>>. There are similarities and differences:
*enum to `String`*
1. Similarity: All not explicit defined mappings will result in each source enum constant value being mapped a `String` value with the same constant value.
2. Similarity: `<ANY_UNMAPPED`> stops after handling defined mapping and proceeds to the switch/default clause value.
3. Difference: `<ANY_REMAINING>` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.
4. Difference: Given 1. and 3. there will never be unmapped values.
5. Similarity: `<THROW_EXCEPTION>` can be used for throwing an exception for particular enum values.
*`String` to enum*
1. Similarity: All not explicit defined mappings will result in the target enum constant mapped from the `String` value when that matches the target enum constant name.
2. Similarity: `<ANY_UNMAPPED`> stops after handling defined mapping and proceeds to the switch/default clause value.
3. Similarity: `<ANY_REMAINING>` will create a mapping for each target enum constant and proceed to the switch/default clause value.
4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `<ANY_REMAINING>` or `<ANY_UNMAPPED`> will result in a warning.
5. Similarity: `<THROW_EXCEPTION>` can be used for throwing an exception for any arbitrary `String` value.
=== Custom name transformation
When no `@ValueMapping`(s) are defined then each constant from the source enum is mapped to a constant with the same name in the target enum type.
However, there are cases where the source enum needs to be transformed before doing the mapping.
E.g. a suffix needs to be applied to map from the source into the target enum.
.Enum types
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CheeseType {
BRIE,
ROQUEFORT
}
public enum CheeseTypeSuffixed {
BRIE_TYPE,
ROQUEFORT_TYPE
}
----
====
.Enum mapping method with custom name transformation strategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CheeseMapper {
CheeseMapper INSTANCE = Mappers.getMapper( CheeseMapper.class );
@EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")
CheeseTypeSuffixed map(CheeseType cheese);
@InheritInverseConfiguration
CheeseType map(CheeseTypeSuffix cheese);
}
----
====
.Enum mapping method with custom name transformation strategy result
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CheeseSuffixMapperImpl implements CheeseSuffixMapper {
@Override
public CheeseTypeSuffixed map(CheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseTypeSuffixed cheeseTypeSuffixed;
switch ( cheese ) {
case BRIE: cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;
break;
case ROQUEFORT: cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseTypeSuffixed;
}
@Override
public CheeseType map(CheeseTypeSuffixed cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case BRIE_TYPE: cheeseType = CheeseType.BRIE;
break;
case ROQUEFORT_TYPE: cheeseType = CheeseType.ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
}
----
====
MapStruct provides the following out of the box enum name transformation strategies:
* _suffix_ - Applies a suffix on the source enum
* _stripSuffix_ - Strips a suffix from the source enum
* _prefix_ - Applies a prefix on the source enum
* _stripPrefix_ - Strips a prefix from the source enum
* _case_ - Applies case transformation to the source enum. Supported _case_ transformations are:
** _upper_ - Performs upper case transformation to the source enum
** _lower_ - Performs lower case transformation to the source enum
** _capital_ - Performs capitalisation of the first character of every word in the source enum and everything else to lowercase. A word is split by "_"
It is also possible to register custom strategies.
For more information on how to do that have a look at <<custom-enum-transformation-strategy>>
[[value-mapping-composition]]
=== ValueMapping Composition
The `@ValueMapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`.
This allows `@ValueMapping` to be used on other (user defined) annotations for re-use purposes.
For example:
.Custom value mapping annotations
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Retention( RetentionPolicy.CLASS )
@ValueMapping(source = "EXTRA", target = "SPECIAL")
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
public @interface CustomValueAnnotation {
}
----
====
It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example:
.Using custom combination annotations
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface ValueMappingCompositionMapper {
@CustomValueAnnotation
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
@CustomValueAnnotation
@ValueMapping(source = "STANDARD", target = "SPECIAL")
ExternalOrderType duplicateAnnotation(OrderType orderType);
}
----
====

View File

@ -0,0 +1,170 @@
[[object-factories]]
== Object factories
By default, the generated code for mapping one bean type into another or updating a bean will call the default constructor to instantiate the target type.
Alternatively you can plug in custom object factories which will be invoked to obtain instances of the target type. One use case for this is JAXB which creates `ObjectFactory` classes for obtaining new instances of schema types.
To make use of custom factories register them via `@Mapper#uses()` as described in <<invoking-other-mappers>>, or implement them directly in your mapper. When creating the target object of a bean mapping, MapStruct will look for a parameterless method, a method annotated with `@ObjectFactory`, or a method with only one `@TargetType` parameter that returns the required target type and invoke this method instead of calling the default constructor:
.Custom object factories
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class DtoFactory {
public CarDto createCarDto() {
return // ... custom factory logic
}
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class EntityFactory {
public <T extends BaseEntity> T createEntity(@TargetType Class<T> entityClass) {
return // ... custom factory logic
}
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(uses= { DtoFactory.class, EntityFactory.class } )
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
CarDto carToCarDto(Car car);
Car carDtoToCar(CarDto carDto);
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
public class CarMapperImpl implements CarMapper {
private final DtoFactory dtoFactory = new DtoFactory();
private final EntityFactory entityFactory = new EntityFactory();
@Override
public CarDto carToCarDto(Car car) {
if ( car == null ) {
return null;
}
CarDto carDto = dtoFactory.createCarDto();
//map properties...
return carDto;
}
@Override
public Car carDtoToCar(CarDto carDto) {
if ( carDto == null ) {
return null;
}
Car car = entityFactory.createEntity( Car.class );
//map properties...
return car;
}
}
----
====
.Custom object factories with update methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper(uses = { DtoFactory.class, EntityFactory.class, CarMapper.class } )
public interface OwnerMapper {
OwnerMapper INSTANCE = Mappers.getMapper( OwnerMapper.class );
void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto);
void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner);
}
----
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
public class OwnerMapperImpl implements OwnerMapper {
private final DtoFactory dtoFactory = new DtoFactory();
private final EntityFactory entityFactory = new EntityFactory();
private final OwnerMapper ownerMapper = Mappers.getMapper( OwnerMapper.class );
@Override
public void updateOwnerDto(Owner owner, @MappingTarget OwnerDto ownerDto) {
if ( owner == null ) {
return;
}
if ( owner.getCar() != null ) {
if ( ownerDto.getCar() == null ) {
ownerDto.setCar( dtoFactory.createCarDto() );
}
// update car within ownerDto
}
else {
ownerDto.setCar( null );
}
// updating other properties
}
@Override
public void updateOwner(OwnerDto ownerDto, @MappingTarget Owner owner) {
if ( ownerDto == null ) {
return;
}
if ( ownerDto.getCar() != null ) {
if ( owner.getCar() == null ) {
owner.setCar( entityFactory.createEntity( Car.class ) );
}
// update car within owner
}
else {
owner.setCar( null );
}
// updating other properties
}
}
----
====
In addition, annotating a factory method with `@ObjectFactory` lets you gain access to the mapping sources.
Source objects can be added as parameters in the same way as for mapping method. The `@ObjectFactory`
annotation is necessary to let MapStruct know that the given method is only a factory method.
.Custom object factories with `@ObjectFactory`
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class DtoFactory {
@ObjectFactory
public CarDto createCarDto(Car car) {
return // ... custom factory logic
}
}
----
====

View File

@ -1,89 +0,0 @@
[[controlling-nested-bean-mappings]]
=== Controlling nested bean mappings
As explained above, MapStruct will generate a method based on the name of the source and target property. Unfortunately, in many occasions these names do not match.
The . notation in an `@Mapping` source or target type can be used to control how properties should be mapped when names do not match.
There is an elaborate https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-nested-bean-mappings[example] in our examples repository to explain how this problem can be overcome.
In the simplest scenario theres a property on a nested level that needs to be corrected.
Take for instance a property `fish` which has an identical name in `FishTankDto` and `FishTank`.
For this property MapStruct automatically generates a mapping: `FishDto fishToFishDto(Fish fish)`.
MapStruct cannot possibly be aware of the deviating properties `kind` and `type`.
Therefore this can be addressed in a mapping rule: `@Mapping(target="fish.kind", source="fish.type")`.
This tells MapStruct to deviate from looking for a name `kind` at this level and map it to `type`.
.Mapper controlling nested beans mappings I
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapper {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", ignore = true)
@Mapping(target = "ornament", source = "interior.ornament")
@Mapping(target = "material.materialType", source = "material")
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
FishTankDto map( FishTank source );
}
----
====
The same constructs can be used to ignore certain properties at a nesting level, as is demonstrated in the second `@Mapping` rule.
MapStruct can even be used to “cherry pick” properties when source and target do not share the same nesting level (the same number of properties).
This can be done in the source and in the target type. This is demonstrated in the next 2 rules: `@Mapping(target="ornament", source="interior.ornament")` and `@Mapping(target="material.materialType", source="material")`.
The latter can even be done when mappings first share a common base.
For example: all properties that share the same name of `Quality` are mapped to `QualityDto`.
Likewise, all properties of `Report` are mapped to `ReportDto`, with one exception: `organisation` in `OrganisationDto` is left empty (since there is no organization at the source level).
Only the `name` is populated with the `organisationName` from `Report`.
This is demonstrated in `@Mapping(target="quality.report.organisation.name", source="quality.report.organisationName")`
Coming back to the original example: what if `kind` and `type` would be beans themselves?
In that case MapStruct would again generate a method continuing to map.
Such is demonstrated in the next example:
.Mapper controlling nested beans mappings II
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface FishTankMapperWithDocument {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", expression = "java(\"Jaws\")")
@Mapping(target = "plant", ignore = true )
@Mapping(target = "ornament", ignore = true )
@Mapping(target = "material", ignore = true)
@Mapping(target = "quality.document", source = "quality.report")
@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
FishTankWithNestedDocumentDto map( FishTank source );
}
----
====
Note what happens in `@Mapping(target="quality.document", source="quality.report")`.
`DocumentDto` does not exist as such on the target side. It is mapped from `Report`.
MapStruct continues to generate mapping code here. That mapping itself can be guided towards another name.
This even works for constants and expression. Which is shown in the final example: `@Mapping(target="quality.document.organisation.name", constant="NoIdeaInc")`.
MapStruct will perform a null check on each nested property in the source.
[TIP]
====
Instead of configuring everything via the parent method we encourage users to explicitly write their own nested methods.
This puts the configuration of the nested mapping into one place (method) where it can be reused from several methods in the upper level,
instead of re-configuring the same things on all of those upper methods.
====
[NOTE]
====
In some cases the `ReportingPolicy` that is going to be used for the generated nested method would be `IGNORE`.
This means that it is possible for MapStruct not to report unmapped target properties in nested mappings.
====

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF8"?>
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.8</version>
<vendor>oracle</vendor>
<id>jdk1.8</id>
</provides>
<configuration>
<jdkHome>/opt/jdk/jdk8.latest</jdkHome>
</configuration>
</toolchain>
</toolchains>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF8"?>
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.8.0_11</version>
<vendor>oracle</vendor>
<id>jdk1.8</id>
</provides>
<configuration>
<jdkHome>C:\Program Files\Java\jdk1.8.0_11</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>1.9.0</version>
<vendor>oracle</vendor>
<id>jdk1.9</id>
</provides>
<configuration>
<jdkHome>C:\Program Files\Java\jdk1.9.0</jdkHome>
</configuration>
</toolchain>
</toolchains>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF8"?>
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.8</version>
<vendor>oracle</vendor>
<id>jdk1.8</id>
</provides>
<configuration>
<jdkHome>/usr/lib/jvm/java-8-oracle/</jdkHome>
</configuration>
</toolchain>
</toolchains>

View File

@ -12,7 +12,7 @@
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-parent</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.7.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
@ -24,14 +24,22 @@
<mapstruct.version>${project.version}</mapstruct.version>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<repositories>
<repository>
<id>gradle</id>
<url>https://repo.gradle.org/artifactory/libs-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- Testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
@ -42,6 +50,34 @@
<artifactId>maven-verifier</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gradle</groupId>
<artifactId>gradle-test-kit</artifactId>
<version>5.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gradle</groupId>
<artifactId>gradle-tooling-api</artifactId>
<version>5.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -86,4 +122,21 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jdk-11-or-newer</id>
<activation>
<jdk>[11</jdk>
</activation>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View File

@ -1,19 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Filip Hrisafov
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite(baseDir = "autoValueBuilderTest",
processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN)
public class AutoValueBuilderTest {
}

View File

@ -1,20 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Andreas Gudian
*
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "cdiTest", processorTypes = ProcessorType.ALL )
public class CdiTest {
}

View File

@ -1,22 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
*
* See: https://github.com/mapstruct/mapstruct/issues/1121
*
* @author Sjaak Derksen
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite(baseDir = "externalbeanjar", processorTypes = ProcessorSuite.ProcessorType.ORACLE_JAVA_8)
public class ExternalBeanJarTest {
}

View File

@ -1,19 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Filip Hrisafov
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "freeBuilderBuilderTest",
processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN)
public class FreeBuilderBuilderTest {
}

View File

@ -0,0 +1,65 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.jupiter.api.condition.JRE;
import org.mapstruct.itest.testutil.extension.ProcessorTest;
/**
* Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers.
*
* @author Andreas Gudian
*/
public final class FullFeatureCompilationExclusionCliEnhancer implements ProcessorTest.CommandLineEnhancer {
@Override
public Collection<String> getAdditionalCommandLineArguments(ProcessorTest.ProcessorType processorType,
JRE currentJreVersion) {
List<String> additionalExcludes = new ArrayList<>();
// 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" );
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" );
switch ( currentJreVersion ) {
case JAVA_8:
additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) {
additionalExcludes.add(
"org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" );
}
break;
case JAVA_9:
// TODO find out why this fails:
additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" );
additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
break;
case JAVA_11:
additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" );
additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
break;
case JAVA_17:
additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" );
break;
default:
}
Collection<String> result = new ArrayList<>(additionalExcludes.size());
for ( int i = 0; i < additionalExcludes.size(); i++ ) {
result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) );
}
return result;
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.runner.RunWith;
import org.mapstruct.itest.tests.FullFeatureCompilationTest.CompilationExclusionCliEnhancer;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.CommandLineEnhancer;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* Integration test that compiles all test mappers in the processor-module, excluding all classes that contain one of
* the following in their path/file name:
* <ul>
* <li>{@code /erronerous/}</li>
* <li>{@code *Erroneous*}</li>
* <li>{@code *Test.java}</li>
* <li>{@code /testutil/}</li>
* <li>possibly more, depending on the processor type - see {@link CompilationExclusionCliEnhancer}</li>
* </ul>
*
* @author Andreas Gudian
*/
@RunWith(ProcessorSuiteRunner.class)
@ProcessorSuite(
baseDir = "fullFeatureTest",
commandLineEnhancer = CompilationExclusionCliEnhancer.class,
processorTypes = {
ProcessorType.ORACLE_JAVA_8,
ProcessorType.ORACLE_JAVA_9,
ProcessorType.ECLIPSE_JDT_JAVA_8
})
public class FullFeatureCompilationTest {
/**
* Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers.
*
* @author Andreas Gudian
*/
public static final class CompilationExclusionCliEnhancer implements CommandLineEnhancer {
@Override
public Collection<String> getAdditionalCommandLineArguments(ProcessorType processorType) {
List<String> additionalExcludes = new ArrayList<>();
// SPI not working correctly here.. (not picked up)
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" );
switch ( processorType ) {
case ORACLE_JAVA_9:
// TODO find out why this fails:
additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" );
break;
default:
}
Collection<String> result = new ArrayList<String>( additionalExcludes.size() );
for ( int i = 0; i < additionalExcludes.size(); i++ ) {
result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) );
}
return result;
}
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.runners.Parameterized.Parameters;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* <p>This is supposed to be run from the mapstruct root project folder.
* Otherwise, use <code>-Dmapstruct_root=path_to_project</code>.
*/
@DisabledForJreRange(min = JRE.JAVA_11)
public class GradleIncrementalCompilationTest {
private static Path rootPath;
private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest";
private static String compileTaskName = "compileJava";
@TempDir
File testBuildDir;
@TempDir
File testProjectDir;
private GradleRunner runner;
private File sourceDirectory;
private List<String> compileArgs; // Gradle compile task arguments
@Parameters(name = "Gradle {0}")
public static List<String> gradleVersions() {
return Arrays.asList( "5.0", "6.0" );
}
private void replaceInFile(File file, CharSequence target, CharSequence replacement) throws IOException {
String content = FileUtils.readFileToString( file, Charset.defaultCharset() );
FileUtils.writeStringToFile( file, content.replace( target, replacement ), Charset.defaultCharset() );
}
private GradleRunner getRunner(String... additionalArguments) {
List<String> fullArguments = new ArrayList<>(compileArgs);
fullArguments.addAll( Arrays.asList( additionalArguments ) );
return runner.withArguments( fullArguments );
}
private void assertCompileOutcome(BuildResult result, TaskOutcome outcome) {
assertEquals( outcome, result.task( ":" + compileTaskName ).getOutcome() );
}
private void assertRecompiled(BuildResult result, int recompiledCount) {
assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE );
assertThat(
result.getOutput(),
containsString( String.format( "Incremental compilation of %d classes completed", recompiledCount ) )
);
}
private List<String> buildCompileArgs() {
// Make Gradle use the temporary build folder by overriding the buildDir property
String buildDirPropertyArg = "-PbuildDir=" + testBuildDir.getAbsolutePath();
// Inject the path to the folder containing the mapstruct-processor JAR
String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString();
return Arrays.asList( compileTaskName, buildDirPropertyArg, jarDirectoryArg );
}
@BeforeAll
public static void setupClass() throws Exception {
rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath();
}
public void setup(String gradleVersion) throws IOException {
if ( !testBuildDir.exists() ) {
testBuildDir.mkdirs();
}
if ( !testProjectDir.exists() ) {
testProjectDir.mkdirs();
}
// Copy test project files to the temp dir
Path gradleProjectPath = rootPath.resolve( projectDir );
FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir );
compileArgs = buildCompileArgs();
sourceDirectory = new File( testProjectDir, "src/main/java" );
runner = GradleRunner.create().withGradleVersion( gradleVersion ).withProjectDir( testProjectDir );
}
@ParameterizedTest
@MethodSource("gradleVersions")
public void testBuildSucceeds(String gradleVersion) throws IOException {
setup( gradleVersion );
// Make sure the test build setup actually compiles
BuildResult buildResult = getRunner().build();
assertCompileOutcome( buildResult, SUCCESS );
}
@ParameterizedTest
@MethodSource("gradleVersions")
public void testUpToDate(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
BuildResult secondBuildResult = getRunner().build();
assertCompileOutcome( secondBuildResult, UP_TO_DATE );
}
@ParameterizedTest
@MethodSource("gradleVersions")
public void testChangeConstant(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
// Change return value in class Target
File targetFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/model/Target.java" );
replaceInFile( targetFile, "original", "changed" );
BuildResult secondBuildResult = getRunner( "--info" ).build();
// 3 classes should be recompiled: Target -> TestMapper -> TestMapperImpl
assertRecompiled( secondBuildResult, 3 );
}
@ParameterizedTest
@MethodSource("gradleVersions")
public void testChangeTargetField(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
// Change target field in mapper interface
File mapperFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/TestMapper.java" );
replaceInFile( mapperFile, "field", "otherField" );
BuildResult secondBuildResult = getRunner( "--info" ).build();
// 2 classes should be recompiled: TestMapper -> TestMapperImpl
assertRecompiled( secondBuildResult, 2 );
}
@ParameterizedTest
@MethodSource("gradleVersions")
public void testChangeUnrelatedFile(String gradleVersion) throws IOException {
setup( gradleVersion );
getRunner().build();
File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" );
replaceInFile( unrelatedFile, "true", "false" );
BuildResult secondBuildResult = getRunner( "--info" ).build();
// Only the UnrelatedComponent class should be recompiled
assertRecompiled( secondBuildResult, 1 );
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Filip Hrisafov
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "immutablesBuilderTest",
processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN)
public class ImmutablesBuilderTest {
}

View File

@ -1,20 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Andreas Gudian
*
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "java8Test", processorTypes = ProcessorType.ALL_JAVA_8 )
public class Java8Test {
}

View File

@ -1,20 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Andreas Gudian
*
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "jaxbTest", processorTypes = ProcessorType.ALL )
public class JaxbTest {
}

View File

@ -1,20 +0,0 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.itest.tests;
import org.junit.runner.RunWith;
import org.mapstruct.itest.testutil.runner.ProcessorSuite;
import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType;
import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner;
/**
* @author Andreas Gudian
*
*/
@RunWith( ProcessorSuiteRunner.class )
@ProcessorSuite( baseDir = "jsr330Test", processorTypes = ProcessorType.ALL )
public class Jsr330Test {
}

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