From 8f962919117c30c95165e5349314c0adea455d8f Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sat, 18 Jan 2025 19:04:42 +0700 Subject: [PATCH 001/125] Fix documentation typo and code polish (#3787) --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- .../src/main/asciidoc/chapter-6-mapping-collections.asciidoc | 4 ++-- .../java/org/mapstruct/ap/spi/util/IntrospectorUtils.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ed64580eb4..ea574603ea 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -302,7 +302,7 @@ If you don't want explicitly name all properties from nested source bean, you ca 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 explicitely 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. +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`). diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 17025da4de..79d4544f9b 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -192,7 +192,7 @@ The option `DEFAULT` should not be used explicitly. It is used to distinguish be [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 intialized collections instead of Mapstruct creating the target entity by its constructor. +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]] @@ -224,4 +224,4 @@ When an iterable or map mapping method declares an interface type as return type |`ConcurrentMap`|`ConcurrentHashMap` |`ConcurrentNavigableMap`|`ConcurrentSkipListMap` -|=== +|=== \ No newline at end of file diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java index fccc22b38b..95dcd7a92d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -32,7 +32,7 @@ private IntrospectorUtils() { * @return The decapitalized version of the string. */ public static String decapitalize(String name) { - if ( name == null || name.length() == 0 ) { + if ( name == null || name.isEmpty() ) { return name; } if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && From e0a7d3d0e62695591eef532f63874729c56f64b7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 17 Jan 2025 18:01:47 +0100 Subject: [PATCH 002/125] Use latest Maven Wrapper --- .mvn/wrapper/MavenWrapperDownloader.java | 117 ------ .mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .mvn/wrapper/maven-wrapper.properties | 21 +- mvnw | 451 ++++++++++------------- mvnw.cmd | 281 +++++++------- 5 files changed, 343 insertions(+), 527 deletions(-) delete mode 100644 .mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 .mvn/wrapper/maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index b901097f2d..0000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed 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. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index abd303b673..d58dfb70ba 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,19 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar +# 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 diff --git a/mvnw b/mvnw index 41c0f0c23d..19529ddf8c 100755 --- a/mvnw +++ b/mvnw @@ -19,292 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac -fi +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 fi fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" +} - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done + printf %x\\n $h +} - saveddir=`pwd` +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - M2_HOME=`dirname "$PRG"`/.. +die() { + printf %s\\n "$1" >&2 + exit 1 +} - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" fi -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; fi -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 86115719e5..249bdf3822 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -18,165 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From 57d4f88a48f6dc95f02ecbd9d19b6c396be2444f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 20 Jan 2025 07:56:19 +0100 Subject: [PATCH 003/125] 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 --- .github/workflows/java-ea.yml | 6 +-- .github/workflows/main.yml | 43 ++++++++----------- .github/workflows/release.yml | 2 +- ...eatureCompilationExclusionCliEnhancer.java | 3 ++ .../itest/tests/MavenIntegrationTest.java | 8 ++++ .../ProcessorInvocationInterceptor.java | 10 ++++- .../test/resources/fullFeatureTest/pom.xml | 10 +++++ .../superTypeGenerationTest/generator/pom.xml | 1 - .../superTypeGenerationTest/usage/pom.xml | 1 - .../generator/pom.xml | 1 - .../targetTypeGenerationTest/usage/pom.xml | 1 - parent/pom.xml | 6 +-- processor/pom.xml | 2 +- .../_1395/{ => spring}/Issue1395Mapper.java | 2 +- .../_1395/{ => spring}/Issue1395Test.java | 2 +- .../_1395/{ => spring}/NotUsedService.java | 2 +- .../test/bugs/_1395/{ => spring}/Source.java | 2 +- .../test/bugs/_1395/{ => spring}/Target.java | 2 +- .../_2807/{ => spring}/Issue2807Test.java | 8 ++-- .../{ => spring}/SpringLifeCycleMapper.java | 8 ++-- .../_2807/{ => spring}/after/AfterMethod.java | 2 +- .../{ => spring}/before/BeforeMethod.java | 2 +- .../beforewithtarget/BeforeWithTarget.java | 2 +- .../test/bugs/_880/{ => spring}/Config.java | 2 +- .../DefaultsToProcessorOptionsMapper.java | 2 +- .../bugs/_880/{ => spring}/Issue880Test.java | 2 +- .../test/bugs/_880/{ => spring}/Poodle.java | 2 +- .../UsesConfigFromAnnotationMapper.java | 2 +- .../decorator/jsr330/Jsr330DecoratorTest.java | 3 ++ ...30DefaultCompileOptionFieldMapperTest.java | 3 ++ ...330CompileOptionConstructorMapperTest.java | 3 ++ .../Jsr330ConstructorMapperTest.java | 3 ++ .../jsr330/field/Jsr330FieldMapperTest.java | 3 ++ .../jsr330/setter/Jsr330SetterMapperTest.java | 3 ++ .../org/mapstruct/ap/testutil/WithSpring.java | 3 ++ readme.md | 2 +- 36 files changed, 96 insertions(+), 63 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Mapper.java (91%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Test.java (92%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/NotUsedService.java (81%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Source.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Target.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/Issue2807Test.java (69%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/SpringLifeCycleMapper.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/after/AfterMethod.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/before/BeforeMethod.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/beforewithtarget/BeforeWithTarget.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Config.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/DefaultsToProcessorOptionsMapper.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Issue880Test.java (97%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Poodle.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/UsesConfigFromAnnotationMapper.java (90%) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index d9b018bfb8..fce2dc06b2 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -7,11 +7,7 @@ env: jobs: test_jdk_ea: - strategy: - fail-fast: false - matrix: - java: [19-ea] - name: 'Linux JDK ${{ matrix.java }}' + name: 'Linux JDK EA' runs-on: ubuntu-latest steps: - name: 'Checkout' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b27dfc075a..9cc098161d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,45 +24,38 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true - linux: - name: 'Linux JDK 11' - runs-on: ubuntu-latest - steps: - - name: 'Checkout' - uses: actions/checkout@v3 - - name: 'Set up JDK 11' - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 11 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} - name: 'Generate coverage report' + if: matrix.java == 17 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' + if: matrix.java == 17 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + if: matrix.java == 17 && 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 - linux-jdk-8: - name: 'Linux JDK 8' + integration_test_jdk: + strategy: + fail-fast: false + matrix: + java: [ 8, 11 ] + name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v3 - - name: 'Set up JDK 11 for building everything' + - name: 'Set up JDK 17 for building everything' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - - name: 'Set up JDK 8 for running integration tests' + - name: 'Set up JDK ${{ matrix.java }} for running integration tests' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 8 + java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest windows: @@ -70,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -82,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f18a12b813..682fa36876 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' cache: maven diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 15d9e42598..77a5e2af06 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -30,6 +30,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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" ); @@ -42,6 +43,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); break; + case JAVA_11: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); default: } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 0bef2994f6..ab43cc4cba 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.itest.tests; +import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.parallel.Execution; @@ -80,12 +81,15 @@ void jakartaJaxbTest() { } @ProcessorTest(baseDir = "jsr330Test") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void jsr330Test() { } @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) + @DisabledOnJre(JRE.OTHER) void lombokBuilderTest() { } @@ -94,6 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) + @DisabledOnJre(JRE.OTHER) void lombokModuleTest() { } @@ -150,6 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 + @DisabledOnJre(JRE.OTHER) void kotlinDataTest() { } @@ -163,6 +169,8 @@ void defaultPackageTest() { } @ProcessorTest(baseDir = "springTest") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void springTest() { } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 89009aaffe..85ce64952f 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -134,14 +134,20 @@ private void addAdditionalCliArguments(Verifier verifier) } private void configureProcessor(Verifier verifier) { - String compilerId = processorTestContext.getProcessor().getCompilerId(); + ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); + String compilerId = processor.getCompilerId(); if ( compilerId != null ) { - String profile = processorTestContext.getProcessor().getProfile(); + String profile = processor.getProfile(); if ( profile == null ) { profile = "generate-via-compiler-plugin"; } verifier.addCliOption( "-P" + profile ); verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); + } + } } else { verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 1a31b28221..849f7150fc 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -27,6 +27,11 @@ x x x + x + x + x + x + x @@ -49,6 +54,11 @@ ${additionalExclude4} ${additionalExclude5} ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 1b84638ef2..5ab2d0d18f 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index ee0d556b9f..d1e1dd7dff 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index bf0d704851..67df383a18 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index da72f667a4..bd06b79a49 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..5509e783d2 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 5.3.31 + 6.2.2 1.6.0 8.36.1 5.10.1 @@ -47,7 +47,7 @@ jdt_apt 1.8 3.21.7 @@ -400,7 +400,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 org.apache.maven.plugins diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..13deafb413 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 11 + 17 diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java index ebc9ae27e0..4e48020c8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java similarity index 92% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java index 042d0288cd..3417fbd707 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java similarity index 81% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java index 765764ef2b..ba47cf179f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java index 1b676c2b62..8f2a52d462 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java index 2c7b22fd4d..1ba393dbe0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java similarity index 69% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java index 2cb96ce37e..102a9d26eb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java @@ -3,11 +3,11 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java index daabe7f52a..408a2ca3e0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java @@ -3,15 +3,15 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.factory.Mappers; /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java index f7c7457348..05770c6603 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.after; +package org.mapstruct.ap.test.bugs._2807.spring.after; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java index 5252bee150..9fb9e7e883 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.before; +package org.mapstruct.ap.test.bugs._2807.spring.before; import org.mapstruct.BeforeMapping; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java index 69e62bdea1..a3ee8b57ea 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.beforewithtarget; +package org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java index 32f58384b9..7aef1e9e1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.MapperConfig; import org.mapstruct.ReportingPolicy; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java index c54da3f7cb..5f67b41552 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java similarity index 97% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java index 9d8540b409..d2f371abed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import javax.tools.Diagnostic.Kind; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java index e822db1cfe..71166f6d4c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; /** * @author Andreas Gudian diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java index d9a1efe33e..bb9a7def9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index a3bad68b92..be1a67cf09 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.decorator.Address; import org.mapstruct.ap.test.decorator.AddressDto; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330DecoratorTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java index 6b92d9815a..192059c8a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330DefaultCompileOptionFieldMapper.class) @WithJavaxInject @Configuration +@DisabledOnJre(JRE.OTHER) public class Jsr330DefaultCompileOptionFieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java index fe9cf295f4..02b634da8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330CompileOptionConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330CompileOptionConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index a5cfce5c2e..d51843f00c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330ConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index 84e67f63a8..d713f9cc88 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330FieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java index 558206e51c..a6f18ed594 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -42,6 +44,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330SetterMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330SetterMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java index 40db5afd81..04e387d429 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java @@ -10,6 +10,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; /** * Meta annotation that adds the needed Spring Dependencies @@ -23,6 +25,7 @@ "spring-beans", "spring-context" }) +@DisabledOnJre(JRE.OTHER) public @interface WithSpring { } diff --git a/readme.md b/readme.md index adb48b2fd4..907c8411cd 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run ./mvnw clean install From c08ba4ca7e2832ad398a61b6a5a181ebaecd4e0d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 22 Jan 2025 08:51:49 +0100 Subject: [PATCH 004/125] Update setup-java and checkout actions to v4 (#3804) --- .github/workflows/java-ea.yml | 2 +- .github/workflows/main.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fce2dc06b2..fc3a4a0fb1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' uses: oracle-actions/setup-java@v1 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cc098161d..a760175c0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -43,16 +43,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK 17 for building everything' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 - 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@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -62,9 +62,9 @@ jobs: name: 'Windows' runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -76,7 +76,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 From 39551242d7cfbab7e6bbadfed8c771b483cc1a13 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:41:35 +0100 Subject: [PATCH 005/125] #3786: Improve error message when mapping non-iterable to array --- .../processor/MethodRetrievalProcessor.java | 5 +++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3786/ErroneousByteArrayMapper.java | 13 +++++++ .../ap/test/bugs/_3786/Issue3786Test.java | 36 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 2c8ad5dcb9..c8c13e6bf9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -555,6 +555,11 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List Date: Sat, 10 May 2025 14:12:20 +0200 Subject: [PATCH 006/125] #3815: chore(docs): Improved wording about @Condition usage --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 1e2bd133d4..b5c116ffd3 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -308,7 +308,7 @@ Conditional mapping can also be used to check if a source parameter should be ma 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 it is not empty then you can do something like: +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 ==== From 668eeb5de1b1dc79cbf90e68cdf38fbba03c98a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:25:00 +0000 Subject: [PATCH 007/125] 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] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 5509e783d2..5735d4760c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -50,7 +50,7 @@ The processor module needs at least Java 17. --> 1.8 - 3.21.7 + 3.25.5 2.3.2 From 2c84d04463a3ec2edd6c05e726a579b4ba936847 Mon Sep 17 00:00:00 2001 From: Cause Chung Date: Sun, 11 May 2025 17:33:35 +0200 Subject: [PATCH 008/125] #3240 Add Support for Java21 SequencedSet and SequencedMap --- .github/workflows/main.yml | 24 +++--- .github/workflows/release.yml | 2 +- .../chapter-6-mapping-collections.asciidoc | 4 + ...eatureCompilationExclusionCliEnhancer.java | 7 ++ .../test/resources/fullFeatureTest/pom.xml | 2 + parent/pom.xml | 2 +- processor/pom.xml | 2 +- .../ap/internal/model/common/TypeFactory.java | 13 ++++ .../util/JavaCollectionConstants.java | 21 ++++++ ...dCollectionsDefaultImplementationTest.java | 74 +++++++++++++++++++ .../jdk21/SequencedCollectionsMapper.java | 26 +++++++ readme.md | 3 +- 12 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a760175c0e..166466348d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17, 21] + java: [21] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -24,31 +24,31 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' - if: matrix.java == 17 + if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 17 + if: matrix.java == 21 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: matrix.java == 17 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + 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 ] + java: [ 8, 11, 17 ] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v4 - - name: 'Set up JDK 17 for building everything' + - name: 'Set up JDK 21 for building everything' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + 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' @@ -63,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v4 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -75,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682fa36876..61f347af3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'zulu' cache: maven diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 79d4544f9b..4510c82cc0 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -212,12 +212,16 @@ When an iterable or map mapping method declares an interface type as return type |`Set`|`LinkedHashSet` +|`SequencedSet`|`LinkedHashSet` + |`SortedSet`|`TreeSet` |`NavigableSet`|`TreeSet` |`Map`|`LinkedHashMap` +|`SequencedMap`|`LinkedHashMap` + |`SortedMap`|`TreeMap` |`NavigableMap`|`TreeMap` diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 77a5e2af06..0cc1a87818 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -34,6 +34,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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" ); @@ -42,9 +43,15 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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: } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 849f7150fc..b5ddae9a79 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -59,6 +59,8 @@ ${additionalExclude9} ${additionalExclude10} ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} diff --git a/parent/pom.xml b/parent/pom.xml index 5735d4760c..f6ad60a22a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -47,7 +47,7 @@ jdt_apt 1.8 3.25.5 diff --git a/processor/pom.xml b/processor/pom.xml index 13deafb413..2341c4ddb0 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 17 + 21 diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index ceb190a332..c2646f63db 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -45,6 +45,7 @@ import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Extractor; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.JavaCollectionConstants; import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.NativeTypes; @@ -149,6 +150,18 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes ConcurrentNavigableMap.class.getName(), withDefaultConstructor( getType( ConcurrentSkipListMap.class ) ) ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_SET_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_MAP_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); this.loggingVerbose = loggingVerbose; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java new file mode 100644 index 0000000000..68d556c542 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util; + +/** + * Helper holding Java collections full qualified class names for conversion registration, + * to achieve Java compatibility. + * + * @author Cause Chung + */ +public final class JavaCollectionConstants { + public static final String SEQUENCED_MAP_FQN = "java.util.SequencedMap"; + public static final String SEQUENCED_SET_FQN = "java.util.SequencedSet"; + + private JavaCollectionConstants() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java new file mode 100644 index 0000000000..4551d5b0b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java @@ -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.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.ap.test.collection.defaultimplementation.Source; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.Target; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceFoo.class, + TargetFoo.class, + SequencedCollectionsMapper.class +}) +@IssueKey("3420") +class SequencedCollectionsDefaultImplementationTest { + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedMap() { + SequencedMap target = + SequencedCollectionsMapper.INSTANCE.sourceFooMapToTargetFooSequencedMap( createSourceFooMap() ); + + assertResultMap( target ); + } + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedSet() { + SequencedSet target = + SequencedCollectionsMapper.INSTANCE.sourceFoosToTargetFooSequencedSet( createSourceFooList() ); + + assertResultList( target ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private void assertResultMap(Map result) { + assertThat( result ).isNotNull(); + assertThat( result ).hasSize( 2 ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + } + + private Map createSourceFooMap() { + Map map = new HashMap<>(); + map.put( 1L, new SourceFoo( "Bob" ) ); + map.put( 2L, new SourceFoo( "Alice" ) ); + + return map; + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java new file mode 100644 index 0000000000..bbffb56b0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java @@ -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.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Collection; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SequencedCollectionsMapper { + + SequencedCollectionsMapper INSTANCE = Mappers.getMapper( SequencedCollectionsMapper.class ); + + SequencedSet sourceFoosToTargetFooSequencedSet(Collection foos); + + SequencedMap sourceFooMapToTargetFooSequencedMap(Map foos); +} diff --git a/readme.md b/readme.md index 907c8411cd..16a70f3f0b 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,8 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 21 is required for building MapStruct from source. +To build the complete project, run ./mvnw clean install From 602e29083fe980be0256fd01d37906ceab1ff6ca Mon Sep 17 00:00:00 2001 From: zral <73640367+zyberzebra@users.noreply.github.com> Date: Sun, 11 May 2025 19:59:59 +0200 Subject: [PATCH 009/125] #1140 Add warning when target has no properties --- .../ap/internal/model/BeanMappingMethod.java | 78 ++++++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../test/annotatewith/AnnotateWithTest.java | 5 +- .../ErroneousMapperWithClassOnMethod.java | 10 +-- .../ap/test/bugs/_1111/Issue1111Mapper.java | 25 +++++- .../ap/test/bugs/_1111/Issue1111Test.java | 2 +- .../ap/test/bugs/_1130/Issue1130Mapper.java | 19 +++++ ...roneousIssue1242MapperMultipleSources.java | 3 +- .../ap/test/bugs/_1242/Issue1242Mapper.java | 3 +- .../ap/test/bugs/_1242/Issue1242Test.java | 2 +- .../mapstruct/ap/test/bugs/_1242/TargetB.java | 9 +++ .../ap/test/bugs/_1345/Issue1345Mapper.java | 18 +++++ .../ap/test/bugs/_1460/Issue1460Mapper.java | 21 ++++- .../_1460/java8/Issue1460JavaTimeMapper.java | 11 ++- .../mapstruct/ap/test/bugs/_1590/Book.java | 10 +++ .../ap/test/bugs/_1590/Issue1590Test.java | 2 +- .../org/mapstruct/ap/test/bugs/_1650/C.java | 10 +++ .../mapstruct/ap/test/bugs/_1650/CPrime.java | 10 +++ .../ap/test/bugs/_1650/Issue1650Test.java | 2 +- .../ap/test/bugs/_1821/Issue1821Mapper.java | 20 +++++ .../ap/test/bugs/_1821/Issue1821Test.java | 2 +- .../ap/test/bugs/_611/Issue611Test.java | 4 +- .../ap/test/bugs/_611/SomeClass.java | 40 ++++++++++ .../ap/test/bugs/_611/SomeOtherClass.java | 20 +++++ .../simple/BuilderInfoTargetTest.java | 16 +++- .../simple/SimpleBuilderMapper.java | 3 - .../simple/SimpleImmutableUpdateMapper.java | 19 +++++ .../immutabletarget/ImmutableProductTest.java | 10 +++ .../ap/test/collection/wildcard/Idea.java | 9 +++ .../ap/test/collection/wildcard/Plan.java | 9 +++ .../visibility/ConstructorVisibilityTest.java | 12 +++ .../AbstractDestinationClassNameMapper.java | 2 +- .../AbstractDestinationPackageNameMapper.java | 2 +- .../DestinationClassNameMapper.java | 2 +- .../DestinationClassNameMapperDecorated.java | 2 +- .../DestinationClassNameMapperDecorator.java | 4 +- .../DestinationClassNameMapperWithConfig.java | 2 +- ...tionClassNameMapperWithConfigOverride.java | 2 +- .../destination/DestinationClassNameTest.java | 1 + .../DestinationClassNameWithJsr330Mapper.java | 2 +- .../DestinationPackageNameMapper.java | 2 +- ...DestinationPackageNameMapperDecorated.java | 2 +- ...DestinationPackageNameMapperDecorator.java | 4 +- ...estinationPackageNameMapperWithConfig.java | 2 +- ...onPackageNameMapperWithConfigOverride.java | 2 +- ...estinationPackageNameMapperWithSuffix.java | 2 +- .../DestinationPackageNameTest.java | 1 + .../mapstruct/ap/test/destination/Target.java | 19 +++++ .../ap/test/emptytarget/EmptyTarget.java | 9 +++ .../test/emptytarget/EmptyTargetMapper.java | 18 +++++ .../ap/test/emptytarget/EmptyTargetTest.java | 39 ++++++++++ .../mapstruct/ap/test/emptytarget/Source.java | 36 +++++++++ .../mapstruct/ap/test/emptytarget/Target.java | 34 ++++++++ .../test/emptytarget/TargetWithNoSetters.java | 15 ++++ .../ap/test/imports/SourceTargetMapper.java | 10 ++- .../ap/test/java8stream/wildcard/Idea.java | 9 +++ .../ap/test/java8stream/wildcard/Plan.java | 9 +++ .../ErroneousSourceTargetMapper.java | 16 +++- .../MissingIgnoredSourceTest.java | 2 +- .../objectfactory/ObjectFactoryMapper.java | 13 +++- .../fixture/AbstractParentSource.java | 9 +++ .../fixture/AbstractParentTarget.java | 9 +++ .../fixture/ImplementedParentSource.java | 9 +++ .../fixture/ImplementedParentTarget.java | 9 +++ .../ap/test/versioninfo/SimpleMapper.java | 3 +- .../SuppressTimestampViaMapper.java | 3 +- .../SuppressTimestampViaMapperConfig.java | 3 +- .../ap/test/versioninfo/VersionInfoTest.java | 3 +- .../fixture/SubclassAbstractMapperImpl.java | 13 ++++ .../SubclassImplementedMapperImpl.java | 8 ++ .../fixture/SubclassInterfaceMapperImpl.java | 5 ++ 71 files changed, 657 insertions(+), 85 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/destination/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 4ff2e8079f..08c8ceda59 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -235,9 +235,10 @@ else if ( !method.isUpdateMethod() ) { this.unprocessedTargetProperties = new LinkedHashMap<>( accessors ); + boolean constructorAccessorHadError = false; if ( !method.isUpdateMethod() && !hasFactoryMethod ) { ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap ); - if ( constructorAccessor != null ) { + if ( constructorAccessor != null && !constructorAccessor.hasError ) { this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors; @@ -250,8 +251,10 @@ else if ( !method.isUpdateMethod() ) { else { this.unprocessedConstructorProperties = new LinkedHashMap<>(); } + constructorAccessorHadError = constructorAccessor != null && constructorAccessor.hasError; this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() ); + this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties ); } else { @@ -322,7 +325,7 @@ else if ( !method.isUpdateMethod() ) { // report errors on unmapped properties if ( shouldHandledDefinedMappings ) { - reportErrorForUnmappedTargetPropertiesIfRequired(); + reportErrorForUnmappedTargetPropertiesIfRequired( resultTypeToMap, constructorAccessorHadError ); reportErrorForUnmappedSourcePropertiesIfRequired(); } reportErrorForMissingIgnoredSourceProperties(); @@ -964,7 +967,7 @@ private ConstructorAccessor getConstructorAccessor(Type type) { ) .collect( Collectors.joining( ", " ) ) ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { return getConstructorAccessor( type, accessibleConstructors.get( 0 ) ); @@ -1023,7 +1026,7 @@ else if ( constructorProperties.size() != constructorParameters.size() ) { GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS, type ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { Map constructorAccessors = new LinkedHashMap<>(); @@ -1731,36 +1734,45 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { return method.getOptions().getMapper().unmappedTargetPolicy(); } - private void reportErrorForUnmappedTargetPropertiesIfRequired() { + private void reportErrorForUnmappedTargetPropertiesIfRequired(Type resultType, + boolean constructorAccessorHadError) { // fetch settings from element to implement ReportingPolicyGem unmappedTargetPolicy = getUnmappedTargetPolicy(); - if ( method instanceof ForgedMethod && targetProperties.isEmpty() ) { - //TODO until we solve 1140 we report this error when the target properties are empty - ForgedMethod forgedMethod = (ForgedMethod) method; - if ( forgedMethod.getHistory() == null ) { - Type sourceType = this.method.getParameters().get( 0 ).getType(); - Type targetType = this.method.getReturnType(); - ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, - sourceType.describe(), - targetType.describe(), - targetType.describe(), - sourceType.describe() - ); + if ( targetProperties.isEmpty() ) { + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( forgedMethod.getHistory() == null ) { + Type sourceType = this.method.getParameters().get( 0 ).getType(); + Type targetType = this.method.getReturnType(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, + sourceType.describe(), + targetType.describe(), + targetType.describe(), + sourceType.describe() + ); + } + else { + ForgedMethodHistory history = forgedMethod.getHistory(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, + history.createSourcePropertyErrorMessage(), + history.getTargetType().describe(), + history.createTargetPropertyName(), + history.getTargetType().describe(), + history.getSourceType().describe() + ); + } } - else { - ForgedMethodHistory history = forgedMethod.getHistory(); + else if ( !constructorAccessorHadError ) { ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, - history.createSourcePropertyErrorMessage(), - history.getTargetType().describe(), - history.createTargetPropertyName(), - history.getTargetType().describe(), - history.getSourceType().describe() + method.getExecutable(), + Message.PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES, + resultType.describe() ); } } @@ -1780,7 +1792,8 @@ else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.require reportErrorForUnmappedProperties( unprocessedTargetProperties, unmappedPropertiesMsg, - unmappedForgedPropertiesMsg ); + unmappedForgedPropertiesMsg + ); } } @@ -1907,12 +1920,19 @@ private void reportErrorForUnusedSourceParameters() { } private static class ConstructorAccessor { + private final boolean hasError; private final List parameterBindings; private final Map constructorAccessors; private ConstructorAccessor( List parameterBindings, Map constructorAccessors) { + this( false, parameterBindings, constructorAccessors ); + } + + private ConstructorAccessor(boolean hasError, List parameterBindings, + Map constructorAccessors) { + this.hasError = hasError; this.parameterBindings = parameterBindings; this.constructorAccessors = constructorAccessors; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 221aed2859..883b3e3792 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -92,6 +92,7 @@ public enum Message { PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ), + PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES( "No target property found for target \"%s\".", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index 3687fe2bc0..eaa8832df7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -176,12 +177,12 @@ public void erroneousMapperWithAnnotationOnlyOnClass() { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousMapperWithClassOnMethod.class, - line = 17, + line = 18, message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods." ) } ) - @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class }) + @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class, WithProperties.class }) public void erroneousMapperWithClassOnMethod() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java index d3d53c910a..c34bce304f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java @@ -7,6 +7,7 @@ import org.mapstruct.AnnotateWith; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Ben Zegveld @@ -15,13 +16,6 @@ public interface ErroneousMapperWithClassOnMethod { @AnnotateWith( value = CustomClassOnlyAnnotation.class ) - Target toString(Source value); + WithProperties toString(String string); - class Source { - - } - - class Target { - - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java index a5a624e684..7cdfa82316 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java @@ -24,8 +24,29 @@ public interface Issue1111Mapper { List> listList(List> in); - class Source { } + class Source { + private final String value; - class Target { } + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java index 5619b64175..f5efb21e3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java @@ -27,7 +27,7 @@ public class Issue1111Test { @ProcessorTest public void shouldCompile() { - List> source = Arrays.asList( Arrays.asList( new Source() ) ); + List> source = Arrays.asList( Arrays.asList( new Source( "test" ) ) ); List> target = Issue1111Mapper.INSTANCE.listList( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java index 042d5a2f8b..350d95eebc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java @@ -30,6 +30,16 @@ public void setB(BEntity b) { } static class BEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } static class ADto { @@ -46,6 +56,7 @@ public void setB(BDto b) { class BDto { private final String passedViaConstructor; + private String id; BDto(String passedViaConstructor) { this.passedViaConstructor = passedViaConstructor; @@ -54,6 +65,14 @@ class BDto { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } abstract void mergeA(AEntity source, @MappingTarget ADto target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java index 7259d343b8..7a8ad05542 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; /** * Results in an ambiguous factory method error, as there are two methods with matching source types available. * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class ErroneousIssue1242MapperMultipleSources { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java index 0491b5ffe0..90ea60a2ee 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; /** * Test mapper for properly resolving the best fitting factory method * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class Issue1242Mapper { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java index f27051670c..036a63f312 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java @@ -54,7 +54,7 @@ public void factoryMethodWithSourceParamIsChosen() { diagnostics = { @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, + line = 21, message = "Ambiguous factory methods found for creating TargetB: " + "TargetB anotherTargetBCreator(SourceB source), " + "TargetB TargetFactories.createTargetB(SourceB source, @TargetType Class clazz), " + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java index ab4f639697..6a28adc828 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java @@ -9,6 +9,7 @@ * @author Andreas Gudian */ class TargetB { + protected String value; private final String passedViaConstructor; TargetB(String passedViaConstructor) { @@ -18,4 +19,12 @@ class TargetB { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java index 7699a8f7e9..b2d89c96e6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java @@ -27,8 +27,17 @@ public interface Issue1345Mapper { class A { + private String value; private String readOnlyProperty; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getReadOnlyProperty() { return readOnlyProperty; } @@ -36,8 +45,17 @@ public String getReadOnlyProperty() { class B { + private String value; private String property; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getProperty() { return property; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java index 90ed83d28a..1d0d4fccbb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java @@ -18,13 +18,13 @@ public abstract class Issue1460Mapper { public abstract Target map(Source source); - public abstract String forceUsageOfIssue1460Enum(Issue1460Enum source); + public abstract Value forceUsageOfIssue1460Enum(Issue1460Enum source); - public abstract String forceUsageOfLocale(Locale source); + public abstract Value forceUsageOfLocale(Locale source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract Value forceUsageOfLocalDate(LocalDate source); - public abstract String forceUsageOfDateTime(DateTime source); + public abstract Value forceUsageOfDateTime(DateTime source); public static class Issue1460Enum { } @@ -37,4 +37,17 @@ public static class LocalDate { public static class DateTime { } + + public static class Value { + + private final T source; + + public Value(T source) { + this.source = source; + } + + public T getSource() { + return source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java index 216cda21fd..b9bedfe451 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java @@ -18,8 +18,17 @@ public abstract class Issue1460JavaTimeMapper { public abstract Target map(Source source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract LocalTarget forceUsageOfLocalDate(LocalDate source); public static class LocalDate { } + + public static class LocalTarget { + + private final LocalDate source; + + public LocalTarget(LocalDate source) { + this.source = source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java index 53355ee603..bfe79c1772 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1590; public class Book { + + private final String name; + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java index d77add25de..0a9113797c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java @@ -28,7 +28,7 @@ public class Issue1590Test { @ProcessorTest public void shouldTestMappingLocalDates() { BookShelf source = new BookShelf(); - source.setBooks( Arrays.asList( new Book() ) ); + source.setBooks( Arrays.asList( new Book("Test") ) ); BookShelf target = BookShelfMapper.INSTANCE.map( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java index 705f2e2d69..dbbd74ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class C { + + private final int value; + + public C(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java index 24e4376f88..db504189f8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class CPrime { + + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java index 2d1437eb77..33237e726c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java @@ -27,7 +27,7 @@ public void shouldCompile() { A a = new A(); a.setB( new B() ); - a.getB().setC( new C() ); + a.getB().setC( new C( 10 ) ); APrime aPrime = new APrime(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java index 08eccae32e..406fa7b71b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java @@ -22,11 +22,31 @@ public interface Issue1821Mapper { ExtendedTarget mapExtended(Source source); class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class ExtendedTarget extends Target { } class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java index 4481ed43e2..865ef8f6c1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java @@ -15,7 +15,7 @@ public class Issue1821Test { @ProcessorTest public void shouldNotGiveNullPtr() { - Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source() ); + Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source( "test" ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java index 79a6e045ec..b77da90ad9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java @@ -43,8 +43,8 @@ public void mapperNestedInsideNestedClassIsFound() { */ @ProcessorTest public void rightMapperIsFound() { - SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source(); - SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source(); + SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source( "test" ); + SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source( "test2" ); SomeClass.InnerMapper.Target target1 = SomeClass.InnerMapper.INSTANCE.toTarget( source1 ); SomeOtherClass.InnerMapper.Target target2 = SomeOtherClass.InnerMapper.INSTANCE.toTarget( source2 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java index 0d41128711..e0fb59d4d7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } @@ -33,9 +53,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java index e50187a82a..2ff2d0d831 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java index 4f3d8b85f4..5d0ff919ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java @@ -9,6 +9,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +83,17 @@ public void testUpdateMutableWithBuilder() { } @ProcessorTest + @WithClasses( { + SimpleImmutableUpdateMapper.class + } ) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleImmutableUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 18, + message = "No target property found for target \"SimpleImmutableTarget\"."), + }) + public void updatingTargetWithNoSettersShouldNotFail() { SimpleMutableSource source = new SimpleMutableSource(); @@ -90,7 +104,7 @@ public void updatingTargetWithNoSettersShouldNotFail() { .build(); assertThat( target.getAge() ).isEqualTo( 20 ); - SimpleBuilderMapper.INSTANCE.toImmutable( source, target ); + SimpleImmutableUpdateMapper.INSTANCE.toImmutable( source, target ); assertThat( target.getAge() ).isEqualTo( 20 ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java index 9f7187c634..6185daee97 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java @@ -21,9 +21,6 @@ public interface SimpleBuilderMapper { @Mapping(target = "builder.name", source = "source.fullName") void updateImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); - // This method is fine as if the mapping target has setters it would use them, otherwise it won't - void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); - @Mapping(target = "name", source = "fullName") SimpleImmutableTarget toImmutable(SimpleMutableSource source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java new file mode 100644 index 0000000000..c6eccc96c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.mappingTarget.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleImmutableUpdateMapper { + + SimpleImmutableUpdateMapper INSTANCE = Mappers.getMapper( SimpleImmutableUpdateMapper.class ); + + // This method is fine as if the mapping target has setters it would use them, otherwise it won't + void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java index de22436296..8097dfdfe9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -11,6 +11,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +44,13 @@ public void shouldHandleImmutableTarget() { CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = CupboardNoSetterMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "No target property found for target \"CupboardEntityOnlyGetter\"."), + }) public void shouldIgnoreImmutableTarget() { CupboardDto in = new CupboardDto(); in.setContent( Arrays.asList( "flour", "peas" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java index fc9b89b571..4e21b223db 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java index 29fcfc2f3a..00db02170c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java index b7a254296d..5eebaf5d05 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java @@ -10,6 +10,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +47,15 @@ public void shouldUseSinglePublicConstructorAlways() { @WithClasses({ SimpleWithPublicParameterlessConstructorMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleWithPublicParameterlessConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "No target property found for target " + + "\"SimpleWithPublicParameterlessConstructorMapper.Person\"."), + }) + public void shouldUsePublicParameterConstructorIfPresent() { PersonDto source = new PersonDto(); source.setName( "Bob" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java index 9498b832f7..8dd010cae3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationClassNameMapper { public static final AbstractDestinationClassNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationClassNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java index b59d5cd21b..e26a6bbe71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationPackageNameMapper { public static final AbstractDestinationPackageNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationPackageNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java index a331096a63..99bd8915a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapper { DestinationClassNameMapper INSTANCE = Mappers.getMapper( DestinationClassNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java index ef44767379..454e5e15bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationClassNameMapperDecorated { DestinationClassNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationClassNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java index 216786da24..3ba1433e21 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationClassNameMapperDecorator(DestinationClassNameMapperDecorate } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java index d76392d11d..d13675f0ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapperWithConfig { DestinationClassNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java index b218fff158..a3f845462b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationClassNameMapperWithConfigOverride { DestinationClassNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java index 30611a8d60..1f6e255b95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java @@ -16,6 +16,7 @@ /** * @author Christophe Labouisse on 27/05/2015. */ +@WithClasses( Target.class ) public class DestinationClassNameTest { @ProcessorTest @WithClasses({ DestinationClassNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java index 92c72a877d..3358f68fd4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java @@ -13,5 +13,5 @@ */ @Mapper(implementationName = "Jsr330Impl", componentModel = MappingConstants.ComponentModel.JSR330) public interface DestinationClassNameWithJsr330Mapper { - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java index 8c4740e323..c049036a13 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapper { DestinationPackageNameMapper INSTANCE = Mappers.getMapper( DestinationPackageNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java index 1dd371b48a..98f6236027 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationPackageNameMapperDecorated { DestinationPackageNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationPackageNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java index 4f6dd3242b..667bd99349 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationPackageNameMapperDecorator(DestinationPackageNameMapperDeco } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java index fbe69ce43e..6a5a870520 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithConfig { DestinationPackageNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java index b53dcaae42..79699006b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationPackageNameMapperWithConfigOverride { DestinationPackageNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java index 2a221e5f5b..068f30331d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithSuffix { DestinationPackageNameMapperWithSuffix INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithSuffix.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java index 63c7ece6d5..5e02c88d93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java @@ -15,6 +15,7 @@ * @author Christophe Labouisse on 27/05/2015. */ @IssueKey( "556" ) +@WithClasses( Target.class ) public class DestinationPackageNameTest { @ProcessorTest @WithClasses({ DestinationPackageNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java new file mode 100644 index 0000000000..5b3bb4ba34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.destination; + +public class Target { + + private final Integer source; + + public Target(Integer source) { + this.source = source; + } + + public Integer getSource() { + return source; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java new file mode 100644 index 0000000000..f1b3a0f073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java @@ -0,0 +1,9 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +public class EmptyTarget { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java new file mode 100644 index 0000000000..8c82f81b7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyTargetMapper { + + TargetWithNoSetters mapToTargetWithSetters(Source source); + + EmptyTarget mapToEmptyTarget(Source source); + + Target mapToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java new file mode 100644 index 0000000000..66fa65b796 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +@IssueKey("1140") +@WithClasses({ + EmptyTarget.class, + EmptyTargetMapper.class, + Source.class, + Target.class, + TargetWithNoSetters.class, +}) +class EmptyTargetTest { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 13, + message = "No target property found for target \"TargetWithNoSetters\"."), + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 15, + message = "No target property found for target \"EmptyTarget\".") + }) + void shouldProvideWarnings() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java new file mode 100644 index 0000000000..643cdccf3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +public class Source { + private String label; + private double weight; + private Object content; + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java new file mode 100644 index 0000000000..488a1fa8a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.emptytarget; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String label; + private final double weight; + private final Object content; + + public Target(String label, double weight, Object content) { + this.label = label; + this.weight = weight; + this.content = content; + } + + public String getLabel() { + return label; + } + + public double getWeight() { + return weight; + } + + public Object getContent() { + return content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java new file mode 100644 index 0000000000..edeb80f0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java @@ -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.ap.test.emptytarget; + +public class TargetWithNoSetters { + private int flightNumber; + private String airplaneName; + + public String getAirplaneName() { + return airplaneName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java index 1380ca400a..845544ba9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java @@ -25,7 +25,7 @@ public interface SourceTargetMapper { ParseException sourceToTarget(Named source); //custom types - Map listToMap(List list); + Value listToMap(List list); java.util.List namedsToExceptions(java.util.List source); @@ -36,4 +36,12 @@ public interface SourceTargetMapper { java.util.Map stringsToDates(java.util.Map stringDates); Target sourceToTarget(Source target); + + class Value { + private T value; + + public Value(List list) { + + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java index bb8434fd63..063a5e5615 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java index 8c55ebc273..7aa366ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java index 659867ca40..1196d773cb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java @@ -8,14 +8,24 @@ import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.factory.Mappers; @Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE) public interface ErroneousSourceTargetMapper { - ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); @BeanMapping(ignoreUnmappedSourceProperties = "bar") - Object sourceToTarget(Object source); + Target map(Target source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java index 77f80c5537..5794126604 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java @@ -22,7 +22,7 @@ public class MissingIgnoredSourceTest { diagnostics = { @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, - line = 20, + line = 18, message = "Ignored unknown source property: \"bar\".") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java index 2ea183bfc9..5ea0a54cb2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java @@ -7,10 +7,11 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; import org.mapstruct.TargetType; import org.mapstruct.factory.Mappers; -@Mapper +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface ObjectFactoryMapper { ObjectFactoryMapper INSTANCE = Mappers.getMapper( ObjectFactoryMapper.class ); @@ -44,6 +45,16 @@ public boolean isA() { } abstract class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class TargetA extends Target { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java index 7f9f19a2ab..295b4e2871 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java @@ -7,4 +7,13 @@ public abstract class AbstractParentSource { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java index 3215aee644..a95f58b260 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java @@ -7,4 +7,13 @@ public abstract class AbstractParentTarget { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java index 94dde481b0..a047ee2573 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java @@ -7,4 +7,13 @@ public class ImplementedParentSource extends AbstractParentSource { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java index 7f3d254e21..a187a87091 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java @@ -7,4 +7,13 @@ public class ImplementedParentTarget extends AbstractParentTarget { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java index 3bcea83364..f40525538c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Andreas Gudian @@ -13,5 +14,5 @@ */ @Mapper public interface SimpleMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java index 02a7b467b1..c3f9d39d26 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java @@ -6,11 +6,12 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(suppressTimestampInGenerated = true) public interface SuppressTimestampViaMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java index a471cad028..54dc6b2fb7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(config = SuppressTimestampViaMapperConfig.Config.class) public interface SuppressTimestampViaMapperConfig { - Object toObject(Object object); + WithProperties toObject(WithProperties object); @MapperConfig(suppressTimestampInGenerated = true) interface Config { diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java index f3192b85ad..60781c399f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -18,7 +19,7 @@ * */ @IssueKey( "424" ) -@WithClasses( SimpleMapper.class ) +@WithClasses( { SimpleMapper.class, WithProperties.class } ) public class VersionInfoTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java index a66b646f1e..b5dd2893c8 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -58,6 +58,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -74,6 +76,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } @@ -88,6 +93,9 @@ protected SubSourceSeparate subTargetSeparateToSubSourceSeparate(SubTargetSepara SubSourceSeparate subSourceSeparate = new SubSourceSeparate( separateValue ); + subSourceSeparate.setParentValue( subTargetSeparate.getParentValue() ); + subSourceSeparate.setImplementedParentValue( subTargetSeparate.getImplementedParentValue() ); + return subSourceSeparate; } @@ -102,6 +110,9 @@ protected SubSourceOverride subTargetOtherToSubSourceOverride(SubTargetOther sub SubSourceOverride subSourceOverride = new SubSourceOverride( finalValue ); + subSourceOverride.setParentValue( subTargetOther.getParentValue() ); + subSourceOverride.setImplementedParentValue( subTargetOther.getImplementedParentValue() ); + return subSourceOverride; } @@ -112,6 +123,8 @@ protected SubSource subTargetToSubSource(SubTarget subTarget) { SubSource subSource = new SubSource(); + subSource.setParentValue( subTarget.getParentValue() ); + subSource.setImplementedParentValue( subTarget.getImplementedParentValue() ); subSource.setValue( subTarget.getValue() ); return subSource; diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java index 444b012078..695ef03577 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java @@ -29,6 +29,9 @@ else if (item instanceof SubSourceOther) { else { ImplementedParentTarget implementedParentTarget = new ImplementedParentTarget(); + implementedParentTarget.setParentValue( item.getParentValue() ); + implementedParentTarget.setImplementedParentValue( item.getImplementedParentValue() ); + return implementedParentTarget; } } @@ -40,6 +43,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -56,6 +61,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java index 8366d53ea9..782831d6e3 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java @@ -38,6 +38,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -54,6 +56,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } From 2fb5776350abf59f09cf26c727eb16ebfffadbc3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 11 May 2025 20:07:53 +0200 Subject: [PATCH 010/125] Add release notes for next version --- NEXT_RELEASE_CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..20cbc8c3f8 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,26 @@ ### Features +* Support for Java 21 Sequenced Collections (#3240) + + ### Enhancements +* Add support for locale parameter for numberFormat and dateFormat (#3628) +* 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. From fce73aee6ab655f3896dee440fa890a1971a281f Mon Sep 17 00:00:00 2001 From: roelmang Date: Sun, 11 May 2025 21:58:47 +0200 Subject: [PATCH 011/125] #3729 Support for using inner class Builder without using static factory method --- NEXT_RELEASE_CHANGELOG.md | 3 + .../chapter-3-defining-a-mapper.asciidoc | 7 +- .../ap/spi/DefaultBuilderProvider.java | 98 +++++++++++++--- .../ErroneousSimpleBuilderMapper.java | 5 +- .../{ => innerclass}/SimpleBuilderMapper.java | 5 +- ...lderThroughInnerClassConstructorTest.java} | 15 +-- ...ImmutablePersonWithInnerClassBuilder.java} | 14 +-- .../ErroneousSimpleBuilderMapper.java | 23 ++++ .../SimpleBuilderMapper.java | 23 ++++ ...BuilderThroughStaticFactoryMethodTest.java | 61 ++++++++++ ...ePersonWithStaticFactoryMethodBuilder.java | 105 ++++++++++++++++++ 11 files changed, 321 insertions(+), 38 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/ErroneousSimpleBuilderMapper.java (72%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/SimpleBuilderMapper.java (74%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutableBuilderTest.java => innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java} (78%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutablePerson.java => innerclass/SimpleImmutablePersonWithInnerClassBuilder.java} (86%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 20cbc8c3f8..2d0555cb24 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -6,6 +6,9 @@ ### 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) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ea574603ea..81980a35ac 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -421,8 +421,11 @@ If a Builder exists for a certain type, then that builder will be used for the m The default implementation of the `BuilderProvider` assumes the following: -* The type has a parameterless public static builder creation method that returns a builder. -So for example `Person` has a public static method that returns `PersonBuilder`. +* 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 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 92cd1ed80d..afcee7d247 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -10,6 +10,8 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -184,32 +186,96 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkPare return null; } - List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); - List builderInfo = new ArrayList<>(); - for ( ExecutableElement method : methods ) { - if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { - TypeElement builderElement = getTypeElement( method.getReturnType() ); - Collection buildMethods = findBuildMethods( builderElement, typeElement ); - if ( !buildMethods.isEmpty() ) { - builderInfo.add( new BuilderInfo.Builder() - .builderCreationMethod( method ) - .buildMethod( buildMethods ) - .build() - ); + // Builder infos which are determined by a static method on the type itself + List methodBuilderInfos = new ArrayList<>(); + // Builder infos which are determined by an inner builder class in the type itself + List innerClassBuilderInfos = new ArrayList<>(); + + for ( Element enclosedElement : typeElement.getEnclosedElements() ) { + if ( ElementKind.METHOD == enclosedElement.getKind() ) { + ExecutableElement method = (ExecutableElement) enclosedElement; + BuilderInfo builderInfo = determineMethodBuilderInfo( method, typeElement ); + if ( builderInfo != null ) { + methodBuilderInfos.add( builderInfo ); } } + else if ( ElementKind.CLASS == enclosedElement.getKind() ) { + if ( !methodBuilderInfos.isEmpty() ) { + // Small optimization to not check the inner classes + // if we already have at least one builder through a method + continue; + } + TypeElement classElement = (TypeElement) enclosedElement; + BuilderInfo builderInfo = determineInnerClassBuilderInfo( classElement, typeElement ); + if ( builderInfo != null ) { + innerClassBuilderInfos.add( builderInfo ); + } + } + } - if ( builderInfo.size() == 1 ) { - return builderInfo.get( 0 ); + if ( methodBuilderInfos.size() == 1 ) { + return methodBuilderInfos.get( 0 ); + } + else if ( methodBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), methodBuilderInfos ); } - else if ( builderInfo.size() > 1 ) { - throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); + else if ( innerClassBuilderInfos.size() == 1 ) { + return innerClassBuilderInfos.get( 0 ); + } + else if ( innerClassBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), innerClassBuilderInfos ); } if ( checkParent ) { return findBuilderInfo( typeElement.getSuperclass() ); } + + return null; + } + + private BuilderInfo determineMethodBuilderInfo(ExecutableElement method, + TypeElement typeElement) { + if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { + TypeElement builderElement = getTypeElement( method.getReturnType() ); + Collection buildMethods = findBuildMethods( builderElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( method ) + .buildMethod( buildMethods ) + .build(); + } + } + + return null; + } + + private BuilderInfo determineInnerClassBuilderInfo(TypeElement innerClassElement, + TypeElement typeElement) { + if ( innerClassElement.getModifiers().contains( Modifier.PUBLIC ) + && innerClassElement.getModifiers().contains( Modifier.STATIC ) + && innerClassElement.getSimpleName().toString().endsWith( "Builder" ) ) { + for ( Element element : innerClassElement.getEnclosedElements() ) { + if ( ElementKind.CONSTRUCTOR == element.getKind() ) { + ExecutableElement constructor = (ExecutableElement) element; + if ( constructor.getParameters().isEmpty() ) { + // We have a no-arg constructor + // Now check if we have build methods + Collection buildMethods = findBuildMethods( innerClassElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( constructor ) + .buildMethod( buildMethods ) + .build(); + } + // If we don't have any build methods + // then we can stop since we are only interested in the no-arg constructor + return null; + } + } + } + } + return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java similarity index 72% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java index 305ae77e80..18e82ed19c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) public interface ErroneousSimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface ErroneousSimpleBuilderMapper { @Mapping(target = "job", ignore = true ), @Mapping(target = "city", ignore = true ) }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java index 0b0e961b30..e2c673401a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) public interface SimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface SimpleBuilderMapper { @Mapping(target = "job", constant = "programmer"), @Mapping(target = "city", expression = "java(\"Bengalore\")") }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java similarity index 78% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java index 7f77621644..0a2c5287bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java @@ -3,11 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.Arrays; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -20,16 +21,16 @@ @WithClasses({ SimpleMutablePerson.class, - SimpleImmutablePerson.class + SimpleImmutablePersonWithInnerClassBuilder.class }) -public class SimpleImmutableBuilderTest { +public class SimpleImmutableBuilderThroughInnerClassConstructorTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); @ProcessorTest @WithClasses({ SimpleBuilderMapper.class }) - public void testSimpleImmutableBuilderHappyPath() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorHappyPath() { SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); SimpleMutablePerson source = new SimpleMutablePerson(); source.setAge( 3 ); @@ -37,7 +38,7 @@ public void testSimpleImmutableBuilderHappyPath() { source.setChildren( Arrays.asList( "Alice", "Tom" ) ); source.setAddress( "Plaza 1" ); - SimpleImmutablePerson targetObject = mapper.toImmutable( source ); + SimpleImmutablePersonWithInnerClassBuilder targetObject = mapper.toImmutable( source ); assertThat( targetObject.getAge() ).isEqualTo( 3 ); assertThat( targetObject.getName() ).isEqualTo( "Bob" ); @@ -53,8 +54,8 @@ public void testSimpleImmutableBuilderHappyPath() { diagnostics = @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousSimpleBuilderMapper.class, - line = 21, + line = 22, message = "Unmapped target property: \"name\".")) - public void testSimpleImmutableBuilderMissingPropertyFailsToCompile() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorMissingPropertyFailsToCompile() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java index 2fe2c8ded2..a9bda42d33 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java @@ -3,12 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.ArrayList; import java.util.List; -public class SimpleImmutablePerson { +public class SimpleImmutablePersonWithInnerClassBuilder { private final String name; private final int age; private final String job; @@ -16,7 +16,7 @@ public class SimpleImmutablePerson { private final String address; private final List children; - SimpleImmutablePerson(Builder builder) { + SimpleImmutablePersonWithInnerClassBuilder(Builder builder) { this.name = builder.name; this.age = builder.age; this.job = builder.job; @@ -25,10 +25,6 @@ public class SimpleImmutablePerson { this.children = new ArrayList<>(builder.children); } - public static Builder builder() { - return new Builder(); - } - public int getAge() { return age; } @@ -66,8 +62,8 @@ public Builder age(int age) { return this; } - public SimpleImmutablePerson build() { - return new SimpleImmutablePerson( this ); + public SimpleImmutablePersonWithInnerClassBuilder build() { + return new SimpleImmutablePersonWithInnerClassBuilder( this ); } public Builder name(String name) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java new file mode 100644 index 0000000000..6fc51ace3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java @@ -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.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ErroneousSimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "address", ignore = true ), + @Mapping(target = "job", ignore = true ), + @Mapping(target = "city", ignore = true ) + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java new file mode 100644 index 0000000000..4bd200bc7c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java @@ -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.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "name", source = "fullName"), + @Mapping(target = "job", constant = "programmer"), + @Mapping(target = "city", expression = "java(\"Bengalore\")") + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java new file mode 100644 index 0000000000..90fac6061e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SimpleMutablePerson.class, + SimpleImmutablePersonWithStaticFactoryMethodBuilder.class +}) +public class SimpleImmutableBuilderThroughStaticFactoryMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleBuilderMapper.class }) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodHappyPath() { + SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + source.setAddress( "Plaza 1" ); + + SimpleImmutablePersonWithStaticFactoryMethodBuilder targetObject = mapper.toImmutable( source ); + + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + assertThat( targetObject.getJob() ).isEqualTo( "programmer" ); + assertThat( targetObject.getCity() ).isEqualTo( "Bengalore" ); + assertThat( targetObject.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( targetObject.getChildren() ).contains( "Alice", "Tom" ); + } + + @ProcessorTest + @WithClasses({ ErroneousSimpleBuilderMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSimpleBuilderMapper.class, + line = 22, + message = "Unmapped target property: \"name\".")) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodMissingPropertyFailsToCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java new file mode 100644 index 0000000000..c3dc590210 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java @@ -0,0 +1,105 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.builder.simple.staticfactorymethod; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleImmutablePersonWithStaticFactoryMethodBuilder { + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + SimpleImmutablePersonWithStaticFactoryMethodBuilder(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.job = builder.job; + this.city = builder.city; + this.address = builder.address; + this.children = new ArrayList<>( builder.children ); + } + + public static Builder builder() { + return new Builder(); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private String name; + private int age; + private String job; + private String city; + private String address; + private List children = new ArrayList<>(); + + private Builder() { + } + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutablePersonWithStaticFactoryMethodBuilder build() { + return new SimpleImmutablePersonWithStaticFactoryMethodBuilder( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder city(String city) { + this.city = city; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public List getChildren() { + throw new UnsupportedOperationException( "This is just a marker method" ); + } + + public Builder addChild(String child) { + this.children.add( child ); + return this; + } + } +} From 6e6fd01a2eb08177d5cc360a97e1721db511239d Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 18 May 2025 00:40:51 +0800 Subject: [PATCH 012/125] #3821: Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` --------- Signed-off-by: TangYang --- .../main/java/org/mapstruct/BeanMapping.java | 12 +++ core/src/main/java/org/mapstruct/Mapper.java | 12 +++ .../main/java/org/mapstruct/MapperConfig.java | 12 +++ .../ap/internal/model/BeanMappingMethod.java | 17 ++++- .../model/source/BeanMappingOptions.java | 9 +++ .../internal/model/source/DefaultOptions.java | 4 + .../model/source/DelegatingOptions.java | 4 + .../model/source/MapperConfigOptions.java | 6 ++ .../internal/model/source/MapperOptions.java | 7 ++ .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../CustomExceptionSubclassMapper.java | 25 ++++++ .../CustomSubclassMappingException.java | 12 +++ .../CustomSubclassMappingExceptionTest.java | 76 +++++++++++++++++++ .../MapperConfigSubclassMapper.java | 29 +++++++ .../MapperSubclassMapper.java | 24 ++++++ 15 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index e94ff98f2d..309458f861 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -132,6 +132,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ 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()}. + *

+ * 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 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 source or target properties. diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 8a6f48dad9..398dc1870a 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -281,6 +281,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default */ 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()}. + *

+ * 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 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. diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 915f3dd120..8631562a56 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -249,6 +249,18 @@ MappingInheritanceStrategy mappingInheritanceStrategy() */ 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()}. + *

+ * 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 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. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 08c8ceda59..34c1ea3ccb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -100,6 +100,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final String finalizedResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final Type subclassExhaustiveException; private final MappingReferences mappingReferences; @@ -378,6 +379,11 @@ else if ( !method.isUpdateMethod() ) { } + TypeMirror subclassExhaustiveException = method.getOptions() + .getBeanMapping() + .getSubclassExhaustiveException(); + Type subclassExhaustiveExceptionType = ctx.getTypeFactory().getType( subclassExhaustiveException ); + List subclasses = new ArrayList<>(); for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { subclasses.add( createSubclassMapping( subclassMappingOptions ) ); @@ -451,7 +457,8 @@ else if ( !sourceParameter.getType().isPrimitive() ) { finalizeMethod, mappingReferences, subclasses, - presenceChecksByParameter + presenceChecksByParameter, + subclassExhaustiveExceptionType ); } @@ -1954,7 +1961,8 @@ private BeanMappingMethod(Method method, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, - Map presenceChecksByParameter) { + Map presenceChecksByParameter, + Type subclassExhaustiveException) { super( method, annotations, @@ -1969,6 +1977,7 @@ private BeanMappingMethod(Method method, this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; + this.subclassExhaustiveException = subclassExhaustiveException; if ( this.finalizerMethod != null ) { this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); @@ -2017,6 +2026,10 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { this.subclassMappings = subclassMappings; } + public Type getSubclassExhaustiveException() { + return subclassExhaustiveException; + } + public List getConstantMappings() { return constantMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index ac27dfff00..73a4d7c12d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -11,6 +11,7 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.BuilderGem; @@ -182,6 +183,14 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { .orElse( next().getSubclassExhaustiveStrategy() ); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveException ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .orElse( next().getSubclassExhaustiveException() ); + } + @Override public ReportingPolicyGem unmappedTargetPolicy() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index c754d3c395..e1f04fd941 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -131,6 +131,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); } + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().getDefaultValue(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy(); if ( nullValueIterableMappingStrategy != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 50c1d84541..34478969fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -106,6 +106,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return next.getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return next.getSubclassExhaustiveException(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { return next.getNullValueIterableMappingStrategy(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index e3ca6162a5..d606658878 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -141,6 +141,12 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return mapperConfig.subclassExhaustiveException().hasValue() ? + mapperConfig.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index ed1af34f79..9c2203efd5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -170,6 +170,13 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().hasValue() ? + mapper.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapper.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 3036e4a2c8..da590bb506 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -42,7 +42,7 @@ else { <#if isAbstractReturnType()> - throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); + throw new <@includeModel object=subclassExhaustiveException />("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); <#else> <#if !existingInstanceMapping> <#if hasConstructorMappings()> diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java new file mode 100644 index 0000000000..52738ef6f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java @@ -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.ap.test.subclassmapping.abstractsuperclass; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CustomExceptionSubclassMapper { + CustomExceptionSubclassMapper INSTANCE = Mappers.getMapper( CustomExceptionSubclassMapper.class ); + + @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java new file mode 100644 index 0000000000..abb675bbb4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.abstractsuperclass; + +public class CustomSubclassMappingException extends RuntimeException { + public CustomSubclassMappingException(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java new file mode 100644 index 0000000000..472c007269 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.abstractsuperclass; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@IssueKey("3821") +@WithClasses({ + Bike.class, + BikeDto.class, + Car.class, + CarDto.class, + Motorcycle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + AbstractVehicle.class, + VehicleDto.class, + CustomSubclassMappingException.class, + CustomExceptionSubclassMapper.class +}) +public class CustomSubclassMappingExceptionTest { + + private static final String EXPECTED_ERROR_MESSAGE = "Not all subclasses are supported for this mapping. " + + "Missing for class org.mapstruct.ap.test.subclassmapping.abstractsuperclass.Motorcycle"; + + @ProcessorTest + void customExceptionIsThrownForUnknownSubclass() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + void customExceptionIsThrownForSingleVehicle() { + AbstractVehicle vehicle = new Motorcycle(); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.map( vehicle ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperConfigSubclassMapper.class }) + void customExceptionIsThrownForMapperConfig() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperConfigSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperSubclassMapper.class }) + void customExceptionIsThrownForMapper() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java new file mode 100644 index 0000000000..b3c1a66bb6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java @@ -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.ap.test.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = MapperConfigSubclassMapper.Config.class) +public interface MapperConfigSubclassMapper { + + MapperConfigSubclassMapper INSTANCE = Mappers.getMapper( MapperConfigSubclassMapper.class ); + + @MapperConfig(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + interface Config { + } + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java new file mode 100644 index 0000000000..34ed3fde22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java @@ -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.ap.test.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) +public interface MapperSubclassMapper { + + MapperSubclassMapper INSTANCE = Mappers.getMapper( MapperSubclassMapper.class ); + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} From 05f27e96e2108344d69a2963b04ee973c83b0eb0 Mon Sep 17 00:00:00 2001 From: Dennis Melzer Date: Sun, 25 May 2025 14:58:23 +0200 Subject: [PATCH 013/125] #3852 Initialize Optionals with empty instead of null --- .../ap/internal/model/common/Type.java | 17 +++++ .../nullvalue/OptionalDefaultMapper.java | 20 ++++++ .../nullvalue/OptionalDefaultMapperTest.java | 53 ++++++++++++++++ .../nullvalue/SimpleConstructorMapper.java | 29 +++++++++ .../ap/test/optional/nullvalue/Source.java | 61 ++++++++++++++++++ .../ap/test/optional/nullvalue/Target.java | 63 +++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6520834066..d0f65ce97a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -17,6 +17,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -367,6 +371,15 @@ public boolean isArrayType() { return componentType != null; } + private boolean isType(Class type) { + return type.getName().equals( getFullyQualifiedName() ); + } + + private boolean isOptionalType() { + return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || + isType( OptionalLong.class ); + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } @@ -1166,6 +1179,10 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { * FTL. */ public String getNull() { + if ( isOptionalType() ) { + return createReferenceName() + ".empty()"; + } + if ( !isPrimitive() || isArrayType() ) { return "null"; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java new file mode 100644 index 0000000000..7397c2ccd1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper +public interface OptionalDefaultMapper { + + OptionalDefaultMapper INSTANCE = Mappers.getMapper( OptionalDefaultMapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java new file mode 100644 index 0000000000..78482831b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java @@ -0,0 +1,53 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dennis Melzer + */ +@WithClasses({ + OptionalDefaultMapper.class, + Source.class, + Target.class +}) +@IssueKey("3852") +public class OptionalDefaultMapperTest { + + @ProcessorTest + public void shouldOptionalNotNull() { + Source source = new Source( null, null, null, null, null, null, null ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).isEmpty(); + assertThat( target.getSomeInteger() ).isEmpty(); + assertThat( target.getSomeDouble() ).isEmpty(); + assertThat( target.getSomeBoolean() ).isEmpty(); + assertThat( target.getSomeIntValue() ).isEmpty(); + assertThat( target.getSomeDoubleValue() ).isEmpty(); + assertThat( target.getSomeLongValue() ).isEmpty(); + } + + @ProcessorTest + public void shouldMapOptional() { + Source source = new Source( "someString", 10, 11D, Boolean.TRUE, 10, 100D, 200L ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).contains( "someString" ); + assertThat( target.getSomeInteger() ).contains( 10 ); + assertThat( target.getSomeDouble() ).contains( 11D ); + assertThat( target.getSomeBoolean() ).contains( Boolean.TRUE ); + assertThat( target.getSomeIntValue() ).hasValue( 10 ); + assertThat( target.getSomeDoubleValue() ).hasValue( 100 ); + assertThat( target.getSomeLongValue() ).hasValue( 200 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java new file mode 100644 index 0000000000..158a01d3bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java @@ -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.ap.test.optional.nullvalue; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedproperties.simple._target.TargetObject; +import org.mapstruct.ap.test.nestedproperties.simple.source.SourceRoot; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + injectionStrategy = InjectionStrategy.CONSTRUCTOR, unmappedTargetPolicy = ReportingPolicy.IGNORE +) +public interface SimpleConstructorMapper { + + SimpleConstructorMapper MAPPER = Mappers.getMapper( SimpleConstructorMapper.class ); + + TargetObject toTargetObject(SourceRoot sourceRoot); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java new file mode 100644 index 0000000000..9746d4a152 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java @@ -0,0 +1,61 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +public class Source { + + private final String someString; + private final Integer someInteger; + private final Double someDouble; + private final Boolean someBoolean; + private final Integer someIntValue; + private final Double someDoubleValue; + private final Long someLongValue; + + public Source(String someString, Integer someInteger, Double someDouble, Boolean someBoolean, Integer someIntValue, + Double someDoubleValue, Long someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return Optional.ofNullable( someString ); + } + + public Optional getSomeInteger() { + return Optional.ofNullable( someInteger ); + } + + public Optional getSomeDouble() { + return Optional.ofNullable( someDouble ); + } + + public Optional getSomeBoolean() { + return Optional.ofNullable( someBoolean ); + } + + public OptionalDouble getSomeDoubleValue() { + return someDouble != null ? OptionalDouble.of( someDoubleValue ) : OptionalDouble.empty(); + } + + public OptionalInt getSomeIntValue() { + return someIntValue != null ? OptionalInt.of( someIntValue ) : OptionalInt.empty(); + } + + public OptionalLong getSomeLongValue() { + return someLongValue != null ? OptionalLong.of( someLongValue ) : OptionalLong.empty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java new file mode 100644 index 0000000000..5fd5d1730b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional someString; + private final Optional someInteger; + private final Optional someDouble; + private final Optional someBoolean; + private final OptionalInt someIntValue; + private final OptionalDouble someDoubleValue; + private final OptionalLong someLongValue; + + public Target(Optional someString, Optional someInteger, Optional someDouble, + Optional someBoolean, OptionalInt someIntValue, OptionalDouble someDoubleValue, + OptionalLong someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return someString; + } + + public Optional getSomeInteger() { + return someInteger; + } + + public Optional getSomeDouble() { + return someDouble; + } + + public Optional getSomeBoolean() { + return someBoolean; + } + + public OptionalLong getSomeLongValue() { + return someLongValue; + } + + public OptionalDouble getSomeDoubleValue() { + return someDoubleValue; + } + + public OptionalInt getSomeIntValue() { + return someIntValue; + } +} From 42c87d1da99252e21699cde7d584538b5c961ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eamil=20Can?= Date: Sun, 25 May 2025 15:22:47 +0200 Subject: [PATCH 014/125] #3848: Mark String to number as lossy conversion --- .../ap/internal/util/NativeTypes.java | 1 + .../conversion/lossy/CutleryInventoryDto.java | 9 +++++++++ .../lossy/CutleryInventoryEntity.java | 9 +++++++++ .../lossy/ErroneousKitchenDrawerMapper6.java | 20 +++++++++++++++++++ .../conversion/lossy/LossyConversionTest.java | 15 ++++++++++++++ .../lossy/OversizedKitchenDrawerDto.java | 8 ++++++++ .../lossy/RegularKitchenDrawerEntity.java | 8 ++++++++ 7 files changed, 70 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 0a4ca0cde0..ff01ae0ef3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -474,6 +474,7 @@ private NativeTypes() { tmp3.put( Double.class.getName(), 6 ); tmp3.put( BigInteger.class.getName(), 50 ); tmp3.put( BigDecimal.class.getName(), 51 ); + tmp3.put( String.class.getName(), 51 ); NARROWING_LUT = Collections.unmodifiableMap( tmp3 ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java index 5a96d6deb2..3bc144511e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java @@ -10,6 +10,7 @@ public class CutleryInventoryDto { private short numberOfKnifes; private int numberOfForks; private byte numberOfSpoons; + private int drawerId; private float approximateKnifeLength; @@ -44,4 +45,12 @@ public float getApproximateKnifeLength() { public void setApproximateKnifeLength(float approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java index 9575ed99dc..755dbc3637 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java @@ -10,6 +10,7 @@ public class CutleryInventoryEntity { private int numberOfKnifes; private Long numberOfForks; private short numberOfSpoons; + private String drawerId; private double approximateKnifeLength; @@ -44,4 +45,12 @@ public double getApproximateKnifeLength() { public void setApproximateKnifeLength(double approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java new file mode 100644 index 0000000000..c33f1bd662 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index d27f04374a..629f727273 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -40,12 +40,14 @@ public void testNoErrorCase() { dto.setNumberOfKnifes( (short) 7 ); dto.setNumberOfSpoons( (byte) 3 ); dto.setApproximateKnifeLength( 3.7f ); + dto.setDrawerId( 1 ); CutleryInventoryEntity entity = CutleryInventoryMapper.INSTANCE.map( dto ); assertThat( entity.getNumberOfForks() ).isEqualTo( 5L ); assertThat( entity.getNumberOfKnifes() ).isEqualTo( 7 ); assertThat( entity.getNumberOfSpoons() ).isEqualTo( (short) 3 ); assertThat( entity.getApproximateKnifeLength() ).isCloseTo( 3.7d, withinPercentage( 0.0001d ) ); + assertThat( entity.getDrawerId() ).isEqualTo( "1" ); } @ProcessorTest @@ -74,6 +76,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"String drawerId\". It has a possibly lossy conversion from " + + "String to int.") + }) + public void testConversionFromStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java index 110bb2f07c..c42b24cc57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java @@ -21,6 +21,7 @@ public class OversizedKitchenDrawerDto { private Double depth; private BigDecimal length; private double height; + private String drawerId; public long getNumberOfForks() { return numberOfForks; @@ -70,4 +71,11 @@ public void setHeight(double height) { this.height = height; } + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java index d49a89052d..e3ae10bc1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java @@ -17,6 +17,7 @@ public class RegularKitchenDrawerEntity { private float depth; private Float length; private VerySpecialNumber height; + private int drawerId; public int getNumberOfForks() { return numberOfForks; @@ -66,4 +67,11 @@ public void setHeight(VerySpecialNumber height) { this.height = height; } + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } From 3a5c70224d60aaa834986c5ff88fcb568df9fc85 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 21:40:43 +0800 Subject: [PATCH 015/125] #3809 Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings Signed-off-by: TangYang --- .../model/assignment/UpdateWrapper.ftl | 15 +++- .../ap/test/bugs/_3809/Issue3809Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3809/Issue3809Test.java | 20 ++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 9cdd07c230..ea8eed2438 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -10,7 +10,7 @@ <@lib.handleExceptions> <#if includeSourceNullCheck> <@lib.sourceLocalVarAssignment/> - if ( <#if sourcePresenceCheckerReference?? ><@includeModel object=sourcePresenceCheckerReference /><#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <@handleSourceReferenceNullCheck/> ) { <@assignToExistingTarget/> <@lib.handleAssignment/>; } @@ -32,3 +32,16 @@ ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; } + +<#macro handleSourceReferenceNullCheck> + <@compress single_line=true> + <#if sourcePresenceCheckerReference?? > + <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName + targetType=ext.targetType/> + <#else> + <#if sourceLocalVarName??> ${sourceLocalVarName} <#else> ${sourceReference} != null + + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java new file mode 100644 index 0000000000..10ff9e2628 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3809; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetPropertyName; + +@Mapper +public interface Issue3809Mapper { + void updateMappingFails(Source source, @MappingTarget Target target); + + @Condition + default boolean canMap(Object source, @TargetPropertyName String propertyName) { + return true; + } + + class Source { + private NestedSource param; + + public NestedSource getParam() { + return param; + } + } + + class NestedSource { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } + + class Target { + + private NestedTarget param; + + public NestedTarget getParam() { + return param; + } + + public void setParam(NestedTarget param) { + this.param = param; + } + + } + + class NestedTarget { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java new file mode 100644 index 0000000000..23d97b877c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3809; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses(Issue3809Mapper.class) +@IssueKey("3809") +public class Issue3809Test { + + @ProcessorTest + public void shouldCompileNoError() { + + } +} From 0badba70038875cb2fdf6d9ff328b6a9ba31d245 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 22:35:38 +0800 Subject: [PATCH 016/125] #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 --- .../org/mapstruct/ap/MappingProcessor.java | 8 +- .../model/LifecycleMethodResolver.java | 2 +- .../model/ObjectFactoryMethodResolver.java | 7 +- .../model/PresenceCheckMethodResolver.java | 3 +- .../LifecycleOverloadDeduplicateSelector.java | 129 +++++++++++++++ .../source/selector/MethodSelectors.java | 16 +- .../mapstruct/ap/internal/option/Options.java | 9 +- .../creation/MappingResolverImpl.java | 2 +- .../mapstruct/ap/test/bugs/_3849/Child.java | 12 ++ .../ap/test/bugs/_3849/ChildDto.java | 16 ++ .../bugs/_3849/DeduplicateBySourceMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateByTargetMapper.java | 69 ++++++++ .../DeduplicateForCompileArgsMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateGenericMapper.java | 51 ++++++ .../ap/test/bugs/_3849/Issue3849Test.java | 150 ++++++++++++++++++ .../mapstruct/ap/test/bugs/_3849/Parent.java | 21 +++ .../ap/test/bugs/_3849/ParentDto.java | 22 +++ 17 files changed, 642 insertions(+), 13 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 407166f6fa..b34b133262 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -95,6 +95,7 @@ MappingProcessor.VERBOSE, MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, + MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, }) public class MappingProcessor extends AbstractProcessor { @@ -115,6 +116,8 @@ public class MappingProcessor extends AbstractProcessor { protected static final String VERBOSE = "mapstruct.verbose"; protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; + protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = + "mapstruct.disableLifecycleOverloadDeduplicateSelector"; private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -174,6 +177,8 @@ private Options createOptions() { String nullValueIterableMappingStrategy = processingEnv.getOptions() .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); + String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() + .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); return new Options( Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), @@ -189,7 +194,8 @@ private Options createOptions() { NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null + NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, + Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index b713fa5f0c..cfe4f9f8b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -133,7 +133,7 @@ private static List collectLifecycleCallbackMe MappingBuilderContext ctx, Set existingVariableNames) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 4cf653b46b..89f295981f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -5,12 +5,9 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -26,6 +23,8 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.util.Message; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * * @author Sjaak Derksen @@ -126,7 +125,7 @@ public static List> getMatchingFactoryMethods( Meth MappingBuilderContext ctx) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), null ); return selectors.getMatchingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 5906db8219..96826f8f66 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -119,7 +119,8 @@ private static List> findMatchingMethods( MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), - ctx.getMessager() + ctx.getMessager(), + null ); return selectors.getMatchingMethods( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java new file mode 100644 index 0000000000..7ce23af186 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java @@ -0,0 +1,129 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selector for deduplicating overloaded lifecycle callback methods + * whose parameter signatures differ only by type hierarchy. + *

+ * In the context of lifecycle callback method selection + * (such as @BeforeMapping or @AfterMapping), it is possible to have multiple overloaded methods + * whose parameter lists are structurally identical except for the specific types, + * where those types are related by inheritance (e.g., one parameter is a superclass or subclass of another). + *

+ * This selector groups such methods by their effective parameter signature + * (ignoring differences only in type hierarchy), and, within each group, + * retains only the method whose parameter types have the closest inheritance distance + * to the actual invocation types. + * This ensures that, for each group of nearly identical overloads, + * only the most specific and appropriate method is selected. + *

+ * Example (see Issue3849Test): + * + *

{@code
+ * @AfterMapping
+ * default void afterMapping(Parent source, @MappingTarget ParentDto target) { ... }
+ * @AfterMapping
+ * default void afterMapping(Parent source, @MappingTarget ChildDto target) { ... }
+ * }
+ * When mapping a Child to a ChildDto, + * only the method with ChildDto is selected, even though both methods match by signature + * except for the target type's inheritance relationship. + */ +public class LifecycleOverloadDeduplicateSelector implements MethodSelector { + @Override + public List> getMatchingMethods(List> methods, + SelectionContext context) { + if ( !context.getSelectionCriteria().isLifecycleCallbackRequired() || methods.size() <= 1 ) { + return methods; + } + Collection>> methodSignatureGroups = + methods.stream() + .collect( Collectors.groupingBy( + LifecycleOverloadDeduplicateSelector::buildSignatureKey, + LinkedHashMap::new, + Collectors.toList() + ) ) + .values(); + List> deduplicatedMethods = new ArrayList<>( methods.size() ); + for ( List> signatureGroup : methodSignatureGroups ) { + if ( signatureGroup.size() == 1 ) { + deduplicatedMethods.add( signatureGroup.get( 0 ) ); + continue; + } + SelectedMethod bestInheritanceMethod = signatureGroup.get( 0 ); + for ( int i = 1; i < signatureGroup.size(); i++ ) { + SelectedMethod candidateMethod = signatureGroup.get( i ); + if ( isInheritanceBetter( candidateMethod, bestInheritanceMethod ) ) { + bestInheritanceMethod = candidateMethod; + } + } + deduplicatedMethods.add( bestInheritanceMethod ); + } + return deduplicatedMethods; + } + + /** + * Builds a grouping key for a method based on its defining type, + * method name, and a detailed breakdown of each parameter binding. + *

+ * The key consists of: + *

    + *
  • The type that defines the method
  • + *
  • The method name
  • + *
  • parameter bindings
  • + *
+ * This ensures that methods are grouped together only if all these aspects match, + * except for differences in type hierarchy, which are handled separately. + */ + private static List buildSignatureKey(SelectedMethod method) { + List key = new ArrayList<>(); + key.add( method.getMethod().getDefiningType() ); + key.add( method.getMethod().getName() ); + for ( ParameterBinding binding : method.getParameterBindings() ) { + key.add( binding.getType() ); + key.add( binding.getVariableName() ); + } + return key; + } + + /** + * Compare the inheritance distance of parameters between two methods to determine if candidateMethod is better. + * Compares parameters in order, returns as soon as a better one is found. + */ + private boolean isInheritanceBetter(SelectedMethod candidateMethod, + SelectedMethod currentBestMethod) { + List candidateBindings = candidateMethod.getParameterBindings(); + List bestBindings = currentBestMethod.getParameterBindings(); + List candidateParams = candidateMethod.getMethod().getParameters(); + List bestParams = currentBestMethod.getMethod().getParameters(); + int paramCount = candidateBindings.size(); + + for ( int i = 0; i < paramCount; i++ ) { + int candidateDistance = candidateBindings.get( i ) + .getType() + .distanceTo( candidateParams.get( i ).getType() ); + int bestDistance = bestBindings.get( i ).getType().distanceTo( bestParams.get( i ).getType() ); + if ( candidateDistance < bestDistance ) { + return true; + } + else if ( candidateDistance > bestDistance ) { + return false; + } + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index c14729a90f..774f25a8c3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -10,6 +10,7 @@ import java.util.List; import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.TypeUtils; @@ -24,20 +25,27 @@ public class MethodSelectors { private final List selectors; public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, - FormattingMessager messager) { - selectors = Arrays.asList( + FormattingMessager messager, Options options) { + List selectorList = new ArrayList<>( Arrays.asList( new MethodFamilySelector(), new TypeSelector( messager ), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils ), new JavaxXmlElementDeclSelector( typeUtils ), new JakartaXmlElementDeclSelector( typeUtils ), - new InheritanceSelector(), + new InheritanceSelector() + ) ); + if ( options != null && !options.isDisableLifecycleOverloadDeduplicateSelector() ) { + selectorList.add( new LifecycleOverloadDeduplicateSelector() ); + } + + selectorList.addAll( Arrays.asList( new CreateOrUpdateSelector(), new SourceRhsSelector(), new FactoryParameterSelector(), new MostSpecificResultTypeSelector() - ); + ) ); + this.selectors = selectorList; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index a544374c0e..095c472c87 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -26,6 +26,7 @@ public class Options { private final boolean verbose; private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; private final NullValueMappingStrategyGem nullValueMapMappingStrategy; + private final boolean disableLifecycleOverloadDeduplicateSelector; //CHECKSTYLE:OFF public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, @@ -36,7 +37,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers boolean disableBuilders, boolean verbose, NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy + NullValueMappingStrategyGem nullValueMapMappingStrategy, + boolean disableLifecycleOverloadDeduplicateSelector ) { //CHECKSTYLE:ON this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; @@ -50,6 +52,7 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers this.verbose = verbose; this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; + this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; } public boolean isSuppressGeneratorTimestamp() { @@ -95,4 +98,8 @@ public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return nullValueMapMappingStrategy; } + + public boolean isDisableLifecycleOverloadDeduplicateSelector() { + return disableLifecycleOverloadDeduplicateSelector; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index d84ba974db..ba903b6048 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -116,7 +116,7 @@ public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtil this.conversions = new Conversions( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null ); this.verboseLogging = verboseLogging; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java new file mode 100644 index 0000000000..393cb16970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class Child extends Parent { + public Child() { + super(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java new file mode 100644 index 0000000000..e8cdae4d17 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class ChildDto extends ParentDto { + public ChildDto(String value) { + super( value ); + } + + public void setValue(String value) { + super.setValue( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java new file mode 100644 index 0000000000..68450c7d9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateBySourceMapper { + + DeduplicateBySourceMapper INSTANCE = Mappers.getMapper( DeduplicateBySourceMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ParentDto mapChild(Child source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSourceInOtherClass" ); + } + + @BeforeMapping + void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSourceInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSource" ); + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSource" ); + } + + @AfterMapping + default void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSource" ); + } + + @AfterMapping + default void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSource" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java new file mode 100644 index 0000000000..0aed8a2957 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateByTargetMapper { + + DeduplicateByTargetMapper INSTANCE = Mappers.getMapper( DeduplicateByTargetMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java new file mode 100644 index 0000000000..276a2c4352 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateForCompileArgsMapper { + + DeduplicateForCompileArgsMapper INSTANCE = Mappers.getMapper( DeduplicateForCompileArgsMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java new file mode 100644 index 0000000000..662137f1a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateGenericMapper { + + DeduplicateGenericMapper INSTANCE = Mappers.getMapper( DeduplicateGenericMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source); + + ChildDto mapChild(Parent source); + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "beforeMappingParentGeneric" ); + } + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChild" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "afterMappingGeneric" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParent" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChild" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java new file mode 100644 index 0000000000..a58c52c921 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java @@ -0,0 +1,150 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package org.mapstruct.ap.test.bugs._3849; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3849") +@WithClasses({ + Parent.class, + ParentDto.class, + Child.class, + ChildDto.class +}) +public class Issue3849Test { + + @ProcessorOption(name = "mapstruct.disableLifecycleOverloadDeduplicateSelector", value = "true") + @ProcessorTest() + @WithClasses(DeduplicateForCompileArgsMapper.class) + void lifecycleMappingOverloadSelectorDisableCompileArgs() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateForCompileArgsMapper.MappingContext mappingContext = + new DeduplicateForCompileArgsMapper.MappingContext(); + ParentDto parentDto = DeduplicateForCompileArgsMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateForCompileArgsMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget", + "afterMappingChildTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest() + @WithClasses( DeduplicateByTargetMapper.class ) + void lifecycleMappingOverloadByTarget() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateByTargetMapper.MappingContext mappingContext = new DeduplicateByTargetMapper.MappingContext(); + ParentDto parentDto = DeduplicateByTargetMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateByTargetMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses( DeduplicateBySourceMapper.class ) + void lifecycleMappingOverloadBySource() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateBySourceMapper.MappingContext mappingContext = new DeduplicateBySourceMapper.MappingContext(); + ParentDto parentDto = DeduplicateBySourceMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentSourceInOtherClass", + "beforeMappingParentSource", + "afterMappingParentSourceInOtherClass", + "afterMappingParentSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateBySourceMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildSourceInOtherClass", + "beforeMappingChildSource", + "afterMappingChildSourceInOtherClass", + "afterMappingChildSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses(DeduplicateGenericMapper.class) + void lifecycleMappingOverloadForGeneric() { + Child child = new Child(); + Parent parent = new Parent(); + + ParentDto parentDto = DeduplicateGenericMapper.INSTANCE.mapParent( parent ); + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentGeneric", + "afterMappingParent" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateGenericMapper.INSTANCE.mapChild( child ); + + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChild", + "afterMappingChild" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java new file mode 100644 index 0000000000..4930677286 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class Parent { + private String value; + + public Parent() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java new file mode 100644 index 0000000000..098917f001 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3849; + +public class ParentDto { + private String value; + + public ParentDto(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} From 6b6600c370cb50b2fbf40f56412238974575ae85 Mon Sep 17 00:00:00 2001 From: Aleksey Ivashin Date: Sun, 25 May 2025 18:05:18 +0300 Subject: [PATCH 017/125] #1958: Add support for ignoring multiple target properties at once --- core/src/main/java/org/mapstruct/Ignored.java | 67 ++++++++++++ .../main/java/org/mapstruct/IgnoredList.java | 54 ++++++++++ core/src/main/java/org/mapstruct/Mapping.java | 3 + .../chapter-5-data-type-conversions.asciidoc | 7 ++ .../ap/internal/gem/GemGenerator.java | 4 + .../processor/MethodRetrievalProcessor.java | 61 ++++++++++- .../org/mapstruct/ap/test/ignored/Animal.java | 64 +++++++++++ .../mapstruct/ap/test/ignored/AnimalDto.java | 63 +++++++++++ .../ap/test/ignored/AnimalMapper.java | 20 ++++ .../ap/test/ignored/ErroneousMapper.java | 26 +++++ .../ap/test/ignored/IgnoredPropertyTest.java | 102 ++++++++++++++++++ .../org/mapstruct/ap/test/ignored/Zoo.java | 48 +++++++++ .../org/mapstruct/ap/test/ignored/ZooDto.java | 48 +++++++++ .../mapstruct/ap/test/ignored/ZooMapper.java | 23 ++++ 14 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/mapstruct/Ignored.java create mode 100644 core/src/main/java/org/mapstruct/IgnoredList.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java diff --git a/core/src/main/java/org/mapstruct/Ignored.java b/core/src/main/java/org/mapstruct/Ignored.java new file mode 100644 index 0000000000..47e4961f43 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Ignored.java @@ -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. + * + *

+ * The name all attributes of for ignored is to be specified via {@link #targets()}. + *

+ * + *

+ * Example 1: Implicitly mapping fields with the same name: + *

+ * + *

+ * // We need ignored Human.name and Human.lastName
+ * // we can use @Ignored with parameters "name", "lastName" {@link #targets()}
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Ignored( targets = { "name", "lastName" } )
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates:
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *    humanDto.setFullName( human.getFullName() );
+ *    // ...
+ * }
+ * 
+ * + * @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 ""; + +} diff --git a/core/src/main/java/org/mapstruct/IgnoredList.java b/core/src/main/java/org/mapstruct/IgnoredList.java new file mode 100644 index 0000000000..e47db6bac7 --- /dev/null +++ b/core/src/main/java/org/mapstruct/IgnoredList.java @@ -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. + *

+ * 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. + * + *

These two examples are equal. + *

+ *

+ * // before Java 8
+ * @Mapper
+ * public interface MyMapper {
+ *     @IgnoredList({
+ *         @Ignored(targets = { "firstProperty" } ),
+ *         @Ignored(targets = { "secondProperty" } )
+ *     })
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ *

+ * // Java 8 and later
+ * @Mapper
+ * public interface MyMapper {
+ *     @Ignored(targets = { "firstProperty" } ),
+ *     @Ignored(targets = { "secondProperty" } )
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ * + * @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(); +} diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 6c95b0db2c..f15cfb75cc 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -306,6 +306,9 @@ * 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. + *

+ * 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 */ diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index bc406cf0b0..ad49fe2960 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -280,6 +280,13 @@ This puts the configuration of the nested mapping into one place (method) where 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`. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 2ed9bd9a09..a8b62babe9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -26,6 +26,8 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; import org.mapstruct.Mapping; +import org.mapstruct.Ignored; +import org.mapstruct.IgnoredList; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.Named; @@ -53,6 +55,8 @@ @GemDefinition(AnnotateWiths.class) @GemDefinition(Mapper.class) @GemDefinition(Mapping.class) +@GemDefinition(Ignored.class) +@GemDefinition(IgnoredList.class) @GemDefinition(Mappings.class) @GemDefinition(IterableMapping.class) @GemDefinition(BeanMapping.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index c8c13e6bf9..6843ac5bbe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -21,6 +21,8 @@ import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.IgnoredGem; +import org.mapstruct.ap.internal.gem.IgnoredListGem; import org.mapstruct.ap.internal.gem.IterableMappingGem; import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.MappingGem; @@ -76,6 +78,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { - return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method ); + Set processedAnnotations = new RepeatableMappings( beanMapping ) + .getProcessedAnnotations( method ); + processedAnnotations.addAll( new IgnoredConditions( processedAnnotations ) + .getProcessedAnnotations( method ) ); + return processedAnnotations; } /** @@ -823,4 +831,55 @@ protected void addInstance(ConditionGem gem, Element source, Set { + + protected final Set processedAnnotations; + + protected IgnoredConditions( Set processedAnnotations ) { + super( elementUtils, IGNORED_FQN, IGNORED_LIST_FQN ); + this.processedAnnotations = processedAnnotations; + } + + @Override + protected IgnoredGem singularInstanceOn(Element element) { + return IgnoredGem.instanceOn( element ); + } + + @Override + protected IgnoredListGem multipleInstanceOn(Element element) { + return IgnoredListGem.instanceOn( element ); + } + + @Override + protected void addInstance(IgnoredGem gem, Element method, Set mappings) { + IgnoredGem ignoredGem = IgnoredGem.instanceOn( method ); + if ( ignoredGem == null ) { + ignoredGem = gem; + } + String prefix = ignoredGem.prefix().get(); + for ( String target : ignoredGem.targets().get() ) { + String realTarget = target; + if ( !prefix.isEmpty() ) { + realTarget = prefix + "." + target; + } + MappingOptions mappingOptions = MappingOptions.forIgnore( realTarget ); + if ( processedAnnotations.contains( mappingOptions ) || mappings.contains( mappingOptions ) ) { + messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, realTarget ); + } + else { + mappings.add( mappingOptions ); + } + } + } + + @Override + protected void addInstances(IgnoredListGem gem, Element method, Set mappings) { + IgnoredListGem ignoredListGem = IgnoredListGem.instanceOn( method ); + for ( IgnoredGem ignoredGem : ignoredListGem.value().get() ) { + addInstance( ignoredGem, method, mappings ); + } + } + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java new file mode 100644 index 0000000000..223f99f705 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class Animal { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColour; + //CHECKSTYLE:OFN + private String colour; + private String name; + private int size; + private Integer age; + + // private String colour; + public Animal() { + } + + public Animal(String name, int size, Integer age, String colour) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColour = colour; + this.colour = colour; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColour() { + return colour; + } + + public void setColour( String colour ) { + this.colour = colour; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java new file mode 100644 index 0000000000..1651f9b56b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class AnimalDto { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColor; + //CHECKSTYLE:ON + private String name; + private Integer size; + private Integer age; + private String color; + + public AnimalDto() { + + } + + public AnimalDto(String name, Integer size, Integer age, String color) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColor = color; + this.color = color; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java new file mode 100644 index 0000000000..2753b36070 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AnimalMapper { + + AnimalMapper INSTANCE = Mappers.getMapper( AnimalMapper.class ); + + @Ignored( targets = { "publicAge", "age", "publicColor", "color" } ) + AnimalDto animalToDto( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java new file mode 100644 index 0000000000..b113e715aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java @@ -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.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousMapper { + + ErroneousMapper INSTANCE = Mappers.getMapper( ErroneousMapper.class ); + + @Mapping(target = "name", ignore = true) + @Ignored(targets = { "name", "color", "publicColor" }) + AnimalDto ignoredAndMappingAnimalToDto( Animal animal ); + + @Mapping(target = "publicColor", source = "publicColour") + @Ignored(targets = { "publicColor", "color" }) + AnimalDto ignoredAndMappingAnimalToDtoMap( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java new file mode 100644 index 0000000000..bc6fc2f6b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java @@ -0,0 +1,102 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for ignoring properties during the mapping. + * + * @author Ivashin Aleksey + */ +@WithClasses({ Animal.class, AnimalDto.class, Zoo.class, ZooDto.class, ZooMapper.class}) +public class IgnoredPropertyTest { + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + + AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); + + assertThat( animalDto ).isNotNull(); + assertThat( animalDto.getName() ).isEqualTo( "Bruno" ); + assertThat( animalDto.getSize() ).isEqualTo( 100 ); + assertThat( animalDto.getAge() ).isNull(); + assertThat( animalDto.publicAge ).isNull(); + assertThat( animalDto.getColor() ).isNull(); + assertThat( animalDto.publicColor ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { ErroneousMapper.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Target property \"name\" must not be mapped more than once." ), + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Target property \"publicColor\" must not be mapped more than once." ) + } + ) + public void shouldFailToGenerateMappings() { + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isEqualTo( "test address" ); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute2() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto2( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isNull(); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java new file mode 100644 index 0000000000..377e03b877 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class Zoo { + + private Animal animal; + + private String name; + + private String address; + + public Zoo() { + } + + public Zoo(Animal animal, String name, String address ) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public Animal getAnimal() { + return animal; + } + + public void setAnimal(Animal animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java new file mode 100644 index 0000000000..7c062cab4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.ignored; + +public class ZooDto { + + private AnimalDto animal; + + private String name; + + private String address; + + public ZooDto() { + } + + public ZooDto(AnimalDto animal, String name, String address) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public AnimalDto getAnimal() { + return animal; + } + + public void setAnimal(AnimalDto animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java new file mode 100644 index 0000000000..f05e045b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java @@ -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.ap.test.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ZooMapper { + + ZooMapper INSTANCE = Mappers.getMapper( ZooMapper.class ); + + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto( Zoo zoo ); + + @Ignored( targets = { "address" } ) + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto2( Zoo zoo ); +} From 5464c3cff805a235e908976fe5d55d0162d2a323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:10:24 +0800 Subject: [PATCH 018/125] #3711: Support generic `@Context` Signed-off-by: TangYang --- .../processor/MethodRetrievalProcessor.java | 25 +++++++++------ .../ap/test/bugs/_3711/BaseMapper.java | 12 +++++++ .../ap/test/bugs/_3711/Issue3711Test.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/JpaContext.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/ParentDto.java | 18 +++++++++++ .../ap/test/bugs/_3711/ParentEntity.java | 18 +++++++++++ .../test/bugs/_3711/SourceTargetMapper.java | 16 ++++++++++ 7 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 6843ac5bbe..d7a0329aeb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -114,7 +114,12 @@ public List process(ProcessorContext context, TypeElement mapperTy } List prototypeMethods = retrievePrototypeMethods( mapperTypeElement, mapperOptions ); - return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperOptions, prototypeMethods ); + return retrieveMethods( + typeFactory.getType( mapperTypeElement.asType() ), + mapperTypeElement, + mapperOptions, + prototypeMethods + ); } @Override @@ -166,19 +171,21 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen /** * Retrieves the mapping methods declared by the given mapper type. * - * @param usedMapper The type of interest (either the mapper to implement or a used mapper via @uses annotation) + * @param usedMapperType The type of interest (either the mapper to implement, a used mapper via @uses annotation, + * or a parameter type annotated with @Context) * @param mapperToImplement the top level type (mapper) that requires implementation * @param mapperOptions the mapper config * @param prototypeMethods prototype methods defined in mapper config type * @return All mapping methods declared by the given type */ - private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, + private List retrieveMethods(Type usedMapperType, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { List methods = new ArrayList<>(); + TypeElement usedMapper = usedMapperType.getTypeElement(); for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( usedMapper ) ) { SourceMethod method = getMethod( - usedMapper, + usedMapperType, executable, mapperToImplement, mapperOptions, @@ -195,7 +202,7 @@ private List retrieveMethods(TypeElement usedMapper, TypeElement m TypeElement usesMapperElement = asTypeElement( mapper ); if ( !mapperToImplement.equals( usesMapperElement ) ) { methods.addAll( retrieveMethods( - usesMapperElement, + typeFactory.getType( mapper ), mapperToImplement, mapperOptions, prototypeMethods ) ); @@ -218,13 +225,13 @@ private TypeElement asTypeElement(DeclaredType type) { return (TypeElement) type.asElement(); } - private SourceMethod getMethod(TypeElement usedMapper, + private SourceMethod getMethod(Type usedMapperType, ExecutableElement method, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { - - ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapper.asType(), method ); + TypeElement usedMapper = usedMapperType.getTypeElement(); + ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapperType.getTypeMirror(), method ); List parameters = typeFactory.getParameters( methodType, method ); Type returnType = typeFactory.getReturnType( methodType ); @@ -357,7 +364,7 @@ private ParameterProvidedMethods retrieveContextProvidedMethods( continue; } List contextParamMethods = retrieveMethods( - contextParam.getType().getTypeElement(), + contextParam.getType(), mapperToImplement, mapperConfig, Collections.emptyList() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java new file mode 100644 index 0000000000..e2a04fe336 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import org.mapstruct.Context; + +interface BaseMapper { + E toEntity(T s, @Context JpaContext ctx); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java new file mode 100644 index 0000000000..7b141e9e14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java @@ -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.ap.test.bugs._3711; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ParentEntity.class, + ParentDto.class, + JpaContext.class, + SourceTargetMapper.class, + BaseMapper.class, +}) +@IssueKey("3711") +class Issue3711Test { + @ProcessorTest + void shouldGenerateContextMethod() { + JpaContext jpaContext = new JpaContext<>(); + SourceTargetMapper.INSTANCE.toEntity( new ParentDto(), jpaContext ); + + assertThat( jpaContext.getInvokedMethods() ) + .containsExactly( "beforeMapping", "afterMapping" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java new file mode 100644 index 0000000000..b21bf639d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java @@ -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.ap.test.bugs._3711; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +public class JpaContext { + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + void beforeMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "beforeMapping" ); + } + + @AfterMapping + void afterMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "afterMapping" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java new file mode 100644 index 0000000000..664d6e58ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +public class ParentDto { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java new file mode 100644 index 0000000000..aaefa949fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +public class ParentEntity { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java new file mode 100644 index 0000000000..b09333fd56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3711; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper extends BaseMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + ParentEntity toDTO(ParentDto dto); +} From 8fc97f5f62743cc72280f5334dbebf9fdc39bfee Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:13:50 +0800 Subject: [PATCH 019/125] #3806: Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters Signed-off-by: TangYang --- .../model/CollectionAssignmentBuilder.java | 1 + .../GetterWrapperForCollectionsAndMaps.java | 17 ++++ .../GetterWrapperForCollectionsAndMaps.ftl | 5 +- .../ap/test/bugs/_3806/Issue3806Mapper.java | 63 ++++++++++++++ .../ap/test/bugs/_3806/Issue3806Test.java | 86 +++++++++++++++++++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 9a0a025845..76e56cd976 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -240,6 +240,7 @@ else if ( hasNoArgsConstructor() ) { result, method.getThrownTypes(), targetType, + nvpms, targetAccessorType.isFieldAssignment() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java index d29a80b420..e1c0c8cb20 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java @@ -9,9 +9,12 @@ import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; + /** * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. @@ -26,6 +29,14 @@ * @author Sjaak Derksen */ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps { + private final boolean ignoreMapNull; + + public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + this( decoratedAssignment, thrownTypesToExclude, targetType, null, fieldAssignment ); + } /** * @param decoratedAssignment source RHS @@ -36,6 +47,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, + NullValuePropertyMappingStrategyGem nvpms, boolean fieldAssignment) { super( @@ -44,6 +56,7 @@ public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, targetType, fieldAssignment ); + this.ignoreMapNull = nvpms == IGNORE; } @Override @@ -54,4 +67,8 @@ public Set getImportTypes() { } return imported; } + + public boolean isIgnoreMapNull() { + return ignoreMapNull; + } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl index 4ef55f3b40..f47a37106e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl @@ -10,10 +10,13 @@ <@lib.sourceLocalVarAssignment/> if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) { <@lib.handleExceptions> - <#if ext.existingInstanceMapping> + <#if ext.existingInstanceMapping && !ignoreMapNull> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); <@lib.handleLocalVarNullCheck needs_explicit_local_var=false> + <#if ext.existingInstanceMapping && ignoreMapNull> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java new file mode 100644 index 0000000000..89f325dfbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3806; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface Issue3806Mapper { + + Issue3806Mapper INSTANCE = Mappers.getMapper( Issue3806Mapper.class ); + + void update(@MappingTarget Target target, Target source); + + class Target { + + private final Collection authors; + private final Map booksByAuthor; + + protected Collection books; + protected Map booksByPublisher; + + public Target(Collection authors, Map booksByAuthor) { + this.authors = authors != null ? new ArrayList<>( authors ) : null; + this.booksByAuthor = booksByAuthor != null ? new HashMap<>( booksByAuthor ) : null; + } + + public Collection getAuthors() { + return authors; + } + + public Map getBooksByAuthor() { + return booksByAuthor; + } + + public Collection getBooks() { + return books; + } + + public void setBooks(Collection books) { + this.books = books; + } + + public Map getBooksByPublisher() { + return booksByPublisher; + } + + public void setBooksByPublisher(Map booksByPublisher) { + this.booksByPublisher = booksByPublisher; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java new file mode 100644 index 0000000000..1df3318c22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java @@ -0,0 +1,86 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3806; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses(Issue3806Mapper.class) +@IssueKey("3806") +class Issue3806Test { + + @ProcessorTest + void shouldNotClearGetterOnlyCollectionsInUpdateMapping() { + Map booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author1", "book1" ); + booksByAuthor.put( "author2", "book2" ); + List authors = new ArrayList<>(); + authors.add( "author1" ); + authors.add( "author2" ); + + List books = new ArrayList<>(); + books.add( "book1" ); + books.add( "book2" ); + Map booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher1", "book1" ); + booksByPublisher.put( "publisher2", "book2" ); + Issue3806Mapper.Target target = new Issue3806Mapper.Target( authors, booksByAuthor ); + target.setBooks( books ); + target.setBooksByPublisher( booksByPublisher ); + + Issue3806Mapper.Target source = new Issue3806Mapper.Target( null, null ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author1", "author2" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author1", "book1" ), + entry( "author2", "book2" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book1", "book2" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher1", "book1" ), + entry( "publisher2", "book2" ) + ); + + booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author3", "book3" ); + authors = new ArrayList<>(); + authors.add( "author3" ); + + books = new ArrayList<>(); + books.add( "book3" ); + booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher3", "book3" ); + source = new Issue3806Mapper.Target( authors, booksByAuthor ); + source.setBooks( books ); + source.setBooksByPublisher( booksByPublisher ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author3" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author3", "book3" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book3" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher3", "book3" ) + ); + } +} From bff88297e367ec856a1c5a5f68fcc9a62662ee70 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 19:29:39 +0800 Subject: [PATCH 020/125] #3807: Properly recognize the type of public generic fields Signed-off-by: TangYang --- .../ap/internal/model/BeanMappingMethod.java | 4 +- .../mapstruct/ap/internal/util/Filters.java | 23 +++++---- .../util/accessor/AbstractAccessor.java | 41 ---------------- .../util/accessor/ElementAccessor.java | 46 ++++++++++++++---- .../accessor/ExecutableElementAccessor.java | 41 ---------------- .../accessor/ParameterElementAccessor.java | 48 ------------------- .../internal/util/accessor/ReadAccessor.java | 10 ++-- .../util/accessor/RecordElementAccessor.java | 38 --------------- .../ap/test/bugs/_3807/Issue3807Mapper.java | 48 +++++++++++++++++++ .../ap/test/bugs/_3807/Issue3807Test.java | 32 +++++++++++++ 10 files changed, 140 insertions(+), 191 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 34c1ea3ccb..b5208d302b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -65,7 +65,7 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -1067,7 +1067,7 @@ private Accessor createConstructorAccessor(Element element, TypeMirror accessedT existingVariableNames ); existingVariableNames.add( safeParameterName ); - return new ParameterElementAccessor( element, accessedType, safeParameterName ); + return new ElementAccessor( element, accessedType, safeParameterName ); } private boolean hasDefaultAnnotationFromAnyPackage(Element element) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 5f7fe74bf2..6492270705 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -12,7 +12,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -23,7 +23,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -64,7 +64,7 @@ public Filters(AccessorNamingUtils accessorNaming, TypeUtils typeUtils, TypeMirr public List getterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isGetterMethod ) - .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) + .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -90,7 +90,10 @@ public Map recordAccessorsIn(Collection recordCom for ( Element recordComponent : recordComponents ) { recordAccessors.put( recordComponent.getSimpleName().toString(), - ReadAccessor.fromRecordComponent( recordComponent ) + ReadAccessor.fromRecordComponent( + recordComponent, + typeUtils.asMemberOf( (DeclaredType) typeMirror, recordComponent ) + ) ); } @@ -101,10 +104,10 @@ private TypeMirror getReturnType(ExecutableElement executableElement) { return getWithinContext( executableElement ).getReturnType(); } - public List fieldsIn(List accessors, Function creator) { + public List fieldsIn(List accessors, BiFunction creator) { return accessors.stream() .filter( Fields::isFieldAccessor ) - .map( creator ) + .map( variableElement -> creator.apply( variableElement, getWithinContext( variableElement ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -117,7 +120,7 @@ public List presenceCheckMethodsIn(List el public List setterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isSetterMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), SETTER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -129,10 +132,14 @@ private ExecutableType getWithinContext( ExecutableElement executableElement ) { return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); } + private TypeMirror getWithinContext( VariableElement variableElement ) { + return typeUtils.asMemberOf( (DeclaredType) typeMirror, variableElement ); + } + public List adderMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isAdderMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), ADDER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), ADDER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java deleted file mode 100644 index 04c4c99992..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java +++ /dev/null @@ -1,41 +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.ap.internal.util.accessor; - -import java.util.Set; - -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; - -/** - * This is an abstract implementation of an {@link Accessor} that provides the common implementation. - * - * @author Filip Hrisafov - */ -abstract class AbstractAccessor implements Accessor { - - protected final T element; - - AbstractAccessor(T element) { - this.element = element; - } - - @Override - public String getSimpleName() { - return element.getSimpleName().toString(); - } - - @Override - public Set getModifiers() { - return element.getModifiers(); - } - - @Override - public T getElement() { - return element; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java index 24e71cc85f..4b363815b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -5,31 +5,61 @@ */ package org.mapstruct.ap.internal.util.accessor; +import java.util.Set; import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** - * An {@link Accessor} that wraps a {@link VariableElement}. - * + * An {@link Accessor} that wraps a {@link Element}. + * Used for getter, setter, filed, constructor, record-class, etc. * @author Filip Hrisafov + * @author Tang Yang */ -public class ElementAccessor extends AbstractAccessor { +public class ElementAccessor implements Accessor { + private final Element element; + private final String name; private final AccessorType accessorType; + private final TypeMirror accessedType; + + public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) { + this( variableElement, accessedType, AccessorType.FIELD ); + } - public ElementAccessor(VariableElement variableElement) { - this( variableElement, AccessorType.FIELD ); + public ElementAccessor(Element element, TypeMirror accessedType, String name) { + this.element = element; + this.name = name; + this.accessedType = accessedType; + this.accessorType = AccessorType.PARAMETER; } - public ElementAccessor(Element element, AccessorType accessorType) { - super( element ); + public ElementAccessor(Element element, TypeMirror accessedType, AccessorType accessorType) { + this.element = element; + this.accessedType = accessedType; this.accessorType = accessorType; + this.name = null; } @Override public TypeMirror getAccessedType() { - return element.asType(); + return accessedType != null ? accessedType : element.asType(); + } + + @Override + public String getSimpleName() { + return name != null ? name : element.getSimpleName().toString(); + } + + @Override + public Set getModifiers() { + return element.getModifiers(); + } + + @Override + public Element getElement() { + return element; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java deleted file mode 100644 index af37acbce1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java +++ /dev/null @@ -1,41 +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.ap.internal.util.accessor; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps an {@link ExecutableElement}. - * - * @author Filip Hrisafov - */ -public class ExecutableElementAccessor extends AbstractAccessor { - - private final TypeMirror accessedType; - private final AccessorType accessorType; - - public ExecutableElementAccessor(ExecutableElement element, TypeMirror accessedType, AccessorType accessorType) { - super( element ); - this.accessedType = accessedType; - this.accessorType = accessorType; - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return accessorType; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java deleted file mode 100644 index 9991370059..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java +++ /dev/null @@ -1,48 +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.ap.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class ParameterElementAccessor extends AbstractAccessor { - - protected final String name; - protected final TypeMirror accessedType; - - public ParameterElementAccessor(Element element, TypeMirror accessedType, String name) { - super( element ); - this.name = name; - this.accessedType = accessedType; - } - - @Override - public String getSimpleName() { - return name != null ? name : super.getSimpleName(); - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.PARAMETER; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 5177bfc75b..31edf3a6da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -17,8 +17,8 @@ public interface ReadAccessor extends Accessor { String getReadValueSource(); - static ReadAccessor fromField(VariableElement variableElement) { - return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { + static ReadAccessor fromField(VariableElement variableElement, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement, accessedType ) ) { @Override public String getReadValueSource() { return getSimpleName(); @@ -26,8 +26,8 @@ public String getReadValueSource() { }; } - static ReadAccessor fromRecordComponent(Element element) { - return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { + static ReadAccessor fromRecordComponent(Element element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; @@ -36,7 +36,7 @@ public String getReadValueSource() { } static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { - return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java deleted file mode 100644 index d163f462f9..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java +++ /dev/null @@ -1,38 +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.ap.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class RecordElementAccessor extends AbstractAccessor { - - public RecordElementAccessor(Element element) { - super( element ); - } - - @Override - public TypeMirror getAccessedType() { - return element.asType(); - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.GETTER; - } - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java new file mode 100644 index 0000000000..83ee1f32bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3807; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3807Mapper { + Issue3807Mapper INSTANCE = Mappers.getMapper( Issue3807Mapper.class ); + + TargetWithoutSetter mapNoSetter(Source target); + + NormalTarget mapNormalSource(Source target); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + //CHECKSTYLE:OFF + class TargetWithoutSetter { + public T value; + } + //CHECKSTYLE:ON + + class NormalTarget { + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java new file mode 100644 index 0000000000..3b895f3e11 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3807; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses(Issue3807Mapper.class) +@IssueKey("3087") +class Issue3807Test { + + @ProcessorTest + void fieldAndSetterShouldWorkWithGeneric() { + Issue3807Mapper.Source source = new Issue3807Mapper.Source( "value" ); + Issue3807Mapper.TargetWithoutSetter targetWithoutSetter = + Issue3807Mapper.INSTANCE.mapNoSetter( source ); + + assertThat( targetWithoutSetter ).isNotNull(); + assertThat( targetWithoutSetter.value ).isEqualTo( "value" ); + + Issue3807Mapper.NormalTarget normalTarget = Issue3807Mapper.INSTANCE.mapNormalSource( source ); + + assertThat( normalTarget ).isNotNull(); + assertThat( normalTarget.getValue() ).isEqualTo( "value" ); + } +} From ce84c81de2ee809c1457dfb9078ec1ae785ad323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 23:52:05 +0800 Subject: [PATCH 021/125] #3659: Support `@AnnotatedWith` on decorators Signed-off-by: TangYang --- .../ap/internal/model/Decorator.java | 20 ++- ...nnotationBasedComponentModelProcessor.java | 6 +- .../processor/JakartaComponentProcessor.java | 3 +- .../processor/Jsr330ComponentProcessor.java | 3 +- .../processor/MapperCreationProcessor.java | 5 + .../processor/SpringComponentProcessor.java | 130 ++++++++++++------ .../ap/test/decorator/AnnotatedMapper.java | 43 ++++++ .../decorator/AnnotatedMapperDecorator.java | 18 +++ .../DecoratedWithAnnotatedWithTest.java | 32 +++++ .../ap/test/decorator/TestAnnotation.java | 20 +++ .../JakartaAnnotateWithMapper.java | 40 ++++++ ...akartaAnnotateWithWithMapperDecorator.java | 31 +++++ .../JakartaDecoratorAnnotateWithTest.java | 59 ++++++++ .../decorator/jsr330/Jsr330DecoratorTest.java | 10 +- .../Jsr330AnnotateWithMapper.java | 28 ++++ .../Jsr330AnnotateWithMapperDecorator.java | 32 +++++ .../Jsr330DecoratorAnnotateWithTest.java | 95 +++++++++++++ .../spring/annotatewith/AnnotateMapper.java | 25 ++++ .../annotatewith/AnnotateMapperDecorator.java | 30 ++++ .../annotatewith/CustomAnnotateMapper.java | 25 ++++ .../CustomAnnotateMapperDecorator.java | 28 ++++ .../spring/annotatewith/CustomComponent.java | 20 +++ .../spring/annotatewith/CustomPrimary.java | 19 +++ .../SpringDecoratorAnnotateWithTest.java | 118 ++++++++++++++++ 24 files changed, 791 insertions(+), 49 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java index b7dc0effcb..df7940da31 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java @@ -7,14 +7,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.SortedSet; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.version.VersionInformation; /** @@ -33,6 +34,7 @@ public static class Builder extends GeneratedTypeBuilder { private String implName; private String implPackage; private boolean suppressGeneratorTimestamp; + private Set customAnnotations; public Builder() { super( Builder.class ); @@ -68,6 +70,11 @@ public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { return this; } + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; + return this; + } + public Decorator build() { String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER, Mapper.getFlatName( mapperElement ) ); @@ -95,7 +102,8 @@ public Decorator build() { suppressGeneratorTimestamp, Accessibility.fromModifiers( mapperElement.getModifiers() ), extraImportedTypes, - decoratorConstructor + decoratorConstructor, + customAnnotations ); } } @@ -110,7 +118,8 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type Options options, VersionInformation versionInformation, boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImports, - DecoratorConstructor decoratorConstructor) { + DecoratorConstructor decoratorConstructor, + Set customAnnotations) { super( typeFactory, packageName, @@ -128,6 +137,11 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type this.decoratorType = decoratorType; this.mapperType = mapperType; + + // Add custom annotations + if ( customAnnotations != null ) { + customAnnotations.forEach( this::addAnnotation ); + } } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index 014ceda58e..bbd695412a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.AnnotatedConstructor; import org.mapstruct.ap.internal.model.AnnotatedSetter; import org.mapstruct.ap.internal.model.Annotation; @@ -24,7 +25,6 @@ import org.mapstruct.ap.internal.model.MapperReference; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.source.MapperOptions; /** @@ -88,7 +88,7 @@ else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) { Decorator decorator = mapper.getDecorator(); - for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { + for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) { decorator.addAnnotation( typeAnnotation ); } @@ -308,7 +308,7 @@ protected Field replacementMapperReference(Field originalReference, List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Collections.emptyList(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java index 999c0a51df..86c95d612b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -39,7 +40,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 00ba72a079..7770eff16c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -42,7 +43,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 3b38a05b59..4fbed6cbec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder; +import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.BeanMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; @@ -287,6 +288,9 @@ else if ( constructor.getParameters().size() == 1 ) { messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_CONSTRUCTOR ); } + // Get annotations from the decorator class + Set decoratorAnnotations = additionalAnnotationsBuilder.getProcessedAnnotations( decoratorElement ); + Decorator decorator = new Decorator.Builder() .elementUtils( elementUtils ) .typeFactory( typeFactory ) @@ -300,6 +304,7 @@ else if ( constructor.getParameters().size() == 1 ) { .implPackage( mapperOptions.implementationPackage() ) .extraImports( getExtraImports( element, mapperOptions ) ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( decoratorAnnotations ) .build(); return decorator; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 84a672bcc9..c31ac51f06 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -9,20 +9,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; - import static javax.lang.model.element.ElementKind.PACKAGE; /** @@ -35,6 +37,9 @@ */ public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + private static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component"; + private static final String SPRING_PRIMARY_ANNOTATION = "org.springframework.context.annotation.Primary"; + @Override protected String getComponentModelIdentifier() { return MappingConstantsGem.ComponentModelGem.SPRING; @@ -54,12 +59,37 @@ protected List getTypeAnnotations(Mapper mapper) { return typeAnnotations; } + /** + * Returns the annotations that need to be added to the generated decorator, filtering out any annotations + * that are already present or represented as meta-annotations. + * + * @param decorator the decorator to process + * @return A list of annotations that should be added to the generated decorator. + */ @Override - protected List getDecoratorAnnotations() { - return Arrays.asList( - component(), - primary() - ); + protected List getDecoratorAnnotations(Decorator decorator) { + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + desiredAnnotationNames.add( SPRING_PRIMARY_ANNOTATION ); + List decoratorAnnotations = decorator.getAnnotations(); + if ( !decoratorAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : decoratorAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return Collections.emptyList(); + } + } + } + + return desiredAnnotationNames.stream() + .map( this::createAnnotation ) + .collect( Collectors.toList() ); } @Override @@ -82,8 +112,12 @@ protected boolean requiresGenerationOfDecoratorClass() { return true; } + private Annotation createAnnotation(String canonicalName) { + return new Annotation( getTypeFactory().getType( canonicalName ) ); + } + private Annotation autowired() { - return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) ); + return createAnnotation( "org.springframework.beans.factory.annotation.Autowired" ); } private Annotation qualifierDelegate() { @@ -96,34 +130,51 @@ private Annotation qualifierDelegate() { ) ) ); } - private Annotation primary() { - return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) ); - } - private Annotation component() { - return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ); + return createAnnotation( SPRING_COMPONENT_ANNOTATION ); } private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) { - Set handledElements = new HashSet<>(); - return mapper.getAnnotations() - .stream() - .anyMatch( - annotation -> isOrIncludesComponentAnnotation( annotation, handledElements ) - ); - } + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + + List mapperAnnotations = mapper.getAnnotations(); + if ( !mapperAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : mapperAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return true; + } + } + } - private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set handledElements) { - return isOrIncludesComponentAnnotation( - annotation.getType().getTypeElement(), handledElements - ); + return false; } - private boolean isOrIncludesComponentAnnotation(Element element, Set handledElements) { - if ( "org.springframework.stereotype.Component".equals( - ( (TypeElement) element ).getQualifiedName().toString() - )) { - return true; + /** + * Removes all the annotations and meta-annotations from {@code annotations} which are on the given element. + * + * @param element the element to check + * @param annotations the annotations to check for + * @param handledElements set of already handled elements to avoid infinite recursion + */ + private void removeAnnotationsPresentOnElement(Element element, Set annotations, + Set handledElements) { + if ( annotations.isEmpty() ) { + return; + } + if ( element instanceof TypeElement && + annotations.remove( ( (TypeElement) element ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { @@ -132,17 +183,16 @@ private boolean isOrIncludesComponentAnnotation(Element element, Set ha if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) && !handledElements.contains( annotationMirrorElement ) ) { handledElements.add( annotationMirrorElement ); - boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation( - annotationMirrorElement, handledElements - ); - - if ( isOrIncludesComponentAnnotation ) { - return true; + if ( annotations.remove( ( (TypeElement) annotationMirrorElement ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } + + removeAnnotationsPresentOnElement( element, annotations, handledElements ); } } - - return false; } private PackageElement getPackageOf( Element element ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java new file mode 100644 index 0000000000..6da0d70c93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +@DecoratedWith(AnnotatedMapperDecorator.class) +public interface AnnotatedMapper { + + AnnotatedMapper INSTANCE = Mappers.getMapper( AnnotatedMapper.class ); + + Target toTarget(Source source); + + class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java new file mode 100644 index 0000000000..6bf7a1fd66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.AnnotateWith; + +@AnnotateWith(value = TestAnnotation.class, elements = @AnnotateWith.Element(strings = "decoratorValue")) +public abstract class AnnotatedMapperDecorator implements AnnotatedMapper { + + private final AnnotatedMapper delegate; + + public AnnotatedMapperDecorator(AnnotatedMapper delegate) { + this.delegate = delegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java new file mode 100644 index 0000000000..1e3d574a6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotatedWith on decorator classes. + */ +@IssueKey("3659") +@WithClasses({ + TestAnnotation.class, + AnnotatedMapper.class, + AnnotatedMapperDecorator.class +}) +public class DecoratedWithAnnotatedWithTest { + + @ProcessorTest + public void shouldApplyAnnotationFromDecorator() { + Class implementationClass = AnnotatedMapper.INSTANCE.getClass(); + + assertThat( implementationClass ).hasAnnotation( TestAnnotation.class ); + assertThat( implementationClass.getAnnotation( TestAnnotation.class ).value() ).isEqualTo( "decoratorValue" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java new file mode 100644 index 0000000000..742184d261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation for testing @AnnotatedWith on decorators. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java new file mode 100644 index 0000000000..a1a754158b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jakarta.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using Jakarta component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +@DecoratedWith(JakartaAnnotateWithWithMapperDecorator.class) +public interface JakartaAnnotateWithMapper { + + /** + * Maps a person to a person DTO. + * + * @param person the person to map + * @return the person DTO + */ + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + /** + * Maps an address to an address DTO. + * + * @param address the address to map + * @return the address DTO + */ + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java new file mode 100644 index 0000000000..89a42aaf6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java @@ -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.ap.test.decorator.jakarta.annotatewith; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link JakartaAnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class JakartaAnnotateWithWithMapperDecorator implements JakartaAnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.annotatewith.JakartaAnnotateWithMapperImpl_") + private JakartaAnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..d98e26b217 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java @@ -0,0 +1,59 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jakarta.annotatewith; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test for the application of @AnnotateWith on decorator classes with Jakarta component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + JakartaAnnotateWithMapper.class, + TestAnnotation.class, + JakartaAnnotateWithWithMapperDecorator.class +}) +@WithJakartaInject +public class JakartaDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( JakartaAnnotateWithMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@TestAnnotation" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( JakartaAnnotateWithMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index be1a67cf09..a29310b9ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -18,6 +18,7 @@ import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330DecoratorAnnotateWithTest; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -27,6 +28,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import static java.lang.System.lineSeparator; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +47,13 @@ PersonMapperDecorator.class }) @IssueKey("592") -@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) +@ComponentScan( + basePackageClasses = Jsr330DecoratorTest.class, + excludeFilters = @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = { Jsr330DecoratorAnnotateWithTest.class } + ) +) @Configuration @WithJavaxInject @DisabledOnJre(JRE.OTHER) diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java new file mode 100644 index 0000000000..7e6ab8b7bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java @@ -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.ap.test.decorator.jsr330.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using JSR-330 component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(Jsr330AnnotateWithMapperDecorator.class) +public interface Jsr330AnnotateWithMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java new file mode 100644 index 0000000000..96cee9f824 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jsr330.annotatewith; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link Jsr330AnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class Jsr330AnnotateWithMapperDecorator implements Jsr330AnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330AnnotateWithMapperImpl_") + private Jsr330AnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..19d77dd5cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java @@ -0,0 +1,95 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.jsr330.annotatewith; + +import java.util.Calendar; +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with JSR-330 component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + Jsr330AnnotateWithMapper.class, + Jsr330AnnotateWithMapperDecorator.class, + TestAnnotation.class +}) +@ComponentScan(basePackageClasses = Jsr330DecoratorAnnotateWithTest.class) +@Configuration +@WithJavaxInject +@DisabledOnJre(JRE.OTHER) +public class Jsr330DecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private Jsr330AnnotateWithMapper jsr330AnnotateWithMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldContainCustomAnnotation() { + generatedSource.forMapper( Jsr330AnnotateWithMapper.class ) + .content() + .contains( "@TestAnnotation" ); + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = jsr330AnnotateWithMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java new file mode 100644 index 0000000000..0ba8e88299 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java @@ -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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(AnnotateMapperDecorator.class) +public interface AnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java new file mode 100644 index 0000000000..cff0a4c148 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = "decoratorComponent")) +@AnnotateWith(value = Primary.class) +public abstract class AnnotateMapperDecorator implements AnnotateMapper { + + @Autowired + @Qualifier("delegate") + private AnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java new file mode 100644 index 0000000000..9e2e35676c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java @@ -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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(CustomAnnotateMapperDecorator.class) +public interface CustomAnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java new file mode 100644 index 0000000000..3668d98b04 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java @@ -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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@AnnotateWith(value = CustomComponent.class, elements = @AnnotateWith.Element(strings = "customComponentDecorator")) +@AnnotateWith(value = CustomPrimary.class) +public abstract class CustomAnnotateMapperDecorator implements CustomAnnotateMapper { + + @Autowired + @Qualifier("delegate") + private CustomAnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java new file mode 100644 index 0000000000..be71249f2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomComponent { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java new file mode 100644 index 0000000000..4e18bdbc7d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Primary; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Primary +public @interface CustomPrimary { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..7b3911c22b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java @@ -0,0 +1,118 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.decorator.spring.annotatewith; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with Spring component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + AnnotateMapper.class, + AnnotateMapperDecorator.class, + CustomComponent.class, + CustomPrimary.class, + CustomAnnotateMapper.class, + CustomAnnotateMapperDecorator.class +}) +@WithSpring +@ComponentScan(basePackageClasses = SpringDecoratorAnnotateWithTest.class) +@Configuration +public class SpringDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private AnnotateMapper annotateMapper; + + @Autowired + private CustomAnnotateMapper customAnnotateMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldNotDuplicateComponentAnnotation() { + generatedSource.forMapper( AnnotateMapper.class ) + .content() + .contains( "@Component(value = \"decoratorComponent\")", "@Primary" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + } + + @ProcessorTest + public void shouldNotDuplicateCustomComponentAnnotation() { + generatedSource.forMapper( CustomAnnotateMapper.class ) + .content() + .contains( "@CustomComponent(value = \"customComponentDecorator\")", "@CustomPrimary" ) + .doesNotContain( "@Component" ); + } + + @ProcessorTest + public void shouldInvokeAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = annotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldInvokeCustomAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = customAnnotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} From 9847eaf195cb891c5a47df96db2b90a1b335b75b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 May 2025 18:14:13 +0200 Subject: [PATCH 022/125] #3876: Move Windows and Mac OS builds outside of the main workflow --- .github/workflows/macos.yml | 20 ++++++++++++++++++++ .github/workflows/main.yml | 24 ------------------------ .github/workflows/windows.yml | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000000..833bb6ba39 --- /dev/null +++ b/.github/workflows/macos.yml @@ -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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 166466348d..b6a2ef371e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,27 +58,3 @@ jobs: java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest - 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 - mac: - name: 'Mac OS' - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - name: 'Set up JDK 21' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 21 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..bda38f8783 --- /dev/null +++ b/.github/workflows/windows.yml @@ -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 From 46ce011e4bd9b9fbcc0f0d337d5ad631f5d214b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Jun 2025 07:52:18 +0200 Subject: [PATCH 023/125] Refactor options and add an enum (#3877) --- .../org/mapstruct/ap/MappingProcessor.java | 101 ++++++------------ .../internal/model/source/DefaultOptions.java | 20 ++-- .../ap/internal/option/MappingOption.java | 43 ++++++++ .../mapstruct/ap/internal/option/Options.java | 89 +++++++-------- 4 files changed, 128 insertions(+), 125 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index b34b133262..a66762d739 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -13,17 +13,16 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -35,9 +34,8 @@ import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; -import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.option.MappingOption; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; @@ -84,19 +82,6 @@ * @author Gunnar Morling */ @SupportedAnnotationTypes("org.mapstruct.Mapper") -@SupportedOptions({ - MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, - MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, - MappingProcessor.UNMAPPED_TARGET_POLICY, - MappingProcessor.UNMAPPED_SOURCE_POLICY, - MappingProcessor.DEFAULT_COMPONENT_MODEL, - MappingProcessor.DEFAULT_INJECTION_STRATEGY, - MappingProcessor.DISABLE_BUILDERS, - MappingProcessor.VERBOSE, - MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, - MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, - MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, -}) public class MappingProcessor extends AbstractProcessor { /** @@ -104,20 +89,32 @@ public class MappingProcessor extends AbstractProcessor { */ private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false; - protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; - protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = - "mapstruct.suppressGeneratorVersionInfoComment"; - protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; - protected static final String UNMAPPED_SOURCE_POLICY = "mapstruct.unmappedSourcePolicy"; - protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; - protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; - protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; - protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; - protected static final String VERBOSE = "mapstruct.verbose"; - protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; - protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; - protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = - "mapstruct.disableLifecycleOverloadDeduplicateSelector"; + // CHECKSTYLE:OFF + // Deprecated options, kept for backwards compatibility. + // They will be removed in a future release. + @Deprecated + protected static final String SUPPRESS_GENERATOR_TIMESTAMP = MappingOption.SUPPRESS_GENERATOR_TIMESTAMP.getOptionName(); + @Deprecated + protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT.getOptionName(); + @Deprecated + protected static final String UNMAPPED_TARGET_POLICY = MappingOption.UNMAPPED_TARGET_POLICY.getOptionName(); + @Deprecated + protected static final String UNMAPPED_SOURCE_POLICY = MappingOption.UNMAPPED_SOURCE_POLICY.getOptionName(); + @Deprecated + protected static final String DEFAULT_COMPONENT_MODEL = MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName(); + @Deprecated + protected static final String DEFAULT_INJECTION_STRATEGY = MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName(); + @Deprecated + protected static final String ALWAYS_GENERATE_SERVICE_FILE = MappingOption.ALWAYS_GENERATE_SERVICE_FILE.getOptionName(); + @Deprecated + protected static final String DISABLE_BUILDERS = MappingOption.DISABLE_BUILDERS.getOptionName(); + @Deprecated + protected static final String VERBOSE = MappingOption.VERBOSE.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY.getOptionName(); + // CHECKSTYLE:ON private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -156,7 +153,7 @@ public MappingProcessor() { public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); - options = createOptions(); + options = new Options( processingEnv.getOptions() ); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), @@ -171,34 +168,6 @@ public synchronized void init(ProcessingEnvironment processingEnv) { } } - private Options createOptions() { - String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); - String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); - String nullValueIterableMappingStrategy = processingEnv.getOptions() - .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); - String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); - String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() - .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); - - return new Options( - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), - unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, - unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, - processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), - Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ), - nullValueIterableMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : - null, - nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, - Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) - ); - } - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -259,13 +228,11 @@ else if ( !deferredMappers.isEmpty() ) { @Override public Set getSupportedOptions() { - Set supportedOptions = super.getSupportedOptions(); - if ( additionalSupportedOptions.isEmpty() ) { - return supportedOptions; - } - Set allSupportedOptions = new HashSet<>( supportedOptions ); - allSupportedOptions.addAll( additionalSupportedOptions ); - return allSupportedOptions; + return Stream.concat( + Stream.of( MappingOption.values() ).map( MappingOption::getOptionName ), + additionalSupportedOptions.stream() + ) + .collect( Collectors.toSet() ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index e1f04fd941..8d76e65c75 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -56,16 +56,18 @@ public Set imports() { @Override public ReportingPolicyGem unmappedTargetPolicy() { - if ( options.getUnmappedTargetPolicy() != null ) { - return options.getUnmappedTargetPolicy(); + ReportingPolicyGem unmappedTargetPolicy = options.getUnmappedTargetPolicy(); + if ( unmappedTargetPolicy != null ) { + return unmappedTargetPolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedTargetPolicy().getDefaultValue() ); } @Override public ReportingPolicyGem unmappedSourcePolicy() { - if ( options.getUnmappedSourcePolicy() != null ) { - return options.getUnmappedSourcePolicy(); + ReportingPolicyGem unmappedSourcePolicy = options.getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy != null ) { + return unmappedSourcePolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().getDefaultValue() ); } @@ -77,8 +79,9 @@ public ReportingPolicyGem typeConversionPolicy() { @Override public String componentModel() { - if ( options.getDefaultComponentModel() != null ) { - return options.getDefaultComponentModel(); + String defaultComponentModel = options.getDefaultComponentModel(); + if ( defaultComponentModel != null ) { + return defaultComponentModel; } return mapper.componentModel().getDefaultValue(); } @@ -97,8 +100,9 @@ public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { @Override public InjectionStrategyGem getInjectionStrategy() { - if ( options.getDefaultInjectionStrategy() != null ) { - return InjectionStrategyGem.valueOf( options.getDefaultInjectionStrategy().toUpperCase() ); + String defaultInjectionStrategy = options.getDefaultInjectionStrategy(); + if ( defaultInjectionStrategy != null ) { + return InjectionStrategyGem.valueOf( defaultInjectionStrategy.toUpperCase() ); } return InjectionStrategyGem.valueOf( mapper.injectionStrategy().getDefaultValue() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java new file mode 100644 index 0000000000..d35b2c0dc6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.option; + +/** + * @author Filip Hrisafov + */ +public enum MappingOption { + + // CHECKSTYLE:OFF + SUPPRESS_GENERATOR_TIMESTAMP( "mapstruct.suppressGeneratorTimestamp" ), + SUPPRESS_GENERATOR_VERSION_INFO_COMMENT( "mapstruct.suppressGeneratorVersionInfoComment" ), + UNMAPPED_TARGET_POLICY("mapstruct.unmappedTargetPolicy"), + UNMAPPED_SOURCE_POLICY("mapstruct.unmappedSourcePolicy"), + DEFAULT_COMPONENT_MODEL("mapstruct.defaultComponentModel"), + DEFAULT_INJECTION_STRATEGY("mapstruct.defaultInjectionStrategy"), + ALWAYS_GENERATE_SERVICE_FILE("mapstruct.alwaysGenerateServicesFile"), + DISABLE_BUILDERS("mapstruct.disableBuilders"), + VERBOSE("mapstruct.verbose"), + NULL_VALUE_ITERABLE_MAPPING_STRATEGY("mapstruct.nullValueIterableMappingStrategy"), + NULL_VALUE_MAP_MAPPING_STRATEGY("mapstruct.nullValueMapMappingStrategy"), + DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR("mapstruct.disableLifecycleOverloadDeduplicateSelector"), + ; + // CHECKSTYLE:ON + + private final String optionName; + + MappingOption(String optionName) { + this.optionName = optionName; + } + + /** + * Returns the name of the option, which can be used in the compiler arguments. + * + * @return the name of the option + */ + public String getOptionName() { + return optionName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 095c472c87..38474baf9c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -5,6 +5,9 @@ */ package org.mapstruct.ap.internal.option; +import java.util.Locale; +import java.util.Map; + import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; @@ -13,93 +16,79 @@ * * @author Andreas Gudian * @author Gunnar Morling + * @author Filip Hrisafov */ public class Options { - private final boolean suppressGeneratorTimestamp; - private final boolean suppressGeneratorVersionComment; - private final ReportingPolicyGem unmappedTargetPolicy; - private final ReportingPolicyGem unmappedSourcePolicy; - private final boolean alwaysGenerateSpi; - private final String defaultComponentModel; - private final String defaultInjectionStrategy; - private final boolean disableBuilders; - private final boolean verbose; - private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; - private final NullValueMappingStrategyGem nullValueMapMappingStrategy; - private final boolean disableLifecycleOverloadDeduplicateSelector; - - //CHECKSTYLE:OFF - public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, - ReportingPolicyGem unmappedTargetPolicy, - ReportingPolicyGem unmappedSourcePolicy, - String defaultComponentModel, String defaultInjectionStrategy, - boolean alwaysGenerateSpi, - boolean disableBuilders, - boolean verbose, - NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy, - boolean disableLifecycleOverloadDeduplicateSelector - ) { - //CHECKSTYLE:ON - this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; - this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; - this.unmappedTargetPolicy = unmappedTargetPolicy; - this.unmappedSourcePolicy = unmappedSourcePolicy; - this.defaultComponentModel = defaultComponentModel; - this.defaultInjectionStrategy = defaultInjectionStrategy; - this.alwaysGenerateSpi = alwaysGenerateSpi; - this.disableBuilders = disableBuilders; - this.verbose = verbose; - this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; - this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; - this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; + + private final Map options; + + public Options(Map options) { + this.options = options; } public boolean isSuppressGeneratorTimestamp() { - return suppressGeneratorTimestamp; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_TIMESTAMP ); } public boolean isSuppressGeneratorVersionComment() { - return suppressGeneratorVersionComment; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ); } public ReportingPolicyGem getUnmappedTargetPolicy() { - return unmappedTargetPolicy; + return parseEnum( MappingOption.UNMAPPED_TARGET_POLICY, ReportingPolicyGem.class ); } public ReportingPolicyGem getUnmappedSourcePolicy() { - return unmappedSourcePolicy; + return parseEnum( MappingOption.UNMAPPED_SOURCE_POLICY, ReportingPolicyGem.class ); } public String getDefaultComponentModel() { - return defaultComponentModel; + return options.get( MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName() ); } public String getDefaultInjectionStrategy() { - return defaultInjectionStrategy; + return options.get( MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName() ); } public boolean isAlwaysGenerateSpi() { - return alwaysGenerateSpi; + return parseBoolean( MappingOption.ALWAYS_GENERATE_SERVICE_FILE ); } public boolean isDisableBuilders() { - return disableBuilders; + return parseBoolean( MappingOption.DISABLE_BUILDERS ); } public boolean isVerbose() { - return verbose; + return parseBoolean( MappingOption.VERBOSE ); } public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { - return nullValueIterableMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { - return nullValueMapMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public boolean isDisableLifecycleOverloadDeduplicateSelector() { - return disableLifecycleOverloadDeduplicateSelector; + return parseBoolean( MappingOption.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); + } + + private boolean parseBoolean(MappingOption option) { + if ( options.isEmpty() ) { + return false; + } + return Boolean.parseBoolean( options.get( option.getOptionName() ) ); + } + + private > E parseEnum(MappingOption option, Class enumType) { + if ( options.isEmpty() ) { + return null; + } + String value = options.get( option.getOptionName() ); + if ( value == null ) { + return null; + } + return Enum.valueOf( enumType, value.toUpperCase( Locale.ROOT ) ); } } From d68819a233030b5d863e4d4645787a84e7db7d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:02:23 +0000 Subject: [PATCH 024/125] 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] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index f6ad60a22a..35fc24e96b 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 6.2.2 + 6.2.7 1.6.0 8.36.1 5.10.1 From f4d18181719b821bc319f53314b7c8e19e929e16 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 21:33:19 +0200 Subject: [PATCH 025/125] Fix issue key in Issue3807Test --- .../java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java index 3b895f3e11..db82472f98 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; @WithClasses(Issue3807Mapper.class) -@IssueKey("3087") +@IssueKey("3807") class Issue3807Test { @ProcessorTest From c90c93630e8580e330b09bcdd6f2bc99b08bdee6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 22:13:56 +0200 Subject: [PATCH 026/125] #3886: Records do not have property write accessors (apart from the record components) --- processor/pom.xml | 1 + .../ap/internal/model/common/Type.java | 4 +++ .../bugs/_3886/jdk21/Issue3886Mapper.java | 31 +++++++++++++++++++ .../test/bugs/_3886/jdk21/Issue3886Test.java | 25 +++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java diff --git a/processor/pom.xml b/processor/pom.xml index 2341c4ddb0..87c3517018 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -286,6 +286,7 @@ org.apache.maven.plugins maven-compiler-plugin + ${minimum.java.version} org.mapstruct.tools.gem diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index d0f65ce97a..24f23ecea8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -796,6 +796,10 @@ public Map getPropertyPresenceCheckers() { * @return an unmodifiable map of all write accessors indexed by property name */ public Map getPropertyWriteAccessors( CollectionMappingStrategyGem cmStrategy ) { + if ( isRecord() ) { + // Records do not have setters, so we return an empty map + return Collections.emptyMap(); + } // collect all candidate target accessors List candidates = new ArrayList<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java new file mode 100644 index 0000000000..aba305a0bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java @@ -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.ap.test.bugs._3886.jdk21; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface Issue3886Mapper { + + RangeRecord map(LocalDate validFrom); + + record RangeRecord(LocalDate validFrom) { + + public RangeRecord restrictTo(RangeRecord other) { + return null; + } + + public void setName(String name) { + // This method is here to ensure that MapStruct won't treat it as a setter + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java new file mode 100644 index 0000000000..93c068cbc7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java @@ -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.ap.test.bugs._3886.jdk21; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3886Mapper.class) +@IssueKey("3886") +class Issue3886Test { + + // The current version of the Eclipse compiler we use does not support records + @ProcessorTest(Compiler.JDK) + void shouldCompile() { + + } +} From e4bc1cdf1e39a3ff25f0a55b44bdb25837d49a69 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 15 Jun 2025 14:29:45 +0800 Subject: [PATCH 027/125] #3884 Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null Signed-off-by: TangYang --- ...anceSetterWrapperForCollectionsAndMaps.ftl | 4 + .../ap/test/bugs/_3884/DestinationType.java | 22 ++++ .../ap/test/bugs/_3884/Issue3884Mapper.java | 21 ++++ .../ap/test/bugs/_3884/Issue3884Test.java | 116 ++++++++++++++++++ .../ap/test/bugs/_3884/SourceType.java | 37 ++++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ 7 files changed, 248 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index cee722e2d3..95662c4c79 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -30,6 +30,10 @@ <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && mapNullToDefault>else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; + } + <#-- wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java new file mode 100644 index 0000000000..467b58990c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Destination type interface for testing null value property mapping strategy with Map properties. + */ +public interface DestinationType { + Map getAttributes(); + + void setAttributes(Map attributes); + + List getItems(); + + void setItems(List items); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java new file mode 100644 index 0000000000..6f6f4f924d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing null value property mapping strategy with Map properties. + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface Issue3884Mapper { + Issue3884Mapper INSTANCE = Mappers.getMapper( Issue3884Mapper.class ); + + void update(@MappingTarget DestinationType destination, SourceType source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java new file mode 100644 index 0000000000..6c789a0c29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java @@ -0,0 +1,116 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Test for issue 3884: NullValuePropertyMappingStrategy.SET_TO_DEFAULT should set target Map/Collection to default + * when source and target are all null. + */ +@IssueKey("3884") +@WithClasses({ + DestinationType.class, + SourceType.class, + Issue3884Mapper.class +}) +public class Issue3884Test { + + @ProcessorTest + public void shouldSetTargetToDefaultWhenBothSourceAndTargetAreNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldClearTargetWhenSourceIsNullAndTargetIsInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenSourceIsInitializedAndTargetIsNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenBothSourceAndTargetAreInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java new file mode 100644 index 0000000000..be5c19e01c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Source type class implementing DestinationType for testing null value property mapping strategy with Map properties. + */ +public class SourceType implements DestinationType { + private Map attributes; + private List items; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public List getItems() { + return items; + } + + @Override + public void setItems(List items) { + this.items = items; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index 76de94378b..f36e2e437d 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index f1bf0fa78f..87d2fccb26 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); From 46a9c29fcabc7ade34f39a92741449c874d7547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Thu, 31 Jul 2025 04:37:50 +0900 Subject: [PATCH 028/125] #3902: Validate unknown properties in @Ignored --- .../ap/internal/model/BeanMappingMethod.java | 60 +++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3902/ErroneousIssue3902Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3902/Issue3902Test.java | 57 +++++++++++++++ 4 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index b5208d302b..c81fc51800 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1280,40 +1280,54 @@ else if ( inheritContext.isReversed() ) { return false; } } - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); Message msg; String[] args; - if ( targetRef.getPathProperties().isEmpty() ) { - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + Element elementForMessage = mapping.getElement(); + if ( elementForMessage == null ) { + elementForMessage = method.getExecutable(); + } + + if ( mapping.isIgnored() && mapping.getElement() == null ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe(), - mostSimilarProperty + resultTypeToMap.describe() }; } else { - List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); - pathProperties.add( mostSimilarProperty ); - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; - args = new String[] { - targetPropertyName, - resultTypeToMap.describe(), - mapping.getTargetName(), - Strings.join( pathProperties, "." ) - }; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + }; + } } - ctx.getMessager() - .printMessage( - mapping.getElement(), - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args - ); + ctx.getMessager().printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args + ); return true; } else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 883b3e3792..8534758285 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,6 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java new file mode 100644 index 0000000000..07f94ce6fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -0,0 +1,69 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing bug #3902. + * + * @author znight1020 + */ +@Mapper +public interface ErroneousIssue3902Mapper { + + ErroneousIssue3902Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3902Mapper.class ); + + @Ignored(targets = {"name", "foo", "bar"}) + ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); + + @Ignored(targets = {"name", "address", "foo"}) + ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + + class Zoo { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class ZooDto { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java new file mode 100644 index 0000000000..f1332bbdc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3902; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Verifies that using an unknown property in {@code @Ignored} yields a proper + * compile error instead of an internal processor error. + * + * @author znight1020 + */ +@WithClasses({ErroneousIssue3902Mapper.class}) +@IssueKey("3902") +public class Issue3902Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + // Test case: mapWithOneKnownAndMultipleUnknowns + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"bar\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + + // Test case: mapWithMultipleKnownAndOneUnknown + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ) + } + ) + public void shouldFailOnUnknownPropertiesInIgnored() { + } +} From 29036542b85ff93a05fc5851793438ede8713f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Mon, 4 Aug 2025 15:08:22 +0900 Subject: [PATCH 029/125] #3908: Add similar suggestion for unknown property in `@Ignored` --- .../ap/internal/model/BeanMappingMethod.java | 22 ++++++++++--------- .../mapstruct/ap/internal/util/Message.java | 2 +- .../bugs/_3902/ErroneousIssue3902Mapper.java | 3 +++ .../ap/test/bugs/_3902/Issue3902Test.java | 15 ++++++++++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index c81fc51800..7973ece427 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1284,6 +1284,9 @@ else if ( inheritContext.isReversed() ) { Message msg; String[] args; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + Element elementForMessage = mapping.getElement(); if ( elementForMessage == null ) { elementForMessage = method.getExecutable(); @@ -1293,13 +1296,11 @@ else if ( inheritContext.isReversed() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe() + resultTypeToMap.describe(), + mostSimilarProperty }; } else { - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); - if ( targetRef.getPathProperties().isEmpty() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; args = new String[] { @@ -1321,12 +1322,13 @@ else if ( inheritContext.isReversed() ) { } } - ctx.getMessager().printMessage( - elementForMessage, - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args + ctx.getMessager() + .printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args ); return true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 8534758285..60aa55eb38 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -45,7 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\". Did you mean \"%s\"?"), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java index 07f94ce6fe..b646f1a08e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -25,6 +25,9 @@ public interface ErroneousIssue3902Mapper { @Ignored(targets = {"name", "address", "foo"}) ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + @Ignored(targets = {"name", "addres"}) + ZooDto mapWithTypo(Zoo source); + class Zoo { private String name; private String address; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java index f1332bbdc6..44247d8c27 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -32,14 +32,14 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), @Diagnostic( type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"bar\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), // Test case: mapWithMultipleKnownAndOneUnknown @@ -48,7 +48,16 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + + // Test case: mapWithTypo + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 29, + message = "No property named \"addres\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"address\"?" ) } ) From fe43563c8184dcf0e96a9ac9bfb40a8ace436e6f Mon Sep 17 00:00:00 2001 From: Ritesh Chopade <94113981+codeswithritesh@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:47:30 +0530 Subject: [PATCH 030/125] #3837 Add warning/error for redundant ignoreUnmappedSourceProperties entries (#3906) Adds compiler warning / error when properties listed in `ignoreUnmappedSourceProperties` are actually mapped. Respects `unmappedSourcePolicy` and includes tests for redundant and valid ignore cases. --- .../ap/internal/model/BeanMappingMethod.java | 44 +++++- .../mapstruct/ap/internal/util/Message.java | 2 + .../IgnoredMappedPropertyTest.java | 136 ++++++++++++++++++ .../ap/test/ignoreunmapped/UserDto.java | 31 ++++ .../ap/test/ignoreunmapped/UserEntity.java | 40 ++++++ .../ignoreunmapped/mapper/UserMapper.java | 19 +++ ...rMapperWithIgnorePolicyInMapperConfig.java | 24 ++++ .../UserMapperWithIgnoreSourcePolicy.java | 21 +++ ...serMapperWithWarnPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithWarnSourcePolicy.java | 21 +++ ...serMapperWithWarnSourcePolicyInMapper.java | 20 +++ .../mapper/UserMapperWithoutBeanMapping.java | 17 +++ ...erMapperWithErrorPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithErrorSourcePolicy.java | 21 +++ ...erMapperWithErrorSourcePolicyInMapper.java | 20 +++ .../erroneous/UserMapperWithMultiMapping.java | 22 +++ 16 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 7973ece427..05e0424a21 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -114,6 +114,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedTargetProperties; private Map unprocessedSourceProperties; private Set missingIgnoredSourceProperties; + private Set redundantIgnoredSourceProperties; private Set targetProperties; private final List propertyMappings = new ArrayList<>(); private final Set unprocessedSourceParameters = new HashSet<>(); @@ -278,11 +279,23 @@ else if ( !method.isUpdateMethod() ) { // get bean mapping (when specified as annotation ) this.missingIgnoredSourceProperties = new HashSet<>(); - if ( beanMapping != null ) { + this.redundantIgnoredSourceProperties = new HashSet<>(); + if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { + // Get source properties explicitly mapped using @Mapping annotations + Set mappedSourceProperties = method.getOptions().getMappings().stream() + .map( MappingOptions::getSourceName ) + .filter( Objects::nonNull ) + .collect( Collectors.toSet() ); + for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) { + // Track missing ignored properties (i.e. not in source class) if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) { missingIgnoredSourceProperties.add( ignoreUnmapped ); } + // Track redundant ignored properties (actually mapped despite being ignored) + if ( mappedSourceProperties.contains( ignoreUnmapped ) ) { + redundantIgnoredSourceProperties.add( ignoreUnmapped ); + } } } @@ -331,6 +344,7 @@ else if ( !method.isUpdateMethod() ) { } reportErrorForMissingIgnoredSourceProperties(); reportErrorForUnusedSourceParameters(); + reportErrorForRedundantIgnoredSourceProperties(); // mapNullToDefault boolean mapNullToDefault = method.getOptions() @@ -1914,6 +1928,34 @@ private void reportErrorForMissingIgnoredSourceProperties() { } } + private void reportErrorForRedundantIgnoredSourceProperties() { + if ( !redundantIgnoredSourceProperties.isEmpty() ) { + ReportingPolicyGem unmappedSourcePolicy = getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy == ReportingPolicyGem.IGNORE ) { //don't show warning + return; + } + + Message message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING; + if ( unmappedSourcePolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) { + message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR; + } + + Object[] args = new Object[] { + MessageFormat.format( + "{0,choice,1#property|1 Date: Thu, 28 Aug 2025 22:31:52 +0200 Subject: [PATCH 031/125] docs: reference gh discussions --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6064004d73..14443592e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,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 +* Discussions: [https://github.com/mapstruct/mapstruct/discussions](https://github.com/mapstruct/mapstruct/discussions) * 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. @@ -15,7 +15,7 @@ When doing changes, keep the following best practices in mind: * Use the code formatter for your IDE * [IntelliJ Formatter](https://github.com/mapstruct/mapstruct/blob/master/etc/mapstruct.xml) * Update the [reference documentation](mapstruct.org/documentation) on [mapstruct.org](mapstruct.org) where required -* Discuss new features you'd like to implement at the [Google group](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) before getting started +* Discuss new features you'd like to implement in the [discussions](https://github.com/mapstruct/mapstruct/discussions) before getting started * Create one pull request per feature * Provide a meaningful history, e.g. squash intermediary commits before submitting a pull request From 0f7f5434295093d1f452d95d3d72c10c7f37103e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 15:57:31 +0200 Subject: [PATCH 032/125] Update Distribution management to point to new Maven Central Portal --- parent/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 35fc24e96b..f90b066774 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -87,12 +87,12 @@ sonatype-nexus-staging Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ From 4e1720c2a899252de448c38eb10942f9fbbf5276 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 16:11:26 +0200 Subject: [PATCH 033/125] #3905: Using custom class Override should compile --- .../model/AbstractMappingMethodBuilder.java | 9 ++++++- .../model/NormalTypeMappingMethod.java | 6 ----- .../ap/internal/model/BeanMappingMethod.ftl | 1 - .../internal/model/IterableMappingMethod.ftl | 1 - .../ap/internal/model/MapMappingMethod.ftl | 1 - .../ap/internal/model/StreamMappingMethod.ftl | 1 - .../ap/test/bugs/_3905/Issue3905Mapper.java | 17 ++++++++++++ .../ap/test/bugs/_3905/Issue3905Test.java | 26 +++++++++++++++++++ .../ap/test/bugs/_3905/Override.java | 22 ++++++++++++++++ .../ap/test/bugs/_3905/OverrideDto.java | 22 ++++++++++++++++ 10 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 7329df5ab7..efb5887c95 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -130,7 +130,14 @@ public List getMethodAnnotations() { ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - return new ArrayList<>( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + List annotations = new ArrayList<>( + additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) + ); + + if ( method.overridesMethod() ) { + annotations.add( new Annotation( ctx.getTypeFactory().getType( Override.class ) ) ); + } + return annotations; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 79cd0e0566..89ee1baab2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -21,7 +21,6 @@ */ public abstract class NormalTypeMappingMethod extends MappingMethod { private final MethodReference factoryMethod; - private final boolean overridden; private final boolean mapNullToDefault; private final List annotations; @@ -33,7 +32,6 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { List afterMappingReferences) { super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); this.factoryMethod = factoryMethod; - this.overridden = method.overridesMethod(); this.mapNullToDefault = mapNullToDefault; this.annotations = annotations; } @@ -59,10 +57,6 @@ public boolean isMapNullToDefault() { return mapNullToDefault; } - public boolean isOverridden() { - return overridden; - } - public MethodReference getFactoryMethod() { return this.factoryMethod; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index da590bb506..059e2d77d4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#assign targetType = resultType /> <#if !existingInstanceMapping> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 4d08c44b38..7c3d3071ac 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 1227dbd27a..957dc712ee 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 7d0080a1d2..2269892b8a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable--> <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java new file mode 100644 index 0000000000..27e3ac9960 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3905Mapper { + + OverrideDto map(Override override); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java new file mode 100644 index 0000000000..f15271362d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java @@ -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.ap.test.bugs._3905; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3905") +@WithClasses({ + Issue3905Mapper.class, + Override.class, + OverrideDto.class +}) +class Issue3905Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java new file mode 100644 index 0000000000..dedc6b28dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class Override { + + private final String name; + + public Override(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java new file mode 100644 index 0000000000..531fab505b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class OverrideDto { + + private final String name; + + public OverrideDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From a0552131a5bf89f8192acb154b81bbd03d334ba4 Mon Sep 17 00:00:00 2001 From: hdulme Date: Wed, 31 Dec 2025 17:23:53 +0100 Subject: [PATCH 034/125] skip coverage upload to codecov on contributors repositories --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6a2ef371e..c4e6092723 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 21 + if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' 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' From 1c8b9107f99e986e5e28cff66ad604ca89975a8c Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:01:18 +0100 Subject: [PATCH 035/125] Update codecov-action to v5 and use token (#3967) --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4e6092723..c4def2a89d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,10 @@ jobs: run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} - 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 From 7bcab2824d0d17fe0a68d520e0c73f83a0e82588 Mon Sep 17 00:00:00 2001 From: ro-otgo <116685946+ro-otgo@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:44:21 +0100 Subject: [PATCH 036/125] Fix example 5 code comment. (#3968) Example 5: Mapping with default value should be using fullName instead of just name in order to map the property. --- core/src/main/java/org/mapstruct/Mapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index f15cfb75cc..c1923eb41a 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -118,7 +118,7 @@ * // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it * @Mapper * public interface HumanMapper { - * @Mapping(source="name", target="name", defaultValue="Somebody") + * @Mapping(source="name", target="fullName", defaultValue="Somebody") * HumanDto toHumanDto(Human human) * } * From 57790791abd0193cf0ff42d33524c69ab0eb6215 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Jan 2026 15:46:36 +0100 Subject: [PATCH 037/125] BeanMappingMethod and NestedPropertyMappingMethod simplification (#3970) * Move `returnTypeBuilder` and `userDefinedReturnType` in `BeanMappingMethod#build` instead of doing it earlier * Move source parameter presence check resolving into `PresenceCheckMethodResolver` * Simplify `NestedPropertyMappingMethod.ftl` by moving logic into java instead of freemarker --- .../internal/model/AbstractBaseBuilder.java | 4 +- .../model/AbstractMappingMethodBuilder.java | 7 +- .../ap/internal/model/BeanMappingMethod.java | 32 +++----- .../model/NestedPropertyMappingMethod.java | 79 ++++++++++++------- .../model/PresenceCheckMethodResolver.java | 4 + .../ap/internal/model/PropertyMapping.java | 2 +- .../AnyPresenceChecksPresenceCheck.java | 64 +++++++++++++++ .../processor/MapperCreationProcessor.java | 14 ---- .../model/NestedPropertyMappingMethod.ftl | 16 +--- .../AnyPresenceChecksPresenceCheck.ftl | 16 ++++ 10 files changed, 152 insertions(+), 86 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 56a1831d52..584e0260bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -9,7 +9,6 @@ import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.model.common.Assignment; -import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; @@ -73,7 +72,7 @@ private boolean isDisableSubMappingMethodsGeneration() { * * @return See above */ - Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) { + Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) { Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { @@ -87,7 +86,6 @@ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, else { forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() .forgedMethod( forgedMethod ) - .returnTypeBuilder( builderType ) .mappingContext( ctx ) .build(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index efb5887c95..3b6e3c1686 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.model; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.SourceRHS; @@ -94,12 +93,8 @@ private Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targe ForgedMethod forgedMethod = forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - return createForgedAssignment( - sourceRHS, - ctx.getTypeFactory().builderTypeFor( targetType, builder ), - forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private String getName(Type sourceType, Type targetType) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 05e0424a21..39222efc88 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -32,6 +32,7 @@ import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; +import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; @@ -52,7 +53,6 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; -import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; @@ -106,8 +106,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { public static class Builder extends AbstractMappingMethodBuilder { - private Type userDefinedReturnType; - /* returnType to construct can have a builder */ private BuilderType returnTypeBuilder; private Map unprocessedConstructorProperties; @@ -135,16 +133,6 @@ protected boolean shouldUsePropertyNamesInHistory() { return true; } - public Builder userDefinedReturnType(Type userDefinedReturnType) { - this.userDefinedReturnType = userDefinedReturnType; - return this; - } - - public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) { - this.returnTypeBuilder = returnTypeBuilder; - return this; - } - public Builder sourceMethod(SourceMethod sourceMethod) { method( sourceMethod ); return this; @@ -181,7 +169,17 @@ public BeanMappingMethod build() { // determine which return type to construct boolean cannotConstructReturnType = false; if ( !method.getReturnType().isVoid() ) { - Type returnTypeImpl = null; + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + Type returnTypeImpl; + Type userDefinedReturnType = null; + if ( selectionParameters != null && selectionParameters.getResultType() != null ) { + // This is a user-defined return type, which means we need to do some extra checks for it + userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); + } + else { + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set returnTypeImpl = returnTypeBuilder.getBuilder(); @@ -446,12 +444,6 @@ else if ( !method.isUpdateMethod() ) { if ( parameterPresenceCheck != null ) { presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); } - else if ( !sourceParameter.getType().isPrimitive() ) { - presenceChecksByParameter.put( - sourceParameter.getName(), - new NullPresenceCheck( sourceParameter.getName() ) - ); - } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index da73e1783f..25c9f8fc03 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; @@ -14,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -68,9 +71,32 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); - for ( PropertyEntry propertyEntry : propertyEntries ) { + for ( int i = 0; i < propertyEntries.size(); i++ ) { + PropertyEntry propertyEntry = propertyEntries.get( i ); + PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } + } + String safeName = Strings.getSafeVariableName( propertyEntry.getName(), existingVariableNames ); - safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName, previousPropertyName ) ); + String source = previousPropertyName + "." + propertyEntry.getReadAccessor().getReadValueSource(); + safePropertyEntries.add( new SafePropertyEntry( + propertyEntry.getType(), + safeName, + source, + presenceCheck + ) ); existingVariableNames.add( safeName ); thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); @@ -79,6 +105,18 @@ public NestedPropertyMappingMethod build() { method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); } + + private PresenceCheck getPresenceCheck(PropertyEntry propertyEntry, String previousPropertyName) { + PresenceCheckAccessor propertyPresenceChecker = propertyEntry.getPresenceChecker(); + if ( propertyPresenceChecker != null ) { + return new SuffixPresenceCheck( + previousPropertyName, + propertyPresenceChecker.getPresenceCheckSuffix(), + true + ); + } + return null; + } } private NestedPropertyMappingMethod( ForgedMethod method, List sourcePropertyEntries ) { @@ -157,44 +195,29 @@ public boolean equals( Object obj ) { public static class SafePropertyEntry { private final String safeName; - private final String readAccessorName; + private final String source; private final PresenceCheck presenceChecker; - private final String previousPropertyName; private final Type type; - public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { + public SafePropertyEntry(Type type, String safeName, String source, PresenceCheck presenceCheck) { this.safeName = safeName; - this.readAccessorName = entry.getReadAccessor().getReadValueSource(); - PresenceCheckAccessor presenceChecker = entry.getPresenceChecker(); - if ( presenceChecker != null ) { - this.presenceChecker = new SuffixPresenceCheck( - previousPropertyName, - presenceChecker.getPresenceCheckSuffix() - ); - } - else { - this.presenceChecker = null; - } - this.previousPropertyName = previousPropertyName; - this.type = entry.getType(); + this.source = source; + this.presenceChecker = presenceCheck; + this.type = type; } public String getName() { return safeName; } - public String getAccessorName() { - return readAccessorName; + public String getSource() { + return source; } public PresenceCheck getPresenceChecker() { return presenceChecker; } - public String getPreviousPropertyName() { - return previousPropertyName; - } - public Type getType() { return type; } @@ -210,7 +233,7 @@ public boolean equals(Object o) { SafePropertyEntry that = (SafePropertyEntry) o; - if ( !Objects.equals( readAccessorName, that.readAccessorName ) ) { + if ( !Objects.equals( source, that.source ) ) { return false; } @@ -218,10 +241,6 @@ public boolean equals(Object o) { return false; } - if ( !Objects.equals( previousPropertyName, that.previousPropertyName ) ) { - return false; - } - if ( !Objects.equals( type, that.type ) ) { return false; } @@ -231,7 +250,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = readAccessorName != null ? readAccessorName.hashCode() : 0; + int result = source != null ? source.hashCode() : 0; result = 31 * result + ( presenceChecker != null ? presenceChecker.hashCode() : 0 ); result = 31 * result + ( type != null ? type.hashCode() : 0 ); return result; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 96826f8f66..049e4caf37 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -86,6 +87,9 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { + if ( !sourceParameter.getType().isPrimitive() ) { + return new NullPresenceCheck( sourceParameter.getName() ); + } return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index db14ccbd8e..08e8adbd1b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -829,7 +829,7 @@ private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sour forgeMethodWithMappingReferences, forgedNamedBased ); - return createForgedAssignment( sourceRHS, targetBuilderType, forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java new file mode 100644 index 0000000000..0b5770e42f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.presence; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { + + private final Collection presenceChecks; + + public AnyPresenceChecksPresenceCheck(Collection presenceChecks) { + this.presenceChecks = presenceChecks; + } + + public Collection getPresenceChecks() { + return presenceChecks; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + for ( PresenceCheck presenceCheck : presenceChecks ) { + importTypes.addAll( presenceCheck.getImportTypes() ); + } + + return importTypes; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + AnyPresenceChecksPresenceCheck that = (AnyPresenceChecksPresenceCheck) o; + return Objects.equals( presenceChecks, that.presenceChecks ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceChecks ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 4fbed6cbec..ba24e868aa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -24,7 +24,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.gem.InheritConfigurationGem; import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem; @@ -420,15 +419,10 @@ else if ( method.isStreamMapping() ) { } else { this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - Type userDefinedReturnType = getUserDesiredReturnType( method ); - Type builderBaseType = userDefinedReturnType != null ? userDefinedReturnType : method.getReturnType(); BeanMappingMethod.Builder beanMappingBuilder = new BeanMappingMethod.Builder(); BeanMappingMethod beanMappingMethod = beanMappingBuilder .mappingContext( mappingContext ) .sourceMethod( method ) - .userDefinedReturnType( userDefinedReturnType ) - .returnTypeBuilder( typeFactory.builderTypeFor( builderBaseType, builder ) ) .build(); // We can consider that the bean mapping method can always be constructed. If there is a problem @@ -466,14 +460,6 @@ private Javadoc getJavadoc(TypeElement element) { return javadoc; } - private Type getUserDesiredReturnType(SourceMethod method) { - SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters(); - if ( selectionParameters != null && selectionParameters.getResultType() != null ) { - return typeFactory.getType( selectionParameters.getResultType() ); - } - return null; - } - private M createWithElementMappingMethod(SourceMethod method, MappingMethodOptions mappingMethodOptions, ContainerMappingMethodBuilder builder) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 8eabe23a16..04d385a9dc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -9,22 +9,14 @@ <#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list propertyEntries as entry> <#if entry.presenceChecker?? > - if ( <#if entry_index != 0>${entry.previousPropertyName} == null || !<@includeModel object=entry.presenceChecker /> ) { + if ( <@includeModel object=entry.presenceChecker /> ) { return ${returnType.null}; } - <#if !entry_has_next> - return ${entry.previousPropertyName}.${entry.accessorName}; - <#if entry_has_next> - <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName}; - <#if !entry.presenceChecker?? > - <#if !entry.type.primitive> - if ( ${entry.name} == null ) { - return ${returnType.null}; - } - - + <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.source}; + <#else> + return ${entry.source}; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl new file mode 100644 index 0000000000..3df10348ad --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck" --> +<@compress single_line=true> +<#list presenceChecks as presenceCheck> + <#if presenceCheck_index != 0> + || + + <@includeModel object=presenceCheck /> + + \ No newline at end of file From 9d75a48df5e40c1ed87d2f68d96fbb35807e3b91 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 26 Jan 2026 11:35:18 +0100 Subject: [PATCH 038/125] Block plexus.snapshots repository in GitHub actions #3972 --- .github/workflows/java-ea.yml | 3 +++ .github/workflows/macos.yml | 3 +++ .github/workflows/main.yml | 6 ++++++ .github/workflows/release.yml | 4 ++++ .github/workflows/windows.yml | 3 +++ 5 files changed, 19 insertions(+) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fc3a4a0fb1..70b81c40f1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -17,5 +17,8 @@ jobs: with: website: jdk.java.net release: EA + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 833bb6ba39..98b4e156e0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4def2a89d..ca66a22291 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,9 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' @@ -59,5 +62,8 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61f347af3c..b4dffe0586 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,10 @@ jobs: distribution: 'zulu' cache: maven + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' + - name: Set release version id: version run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bda38f8783..fc37af6a9d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw %MAVEN_ARGS% install From 99e865f9861bba3d8cc24d90b89821e8bb191164 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 10:27:59 +0100 Subject: [PATCH 039/125] Improve testing support for Kotlin as part of the regular processor testing (#3977) --- .../itest/tests/MavenIntegrationTest.java | 6 + .../test/resources/fullFeatureTest/pom.xml | 1 + .../resources/kotlinFullFeatureTest/pom.xml | 143 ++++++++++++++++++ parent/pom.xml | 13 ++ processor/pom.xml | 68 +++++++++ .../ap/test/kotlin/data/CustomerDto.kt | 11 ++ .../ap/test/kotlin/data/CustomerEntity.java | 31 ++++ .../ap/test/kotlin/data/CustomerMapper.java | 27 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 54 +++++++ .../ap/testutil/WithKotlinSources.java | 32 ++++ .../model/CompilationOutcomeDescriptor.java | 31 ++++ .../model/DiagnosticDescriptor.java | 34 +++++ .../testutil/runner/CompilationRequest.java | 12 +- .../testutil/runner/CompilingExtension.java | 102 ++++++++++++- .../runner/EclipseCompilingExtension.java | 11 +- .../runner/JdkCompilingExtension.java | 15 +- 16 files changed, 579 insertions(+), 12 deletions(-) create mode 100644 integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index ab43cc4cba..70e1f948b9 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -159,6 +159,12 @@ void expressionTextBlocksTest() { void kotlinDataTest() { } + @ProcessorTest(baseDir = "kotlinFullFeatureTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC_WITH_PATHS + }) + void kotlinFullFeatureTest() { + } + @ProcessorTest(baseDir = "simpleTest") void simpleTest() { } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index b5ddae9a79..670d5cff92 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -47,6 +47,7 @@ **/*Test.java **/testutil/**/*.java **/spi/**/*.java + **/kotlin/**/*.java ${additionalExclude0} ${additionalExclude1} ${additionalExclude2} diff --git a/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml new file mode 100644 index 0000000000..ba18457f42 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + kotlinFullFeatureTest + jar + + + 2.3.0 + + + + + generate-via-compiler-plugin-with-annotation-processor-paths + + false + + + + + maven-resources-plugin + + + filter-kotlin-patterns + generate-sources + + copy-resources + + + \${project.build.directory}/kotlin-sources + + + ../../../../../processor/src/test/java + + **/kotlin/**/*.kt + + + + true + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + kotlin-compile + compile + + compile + + + + \${project.build.directory}/kotlin-sources + + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + ../../../../../processor/src/test/java + + + **/kotlin/**/*.java + + + **/erroneous/**/*.java + **/*Erroneous*.java + **/*Test.java + **/testutil/**/*.java + + + + + java-test-compile + test-compile + + testCompile + + + + + + + + + + + + org.jetbrains.kotlin + kotlin-stdlib + + + + diff --git a/parent/pom.xml b/parent/pom.xml index f90b066774..5c3a73da88 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -52,6 +52,7 @@ 1.8 3.25.5 2.3.2 + 2.3.0 @@ -151,6 +152,13 @@ ${com.puppycrawl.tools.checkstyle.version} + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + org.junit junit-bom @@ -490,6 +498,11 @@ maven-shade-plugin 3.2.0 + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + com.mycila.maven-license-plugin maven-license-plugin diff --git a/processor/pom.xml b/processor/pom.xml index 87c3517018..9f24281e33 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-compiler-embeddable + test + org.eclipse.tycho tycho-compiler-jdt @@ -282,6 +287,42 @@ + + org.jetbrains.kotlin + + kotlin-maven-plugin + + false + + + + kotlin-compile + compile + + compile + + + + src/main/kotlin + + src/main/java + + + + + kotlin-test-compile + test-compile + + test-compile + + + + src/test/java + + + + + org.apache.maven.plugins maven-compiler-plugin @@ -296,6 +337,33 @@ + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + org.apache.maven.plugins diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt new file mode 100644 index 0000000000..3fbd48b0b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class CustomerDto(var name: String?, var email: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java new file mode 100644 index 0000000000..7ddd41bda0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java @@ -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.ap.test.kotlin.data; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java new file mode 100644 index 0000000000..1830a9c583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromData(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toData(CustomerEntity entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java new file mode 100644 index 0000000000..310039363d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -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.ap.test.kotlin.data; + +import org.junit.jupiter.api.Nested; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class KotlinDataTest { + + @Nested + @WithClasses({ + CustomerEntity.class, + CustomerMapper.class + }) + @WithKotlinSources("CustomerDto.kt") + class Standard { + + @ProcessorTest + void shouldMapData() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromData( new CustomerDto( + "Kermit", + "kermit@test.com" + ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @ProcessorTest + void shouldMapIntoData() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toData( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getEmail() ).isEqualTo( "kermit@test.com" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java new file mode 100644 index 0000000000..656dc9bc5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.intellij.lang.annotations.Language; + +/** + * Specifies the kotlin sources to compile during an annotation processor test. + * If given both on the class-level and the method-level for a given test, all the given sources will be compiled. + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface WithKotlinSources { + + /** + * The classes to be compiled for the annotated test class or method. + * + * @return the classes to be compiled for the annotated test class or method + */ + @Language(value = "file-reference", prefix = "", suffix = ".kt") + String[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java index b9f7bb8425..b5a4dc535d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java @@ -18,6 +18,8 @@ import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.compiler.CompilerResult; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; @@ -104,6 +106,25 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerR return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, Collections.emptyList() ); } + public static CompilationOutcomeDescriptor forResult(String sourceDir, ExitCode exitCode, + List messages) { + List notes = new ArrayList<>(); + List diagnosticDescriptors = new ArrayList<>( messages.size() ); + for ( MessageCollectorImpl.Message message : messages ) { + //ignore notes created by the compiler + DiagnosticDescriptor descriptor = DiagnosticDescriptor.forCompilerMessage( sourceDir, message ); + if ( descriptor.getKind() != Kind.NOTE ) { + diagnosticDescriptors.add( descriptor ); + } + else { + notes.add( descriptor.getMessage() ); + } + } + CompilationResult compilationResult = + exitCode == ExitCode.OK ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, notes ); + } + public CompilationResult getCompilationResult() { return compilationResult; } @@ -116,6 +137,16 @@ public List getNotes() { return notes; } + public CompilationOutcomeDescriptor merge(CompilationOutcomeDescriptor other) { + CompilationResult compilationResult = + this.compilationResult == CompilationResult.SUCCEEDED ? other.compilationResult : this.compilationResult; + List diagnostics = new ArrayList<>( this.diagnostics ); + diagnostics.addAll( other.diagnostics ); + List notes = new ArrayList<>( this.notes ); + notes.addAll( other.notes ); + return new CompilationOutcomeDescriptor( compilationResult, diagnostics, notes ); + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index ecfec14a16..68e2cb00fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -15,6 +15,9 @@ import javax.tools.JavaFileObject; import org.codehaus.plexus.compiler.CompilerMessage; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; /** @@ -100,6 +103,37 @@ private static Kind toJavaxKind(CompilerMessage.Kind kind) { } } + public static DiagnosticDescriptor forCompilerMessage(String sourceDir, MessageCollectorImpl.Message message) { + CompilerMessageSourceLocation location = message.getLocation(); + String sourceFileName; + Long line; + if ( location != null ) { + sourceFileName = location.getPath(); + line = (long) location.getLine(); + } + else { + sourceFileName = null; + line = null; + } + return new DiagnosticDescriptor( + sourceFileName, + toJavaxKind( message.getSeverity() ), + line, + message.getMessage(), + null + ); + } + + private static Kind toJavaxKind(CompilerMessageSeverity severity) { + return switch ( severity ) { + case ERROR, EXCEPTION -> Kind.ERROR; + case WARNING, FIXED_WARNING -> Kind.WARNING; + case STRONG_WARNING -> Kind.MANDATORY_WARNING; + case INFO, LOGGING -> Kind.NOTE; + case OUTPUT -> Kind.OTHER; + }; + } + private static String getSourceName(String sourceDir, javax.tools.Diagnostic diagnostic) { if ( diagnostic.getSource() == null ) { return null; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 9f1b78caa0..8622fbe92c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,14 +19,17 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, - List processorOptions, Collection testDependencies) { + List processorOptions, Collection testDependencies, + Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.kotlinSources = kotlinSources; } @Override @@ -58,7 +61,8 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) - && sourceClasses.equals( other.sourceClasses ); + && sourceClasses.equals( other.sourceClasses ) + && kotlinSources.equals( other.kotlinSources ); } public Set> getSourceClasses() { @@ -77,6 +81,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getKotlinSources() { + return kotlinSources; + } + public Compiler getCompiler() { return compiler; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 95874e4b3d..7ff78753a6 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; @@ -30,9 +31,15 @@ import com.puppycrawl.tools.checkstyle.PropertiesExpander; import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; +import org.jetbrains.kotlin.config.Services; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -455,6 +462,20 @@ protected static Set getSourceFiles(Collection> classes) { return sourceFiles; } + private Collection getKotlinSources(ExtensionContext context) { + Collection kotlinSources = new HashSet<>(); + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + findAnnotation( testMethod, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + findAnnotation( testClass, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + return kotlinSources; + } + private CompilationOutcomeDescriptor compile(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); Class testClass = context.getRequiredTestClass(); @@ -464,7 +485,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getTestClasses( testMethod, testClass ), getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), - getAdditionalTestDependencies( testMethod, testClass ) + getAdditionalTestDependencies( testMethod, testClass ), + getKotlinSources( context ) ); ExtensionContext.Store rootStore = context.getRoot().getStore( NAMESPACE ); @@ -484,7 +506,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { boolean needsAdditionalCompilerClasspath = prepareServices( compilationRequest ); CompilationOutcomeDescriptor resultHolder; - resultHolder = compileWithSpecificCompiler( + resultHolder = compile( + context, compilationRequest, sourceOutputDir, classOutputDir, @@ -503,6 +526,81 @@ protected Object loadAndInstantiate(ClassLoader processorClassloader, Class c } } + protected CompilationOutcomeDescriptor compile( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = compileWithKotlin( + context, + compilationRequest, + sourceOutputDir, + classOutputDir + ); + + if ( kotlinCompilationOutcome != null && + kotlinCompilationOutcome.getCompilationResult() == CompilationResult.FAILED ) { + return kotlinCompilationOutcome; + } + + CompilationOutcomeDescriptor javaCompilationOutcome = compileWithSpecificCompiler( + compilationRequest, + sourceOutputDir, + classOutputDir, + additionalCompilerClasspath + ); + return kotlinCompilationOutcome == null ? javaCompilationOutcome : + kotlinCompilationOutcome.merge( javaCompilationOutcome ); + } + + private CompilationOutcomeDescriptor compileWithKotlin( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir + ) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = null; + if ( !compilationRequest.getKotlinSources().isEmpty() ) { + K2JVMCompiler k2JvmCompiler = new K2JVMCompiler(); + MessageCollectorImpl messageCollector = new MessageCollectorImpl(); + K2JVMCompilerArguments k2JvmArguments = new K2JVMCompilerArguments(); + k2JvmArguments.setClasspath( + String.join( + File.pathSeparator, filterBootClassPath( List.of( + "kotlin-stdlib", + "kotlin-reflect" + ) ) + ) + ); + k2JvmArguments.setNoStdlib( true ); + k2JvmArguments.setNoReflect( true ); + String packageName = context.getRequiredTestClass() + .getPackageName(); + String sourcePrefix = + SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; + k2JvmArguments.setFreeArgs( Arrays.asList( + compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.joining( File.pathSeparator ) ) + ) ); + k2JvmArguments.setVerbose( true ); + k2JvmArguments.setSuppressWarnings( false ); + k2JvmArguments.setDestination( classOutputDir ); + + ExitCode kotlinExitCode = k2JvmCompiler.exec( messageCollector, Services.EMPTY, k2JvmArguments ); + + kotlinCompilationOutcome = CompilationOutcomeDescriptor.forResult( + sourceOutputDir, + kotlinExitCode, + messageCollector.getMessages() + ); + + } + return kotlinCompilationOutcome; + } + protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( CompilationRequest compilationRequest, String sourceOutputDir, diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index c356347664..4755df0bd1 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -68,24 +68,27 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe return clHelper.compileInOtherClassloader( compilationRequest, - getTestCompilationClasspath( compilationRequest ), + getTestCompilationClasspath( compilationRequest, classOutputDir ), getSourceFiles( compilationRequest.getSourceClasses() ), SOURCE_DIR, sourceOutputDir, classOutputDir ); } - private static List getTestCompilationClasspath(CompilationRequest request) { + private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); + if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.add( classOutputDir ); + } return testCompilationPaths; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 5185bfb7ed..b6d8afd3bf 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -59,7 +59,10 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); try { - fileManager.setLocation( StandardLocation.CLASS_PATH, getCompilerClasspathFiles( compilationRequest ) ); + fileManager.setLocation( + StandardLocation.CLASS_PATH, + getCompilerClasspathFiles( compilationRequest, classOutputDir ) + ); fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); } @@ -99,20 +102,24 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe diagnostics.getDiagnostics() ); } - private static List getCompilerClasspathFiles(CompilationRequest request) { + private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } + if ( !request.getKotlinSources().isEmpty() ) { + compilerClasspathFiles.add( new File( classOutputDir ) ); + } + return compilerClasspathFiles; } From d3006640938014a1d8fa193d735cee934771309b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 12:30:13 +0100 Subject: [PATCH 040/125] Improve support for Kotlin Data Classes (#3978) * Properly handle single field data classes * Properly handle multi constructor data classes * Properly handle all defaults constructor data classes Fixes #2281, #2577, #3031 --- NEXT_RELEASE_CHANGELOG.md | 6 + distribution/pom.xml | 5 + .../main/asciidoc/chapter-2-set-up.asciidoc | 71 +++- parent/pom.xml | 5 + processor/pom.xml | 5 + .../ap/internal/model/BeanMappingMethod.java | 22 ++ .../ap/internal/model/common/Type.java | 156 ++++++++ .../internal/util/kotlin/KotlinMetadata.java | 21 + .../ap/spi/DefaultAccessorNamingStrategy.java | 68 +++- .../test/kotlin/data/AllDefaultsProperty.kt | 11 + .../data/AllDefaultsPropertyMapper.java | 39 ++ .../ap/test/kotlin/data/KotlinDataTest.java | 370 ++++++++++++++++++ .../kotlin/data/MultiConstructorProperty.kt | 13 + .../data/MultiConstructorPropertyMapper.java | 45 +++ .../data/MultiDefaultConstructorProperty.kt | 18 + ...MultiDefaultConstructorPropertyMapper.java | 45 +++ .../data/MultiSimilarConstructorProperty.kt | 50 +++ ...MultiSimilarConstructorPropertyMapper.java | 57 +++ .../ap/test/kotlin/data/SingleProperty.kt | 11 + .../kotlin/data/SinglePropertyMapper.java | 32 ++ .../org/mapstruct/ap/testutil/WithKotlin.java | 29 ++ .../testutil/WithProcessorDependencies.java | 22 ++ .../ap/testutil/WithProcessorDependency.java | 28 ++ .../testutil/runner/CompilationRequest.java | 8 + .../testutil/runner/CompilingExtension.java | 22 ++ .../runner/EclipseCompilingExtension.java | 18 +- .../runner/JdkCompilingExtension.java | 31 +- 27 files changed, 1186 insertions(+), 22 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 2d0555cb24..f279193f6a 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,12 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. + - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: + - Single field data classes + - Proper primary constructor detection + - Data classes with multiple constructors + - Data classes with all default parameters ### Enhancements diff --git a/distribution/pom.xml b/distribution/pom.xml index db654236e4..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -39,6 +39,11 @@ org.mapstruct.tools.gem gem-api + + + org.jetbrains.kotlin + kotlin-metadata-jvm + jakarta.xml.bind diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 58759ff8e0..86ed345630 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -35,10 +35,10 @@ For Maven based projects add the following to your POM file in order to use MapS org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -141,10 +141,10 @@ When invoking javac directly, these options are passed to the compiler in the fo org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -323,3 +323,62 @@ Some features include: * Code completion in `target` and `source` * Quick Fixes + +[[kotlin-setup]] +=== Kotlin Support + +MapStruct provides support for Kotlin interoperability with Java. + +When using MapStruct with Kotlin, it's recommended to add the `org.jetbrains.kotlin:kotlin-metadata-jvm` library to enable proper introspection of Kotlin metadata: + +.Maven configuration for Kotlin support +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + + + + + + +---- +==== + +.Gradle configuration for Kotlin support +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- +dependencies { + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.jetbrains.kotlin:kotlin-metadata-jvm:${kotlinVersion}" +} +---- +==== + +[NOTE] +==== +The `org.jetbrains.kotlin:kotlin-metadata-jvm` dependency is optional but highly recommended when working with Kotlin and Java. +Without it, MapStruct will still work but may not handle complex Kotlin scenarios optimally. +==== \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 5c3a73da88..8c1266ed19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -159,6 +159,11 @@ pom import + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + org.junit junit-bom diff --git a/processor/pom.xml b/processor/pom.xml index 9f24281e33..c51c2bca26 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-metadata-jvm + provided + org.jetbrains.kotlin kotlin-compiler-embeddable diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 39222efc88..619272a175 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -68,6 +68,7 @@ import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; import static org.mapstruct.ap.internal.util.Collections.first; @@ -904,6 +905,27 @@ private ConstructorAccessor getConstructorAccessor(Type type) { return new ConstructorAccessor( parameterBindings, constructorAccessors ); } + KotlinMetadata kotlinMetadata = type.getKotlinMetadata(); + if ( kotlinMetadata != null && kotlinMetadata.isDataClass() ) { + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + ExecutableElement primaryConstructor = kotlinMetadata.determinePrimaryConstructor( constructors ); + + return primaryConstructor != null ? getConstructorAccessor( type, primaryConstructor ) : null; + } + List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 24f23ecea8..313ff38125 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -43,6 +43,13 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleTypeVisitor8; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.KmClass; +import kotlin.metadata.KmConstructor; +import kotlin.metadata.jvm.JvmExtensionsKt; +import kotlin.metadata.jvm.JvmMethodSignature; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.ElementUtils; @@ -58,6 +65,7 @@ import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static java.util.Collections.emptyList; import static org.mapstruct.ap.internal.util.Collections.first; @@ -75,6 +83,7 @@ */ public class Type extends ModelElement implements Comparable { private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD; + private static final boolean KOTLIN_METADATA_JVM_PRESENT; static { Method permittedSubclassesMethod; @@ -85,6 +94,16 @@ public class Type extends ModelElement implements Comparable { permittedSubclassesMethod = null; } SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod; + + boolean kotlinMetadataJvmPresent; + try { + Class.forName( "kotlin.metadata.jvm.KotlinClassMetadata", false, ModelElement.class.getClassLoader() ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; } private final TypeUtils typeUtils; @@ -139,6 +158,8 @@ public class Type extends ModelElement implements Comparable { private Type boxedEquivalent = null; private Boolean hasAccessibleConstructor; + private KotlinMetadata kotlinMetadata; + private boolean kotlinMetadataInitialized; private final Filters filters; @@ -1370,6 +1391,23 @@ public boolean hasAccessibleConstructor() { return hasAccessibleConstructor; } + public KotlinMetadata getKotlinMetadata() { + if ( !kotlinMetadataInitialized ) { + kotlinMetadataInitialized = true; + if ( typeElement != null && KOTLIN_METADATA_JVM_PRESENT ) { + Metadata metadataAnnotation = typeElement.getAnnotation( Metadata.class ); + if ( metadataAnnotation != null ) { + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( metadataAnnotation ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + kotlinMetadata = new KotlinMetadataImpl( (KotlinClassMetadata.Class) classMetadata ); + } + } + } + } + + return kotlinMetadata; + } + /** * Returns the direct supertypes of a type. The interface types, if any, * will appear last in the list. @@ -1831,4 +1869,122 @@ public List getPermittedSubclasses() { } } + private class KotlinMetadataImpl implements KotlinMetadata { + + private final KotlinClassMetadata.Class kotlinClassMetadata; + + private KotlinMetadataImpl(KotlinClassMetadata.Class kotlinClassMetadata) { + this.kotlinClassMetadata = kotlinClassMetadata; + } + + @Override + public boolean isDataClass() { + return Attributes.isData( kotlinClassMetadata.getKmClass() ); + } + + @Override + public ExecutableElement determinePrimaryConstructor(List constructors) { + if ( constructors.size() == 1 ) { + // If we have one constructor, that this constructor is the primary one + return constructors.get( 0 ); + } + KmClass kmClass = kotlinClassMetadata.getKmClass(); + KmConstructor primaryKmConstructor = null; + for ( KmConstructor constructor : kmClass.getConstructors() ) { + if ( !Attributes.isSecondary( constructor ) ) { + primaryKmConstructor = constructor; + } + + } + + if ( primaryKmConstructor == null ) { + return null; + } + + List sameParametersSizeConstructors = new ArrayList<>(); + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getParameters().size() == primaryKmConstructor.getValueParameters().size() ) { + sameParametersSizeConstructors.add( constructor ); + } + } + + if ( sameParametersSizeConstructors.size() == 1 ) { + return sameParametersSizeConstructors.get( 0 ); + } + + JvmMethodSignature signature = JvmExtensionsKt.getSignature( primaryKmConstructor ); + if ( signature == null ) { + return null; + } + + String signatureDescriptor = signature.getDescriptor(); + for ( ExecutableElement constructor : constructors ) { + String constructorDescriptor = buildJvmConstructorDescriptor( constructor ); + if ( signatureDescriptor.equals( constructorDescriptor ) ) { + return constructor; + } + } + + return null; + } + + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { + StringBuilder signature = new StringBuilder( "(" ); + + for ( VariableElement param : constructor.getParameters() ) { + signature.append( getJvmTypeDescriptor( param.asType() ) ); + } + + signature.append( ")V" ); + return signature.toString(); + } + + private String getJvmTypeDescriptor(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8() { + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + switch ( t.getKind() ) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case CHAR: + return "C"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + default: + return ""; + } + } + + @Override + public String visitDeclared(DeclaredType t, Void p) { + TypeElement element = (TypeElement) t.asElement(); + String binaryName = elementUtils.getBinaryName( element ).toString(); + return "L" + binaryName.replace( '.', '/' ) + ";"; + } + + @Override + public String visitArray(ArrayType t, Void p) { + return "[" + getJvmTypeDescriptor( t.getComponentType() ); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return ""; + } + }, null + ); + } + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java new file mode 100644 index 0000000000..d58ab75c79 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util.kotlin; + +import java.util.List; +import javax.lang.model.element.ExecutableElement; + +/** + * Information about a type in case it's a Kotlin type. + * + * @author Filip Hrisafov + */ +public interface KotlinMetadata { + + boolean isDataClass(); + + ExecutableElement determinePrimaryConstructor(List constructors); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 82ecb4e17b..511504c937 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -7,6 +7,7 @@ import java.util.regex.Pattern; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -17,6 +18,9 @@ import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.spi.util.IntrospectorUtils; /** @@ -28,6 +32,24 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + private static final boolean KOTLIN_METADATA_JVM_PRESENT; + + static { + boolean kotlinMetadataJvmPresent; + try { + Class.forName( + "kotlin.metadata.jvm.KotlinClassMetadata", + false, + AccessorNamingStrategy.class.getClassLoader() + ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; + } + protected Elements elementUtils; protected Types typeUtils; @@ -102,10 +124,48 @@ public boolean isSetterMethod(ExecutableElement method) { } protected boolean isFluentSetter(ExecutableElement method) { - return method.getParameters().size() == 1 && - !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && - !isAdderWithUpperCase4thCharacter( method ) && - typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); + if ( method.getParameters().size() != 1 ) { + return false; + } + Element methodOwnerElement = method.getEnclosingElement(); + if ( !canMethodOwnerBeUsedInFluentSetter( methodOwnerElement ) ) { + return false; + } + return !isAdderWithUpperCase4thCharacter( method ) && + typeUtils.isAssignable( method.getReturnType(), methodOwnerElement.asType() ); + } + + private boolean canMethodOwnerBeUsedInFluentSetter(Element methodOwnerElement) { + if ( !( methodOwnerElement instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) methodOwnerElement; + if ( JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName().toString() ).matches() ) { + return false; + } + if ( isKotlinDataClass( typeElement ) ) { + return false; + } + + return true; + } + + private boolean isKotlinDataClass(TypeElement typeElement) { + if ( !KOTLIN_METADATA_JVM_PRESENT ) { + return false; + } + + Metadata kotlinMetadata = typeElement.getAnnotation( Metadata.class ); + if ( kotlinMetadata == null ) { + return false; + } + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( kotlinMetadata ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + return Attributes.isData( ( (KotlinClassMetadata.Class) classMetadata ).getKmClass() ); + } + + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt new file mode 100644 index 0000000000..e452102f6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class AllDefaultsProperty(val firstName: String? = null, val lastName: String? = null) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java new file mode 100644 index 0000000000..1f8aab4624 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface AllDefaultsPropertyMapper { + + AllDefaultsPropertyMapper INSTANCE = Mappers.getMapper( AllDefaultsPropertyMapper.class ); + + AllDefaultsProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 310039363d..1daa19c231 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -8,7 +8,11 @@ import org.junit.jupiter.api.Nested; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import static org.assertj.core.api.Assertions.assertThat; @@ -51,4 +55,370 @@ void shouldMapIntoData() { } } + + @Nested + @WithClasses({ + SinglePropertyMapper.class, + }) + @WithKotlinSources("SingleProperty.kt") + class SingleData { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = SinglePropertyMapper.class, + line = 19, + message = "Unmapped target property: \"copy\"." + ) + } + ) + void shouldCompileWithWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + } + + @Nested + @WithClasses({ + AllDefaultsPropertyMapper.class, + }) + @WithKotlinSources("AllDefaultsProperty.kt") + class AllDefaults { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = AllDefaultsPropertyMapper.class, + line = 19, + message = "No target property found for target \"AllDefaultsProperty\"." + ) + } + ) + void shouldCompileWithWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + } + + @Nested + @WithClasses({ + MultiConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiConstructorProperty.kt") + class MultiConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiConstructorProperty property = MultiConstructorPropertyMapper.INSTANCE + .map( new MultiConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*MultiConstructorProperty: " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\), " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\)\\. " + + "Either declare parameterless constructor or annotate the default constructor with an " + + "annotation named @Default\\." + ) + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiSimilarConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiSimilarConstructorProperty.kt") + class MultiSimilarConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + PrimaryString primaryString = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + "Kermit the Frog" + ); + assertThat( primaryString ).isNotNull(); + assertThat( primaryString.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryString.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryString.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + + PrimaryInt primaryInt = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42 + ); + assertThat( primaryInt ).isNotNull(); + assertThat( primaryInt.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryInt.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryInt.getAge() ).isEqualTo( 42 ); + + PrimaryLong primaryLong = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42L + ); + assertThat( primaryLong ).isNotNull(); + assertThat( primaryLong.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryLong.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryLong.getAge() ).isEqualTo( 42 ); + + PrimaryBoolean primaryBoolean = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + true + ); + assertThat( primaryBoolean ).isNotNull(); + assertThat( primaryBoolean.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryBoolean.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryBoolean.getActive() ).isTrue(); + + PrimaryByte primaryByte = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (byte) 4 + ); + assertThat( primaryByte ).isNotNull(); + assertThat( primaryByte.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryByte.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryByte.getB() ).isEqualTo( (byte) 4 ); + + PrimaryShort primaryShort = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (short) 4 + ); + assertThat( primaryShort ).isNotNull(); + assertThat( primaryShort.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryShort.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryShort.getAge() ).isEqualTo( (short) 4 ); + + PrimaryChar primaryChar = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 't' + ); + assertThat( primaryChar ).isNotNull(); + assertThat( primaryChar.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryChar.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryChar.getC() ).isEqualTo( 't' ); + + PrimaryFloat primaryFloat = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2f + ); + assertThat( primaryFloat ).isNotNull(); + assertThat( primaryFloat.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryFloat.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryFloat.getPrice() ).isEqualTo( 42.2f ); + + PrimaryDouble primaryDouble = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2 + ); + assertThat( primaryDouble ).isNotNull(); + assertThat( primaryDouble.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryDouble.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryDouble.getPrice() ).isEqualTo( 42.2d ); + + PrimaryArray primaryArray = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + new String[] { "Kermit", "the Frog" } + ); + assertThat( primaryArray ).isNotNull(); + assertThat( primaryArray.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryArray.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryArray.getElements() ).containsExactly( "Kermit", "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryString" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 21, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryInt" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 23, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryLong" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 25, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryBoolean" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 27, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryByte" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 29, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryShort" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 31, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryChar" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 33, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryFloat" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 35, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryDouble" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 37, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryArray" + ) + + + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiDefaultConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiDefaultConstructorProperty.kt") + class MultiDefaultConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt new file mode 100644 index 0000000000..46cb8034e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiConstructorProperty(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java new file mode 100644 index 0000000000..3186226a57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiConstructorPropertyMapper { + + MultiConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiConstructorPropertyMapper.class ); + + MultiConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt new file mode 100644 index 0000000000..712cf1151b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: String?, val displayName: String?) { + @Default + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java new file mode 100644 index 0000000000..da04c01fb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiDefaultConstructorPropertyMapper { + + MultiDefaultConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiDefaultConstructorPropertyMapper.class ); + + MultiDefaultConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt new file mode 100644 index 0000000000..e122169075 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class PrimaryString(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?, age: Int) : this(firstName, lastName, null as String?) +} + +data class PrimaryInt(var firstName: String?, var lastName: String?, var age: Int) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryLong(var firstName: String?, var lastName: String?, var age: Long) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryBoolean(var firstName: String?, var lastName: String?, var active: Boolean) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, false) +} + +data class PrimaryByte(var firstName: String?, var lastName: String?, var b: Byte) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryShort(var firstName: String?, var lastName: String?, var age: Short) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryChar(var firstName: String?, var lastName: String?, var c: Char) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 'a') +} + +data class PrimaryFloat(var firstName: String?, var lastName: String?, var price: Float) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0f) +} + +data class PrimaryDouble(var firstName: String?, var lastName: String?, var price: Double) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0) +} + +@Suppress("ArrayInDataClass") +data class PrimaryArray(var firstName: String?, var lastName: String?, var elements: Array) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, emptyArray()) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java new file mode 100644 index 0000000000..86021cd6d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java @@ -0,0 +1,57 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiSimilarConstructorPropertyMapper { + + MultiSimilarConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiSimilarConstructorPropertyMapper.class ); + + PrimaryString map(Source source, String displayName); + + PrimaryInt map(Source source, int age); + + PrimaryLong map(Source source, long age); + + PrimaryBoolean map(Source source, boolean active); + + PrimaryByte map(Source source, byte b); + + PrimaryShort map(Source source, short age); + + PrimaryChar map(Source source, char c); + + PrimaryFloat map(Source source, float price); + + PrimaryDouble map(Source source, double price); + + PrimaryArray map(Source source, String[] elements); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt new file mode 100644 index 0000000000..1bb63c410d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class SingleProperty(val value: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java new file mode 100644 index 0000000000..d3d70f261c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java @@ -0,0 +1,32 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SinglePropertyMapper { + + SinglePropertyMapper INSTANCE = Mappers.getMapper( SinglePropertyMapper.class ); + + SingleProperty map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java new file mode 100644 index 0000000000..f4bd2c5970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java @@ -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.ap.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithProcessorDependency({ + "kotlin-stdlib", + "kotlin-metadata-jvm", +}) +@WithTestDependency("kotlin-stdlib") +public @interface WithKotlin { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java new file mode 100644 index 0000000000..cccb30b6b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithProcessorDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithProcessorDependencies { + + WithProcessorDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java new file mode 100644 index 0000000000..8dbf8c8acd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java @@ -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.ap.testutil; + +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; + +/** + * Specifies the group id of the additional dependencies that should be added to the processor path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable(WithProcessorDependencies.class) +public @interface WithProcessorDependency { + /** + * @return The group ids of the additional dependencies for the processor path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 8622fbe92c..c9718c6c21 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,16 +19,19 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection processorDependencies; private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, List processorOptions, Collection testDependencies, + Collection processorDependencies, Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.processorDependencies = processorDependencies; this.kotlinSources = kotlinSources; } @@ -61,6 +64,7 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) + && processorDependencies.equals( other.processorDependencies ) && sourceClasses.equals( other.sourceClasses ) && kotlinSources.equals( other.kotlinSources ); } @@ -81,6 +85,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getProcessorDependencies() { + return processorDependencies; + } + public Collection getKotlinSources() { return kotlinSources; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 7ff78753a6..d8ed3b953d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithProcessorDependency; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -152,6 +153,15 @@ protected static List filterBootClassPath(Collection whitelist) return classpath; } + protected static Collection getProcessorClasspathDependencies(CompilationRequest request, + String additionalCompilerClasspath) { + List processClassPaths = filterBootClassPath( request.getProcessorDependencies() ); + if ( additionalCompilerClasspath != null ) { + processClassPaths.add( additionalCompilerClasspath ); + } + return processClassPaths; + } + private static boolean isWhitelisted(String path, Collection whitelist) { return whitelist.stream().anyMatch( path::contains ); } @@ -392,6 +402,17 @@ private Collection getAdditionalTestDependencies(Method testMethod, Clas return testDependencies; } + private Collection getAdditionalProcessorDependencies(Method testMethod, Class testClass) { + Collection processorDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + return processorDependencies; + } + private void addServices(Map, Class> services, List withImplementations) { for ( WithServiceImplementation withImplementation : withImplementations ) { addService( services, withImplementation ); @@ -486,6 +507,7 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), getAdditionalTestDependencies( testMethod, testClass ), + getAdditionalProcessorDependencies( testMethod, testClass ), getKotlinSources( context ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index 4755df0bd1..6b13036be3 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -47,8 +47,12 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe String sourceOutputDir, String classOutputDir, String additionalCompilerClasspath) { + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader compilerClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER; } else { @@ -59,7 +63,7 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH ) .withPaths( PROCESSOR_CLASSPATH ) .withOriginOf( ClassLoaderExecutor.class ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -77,16 +81,19 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + processorDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); - if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.addAll( filterBootClassPath( processorDependencies ) ); + if ( !kotlinSources.isEmpty() ) { testCompilationPaths.add( classOutputDir ); } return testCompilationPaths; @@ -96,6 +103,7 @@ private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { return new FilteringParentClassLoader( // reload eclipse compiler classes "org.eclipse.", + "kotlin.", // reload mapstruct processor classes "org.mapstruct.ap.internal.", "org.mapstruct.ap.spi.", diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index b6d8afd3bf..115618382c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -39,7 +39,7 @@ class JdkCompilingExtension extends CompilingExtension { private static final List COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH ); private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER = - new ModifiableURLClassLoader( new FilteringParentClassLoader( "org.mapstruct." ) ) + new ModifiableURLClassLoader( newFilteringClassLoaderForJdk() ) .withPaths( PROCESSOR_CLASSPATH ); JdkCompilingExtension() { @@ -70,15 +70,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe throw new RuntimeException( e ); } + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader processorClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER; } else { processorClassloader = new ModifiableURLClassLoader( - new FilteringParentClassLoader( "org.mapstruct." ) ) + newFilteringClassLoaderForJdk() + .hidingClasses( compilationRequest.getServices().values() ) + ) .withPaths( PROCESSOR_CLASSPATH ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -104,19 +110,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + processorDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } - if ( !request.getKotlinSources().isEmpty() ) { + if ( !kotlinSources.isEmpty() ) { compilerClasspathFiles.add( new File( classOutputDir ) ); } @@ -161,4 +169,13 @@ protected List filterExpectedDiagnostics(List Date: Thu, 29 Jan 2026 15:38:08 +0100 Subject: [PATCH 041/125] Add test case with Kotlin unsigned type (#3979) --- .../ap/internal/model/BeanMappingMethod.java | 5 +- .../mapstruct/ap/test/kotlin/data/Default.kt | 10 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 50 ++++++++++++++++++- .../data/MultiDefaultConstructorProperty.kt | 3 -- .../ap/test/kotlin/data/UnsignedProperty.kt | 17 +++++++ .../kotlin/data/UnsignedPropertyMapper.java | 35 +++++++++++++ .../testutil/runner/CompilingExtension.java | 11 ++-- 7 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 619272a175..086773dbe9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -910,8 +910,11 @@ private ConstructorAccessor getConstructorAccessor(Type type) { List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); - for ( ExecutableElement constructor : constructors ) { + Iterator constructorIterator = constructors.iterator(); + while ( constructorIterator.hasNext() ) { + ExecutableElement constructor = constructorIterator.next(); if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + constructorIterator.remove(); continue; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt new file mode 100644 index 0000000000..58ebbd128d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 1daa19c231..f55cb42535 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -10,6 +10,7 @@ import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -388,7 +389,10 @@ void shouldFailToCompile() { @WithClasses({ MultiDefaultConstructorPropertyMapper.class, }) - @WithKotlinSources("MultiDefaultConstructorProperty.kt") + @WithKotlinSources({ + "MultiDefaultConstructorProperty.kt", + "Default.kt" + }) class MultiDefaultConstructor { @ProcessorTest @@ -421,4 +425,48 @@ void shouldCompileWithoutKotlin() { assertThat( property.getDisplayName() ).isNull(); } } + + @Nested + @WithClasses({ + UnsignedPropertyMapper.class, + }) + @WithKotlinSources("UnsignedProperty.kt") + class Unsigned { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + + @ProcessorTest + @WithTestDependency( "kotlin-stdlib" ) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = UnsignedPropertyMapper.class, + line = 19, + messageRegExp = "Unmapped target property: \"copy-.*\"\\." + ) + } + ) + void shouldCompileWithoutKotlinJvmMetadata() { + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt index 712cf1151b..9f9af16053 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -13,6 +13,3 @@ data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) } -@Target(AnnotationTarget.CONSTRUCTOR) -@Retention(AnnotationRetention.BINARY) -annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt new file mode 100644 index 0000000000..c52426b3af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class UnsignedProperty(val age: UInt?) { + // Java-friendly secondary constructor + constructor(age: Int?) : this(age?.toUInt()) + + @JvmName("getAge") + fun getAgeAsLong(): Long? = age?.toLong() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java new file mode 100644 index 0000000000..4f055c7b41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UnsignedPropertyMapper { + + UnsignedPropertyMapper INSTANCE = Mappers.getMapper( UnsignedPropertyMapper.class ); + + UnsignedProperty map(Source source); + + Source map(UnsignedProperty property); + + class Source { + + private final int age; + + public Source(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index d8ed3b953d..08056ca697 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -601,12 +601,11 @@ File.pathSeparator, filterBootClassPath( List.of( .getPackageName(); String sourcePrefix = SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; - k2JvmArguments.setFreeArgs( Arrays.asList( - compilationRequest.getKotlinSources() - .stream() - .map( kotlinSource -> sourcePrefix + kotlinSource ) - .collect( Collectors.joining( File.pathSeparator ) ) - ) ); + k2JvmArguments.setFreeArgs( compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.toList() ) + ); k2JvmArguments.setVerbose( true ); k2JvmArguments.setSuppressWarnings( false ); k2JvmArguments.setDestination( classOutputDir ); From 3f20144b2f0529e048eee06cdcd93015686544c8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 15:54:38 +0100 Subject: [PATCH 042/125] Add test case for Kotlin property named default (#3980) --- .../ap/test/kotlin/data/DefaultProperty.kt | 16 ++++++ .../kotlin/data/DefaultPropertyMapper.java | 22 +++++++++ .../ap/test/kotlin/data/KotlinDataTest.java | 49 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt new file mode 100644 index 0000000000..f5ff27d3d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt @@ -0,0 +1,16 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class DefaultPropertySource(val default: Boolean, val identifier: String?) + +class DefaultPropertyTarget( + var default: Boolean, + var identifier: String? +) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java new file mode 100644 index 0000000000..ce08e26e7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface DefaultPropertyMapper { + + DefaultPropertyMapper INSTANCE = Mappers.getMapper( DefaultPropertyMapper.class ); + + DefaultPropertyTarget map(DefaultPropertySource source); + + DefaultPropertySource map(DefaultPropertyTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index f55cb42535..94d0d90d75 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -469,4 +469,53 @@ void shouldCompileWithoutKotlinJvmMetadata() { assertThat( source.getAge() ).isEqualTo( 20 ); } } + + @Nested + @WithClasses({ + DefaultPropertyMapper.class, + }) + @WithKotlinSources("DefaultProperty.kt") + class Default { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + } + } } From ec1e5f9317d399b3af420191bc5d6d025726073d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 16:07:57 +0100 Subject: [PATCH 043/125] Add tests for sealed classes in processor module --- ...eatureCompilationExclusionCliEnhancer.java | 6 ++ .../subclassmapping/jdk17/sealed/Motor.java | 42 ++++++++++ .../jdk17/sealed/MotorDto.java | 42 ++++++++++ .../jdk17/sealed/SealedSubclassMapper.java | 31 +++++++ .../jdk17/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ .../subclassmapping/jdk17/sealed/Vehicle.java | 51 ++++++++++++ .../jdk17/sealed/VehicleCollection.java | 17 ++++ .../jdk17/sealed/VehicleCollectionDto.java | 17 ++++ .../jdk17/sealed/VehicleDto.java | 51 ++++++++++++ 9 files changed, 337 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 0cc1a87818..7d397c9499 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -35,6 +35,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { additionalExcludes.add( "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); @@ -43,10 +45,14 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces 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/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.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/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_17: diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java new file mode 100644 index 0000000000..3498bb09e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java @@ -0,0 +1,42 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class Motor extends Vehicle { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class Davidson extends Motor { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class Harley extends Motor { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java new file mode 100644 index 0000000000..a7ec0be4a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java @@ -0,0 +1,42 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class MotorDto extends VehicleDto { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class DavidsonDto extends MotorDto { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class HarleyDto extends MotorDto { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..806eec78a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java @@ -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.ap.test.subclassmapping.jdk17.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f9f59c45ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Motor.class, + MotorDto.class, + SealedSubclassMapper.class, + Vehicle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + VehicleDto.class +}) +public class SealedSubclassTest { + + @ProcessorTest(Compiler.JDK) + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java new file mode 100644 index 0000000000..c558ec877e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class Vehicle permits Motor, Vehicle.Bike, Vehicle.Car { + private String name; + private String vehicleManufacturingCompany; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVehicleManufacturingCompany() { + return vehicleManufacturingCompany; + } + + public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) { + this.vehicleManufacturingCompany = vehicleManufacturingCompany; + } + + public static final class Bike extends Vehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class Car extends Vehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java new file mode 100644 index 0000000000..2285789baa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java new file mode 100644 index 0000000000..6c9dc7d382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java new file mode 100644 index 0000000000..40cbb0df60 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.subclassmapping.jdk17.sealed; + +public abstract sealed class VehicleDto permits VehicleDto.CarDto, VehicleDto.BikeDto, MotorDto { + private String name; + private String maker; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaker() { + return maker; + } + + public void setMaker(String maker) { + this.maker = maker; + } + + public static final class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} From 49963e34dec5cebc7a3dceda03a568d96939c2f1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 17:42:00 +0100 Subject: [PATCH 044/125] Upgrade to Checkstyle 13.0.0 Fix errors which are now reported. Run only on Java 21 or later for integration tests module --- integrationtest/pom.xml | 36 ++++++++++++------- parent/pom.xml | 4 +-- .../ap/internal/model/common/Assignment.java | 1 - .../ap/internal/model/source/Method.java | 1 - .../processor/ModelElementProcessor.java | 2 +- .../bugs/_3485/ErroneousIssue3485Mapper.java | 1 + .../ap/test/bugs/_3485/Issue3485Test.java | 2 +- .../java8time/SourceTargetMapper.java | 1 + .../java/MultiLineExpressionMapper.java | 1 - .../expressions/java/SourceTargetMapper.java | 1 - .../compilation/annotation/ExpectedNote.java | 2 +- .../testutil/runner/CompilingExtension.java | 8 ++--- 12 files changed, 34 insertions(+), 26 deletions(-) diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -107,19 +107,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - @@ -138,5 +125,28 @@ + + jdk-21-or-newer + + [21 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + check-style + verify + + checkstyle + + + + + + + diff --git a/parent/pom.xml b/parent/pom.xml index 8c1266ed19..f7d26dfd22 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -36,7 +36,7 @@ 3.1.0 6.2.7 1.6.0 - 8.36.1 + 13.0.0 5.10.1 2.2.0 1.12.0 @@ -376,7 +376,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.1 + 3.6.0 build-config/checkstyle.xml true diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java index 293df9516b..7ed0140d81 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java @@ -147,7 +147,6 @@ public boolean isConverted() { */ void setSourceLoopVarName(String sourceLoopVarName); - /** * Returns whether the type of assignment * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index bea4d56101..ba32b12b46 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -97,7 +97,6 @@ public interface Method { */ Parameter getTargetTypeParameter(); - /** * Returns the {@link Accessibility} of this method. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java index d43b52953d..4a30b776b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java @@ -40,7 +40,7 @@ public interface ModelElementProcessor { * * @author Gunnar Morling */ - public interface ProcessorContext { + interface ProcessorContext { Filer getFiler(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java index 5714e04684..dc1558907c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java @@ -17,6 +17,7 @@ public interface ErroneousIssue3485Mapper { ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + class Target { private final String value; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java index 7427b63a4e..964de36ec8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -24,7 +24,7 @@ public class Issue3485Test { diagnostics = { @Diagnostic(type = ErroneousIssue3485Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 33, + line = 34, message = "Using @Mapping( target = \".\") requires a source property. Expression or " + "constant cannot be used as a source.") }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java index 37fc7d6cbe..f2351f4175 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java @@ -26,6 +26,7 @@ public interface SourceTargetMapper { String LOCAL_TIME_FORMAT = "HH:mm"; SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + @Mappings( { @Mapping( target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT ), @Mapping( target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT ), @Mapping( target = "localDate", dateFormat = LOCAL_DATE_FORMAT ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java index 50d0b4b2cd..76286bfa3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java @@ -17,7 +17,6 @@ @Mapper(imports = TimeAndFormat.class) public interface MultiLineExpressionMapper { - MultiLineExpressionMapper INSTANCE = Mappers.getMapper( MultiLineExpressionMapper.class ); @Mappings({ diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java index 0e462ab3b3..56809494a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java @@ -18,7 +18,6 @@ @Mapper( imports = TimeAndFormat.class ) public interface SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings( { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java index 1cb95f52c2..2070c028c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java @@ -29,7 +29,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) - public @interface ExpectedNotes { + @interface ExpectedNotes { /** * Regexp for the note to match. diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 08056ca697..bcfc707d68 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -25,11 +25,11 @@ import java.util.Set; import java.util.stream.Collectors; +import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; import com.puppycrawl.tools.checkstyle.DefaultLogger; import com.puppycrawl.tools.checkstyle.PropertiesExpander; -import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; @@ -225,10 +225,10 @@ private void assertCheckstyleRules() throws Exception { ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); checker.addListener( new DefaultLogger( - NullOutputStream.NULL_OUTPUT_STREAM, - AutomaticBean.OutputStreamOptions.CLOSE, + NullOutputStream.INSTANCE, + AbstractAutomaticBean.OutputStreamOptions.CLOSE, errorStream, - AutomaticBean.OutputStreamOptions.CLOSE + AbstractAutomaticBean.OutputStreamOptions.CLOSE ) ); From 60151132145a606990224376d35be6eaff9e250f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 22:33:00 +0100 Subject: [PATCH 045/125] #3404 Add support for Kotlin Sealed classes --- NEXT_RELEASE_CHANGELOG.md | 1 + .../ap/internal/model/common/Type.java | 26 ++++++ .../internal/util/kotlin/KotlinMetadata.java | 5 ++ .../mapstruct/ap/test/kotlin/sealed/Dtos.kt | 36 +++++++++ .../ap/test/kotlin/sealed/Entities.kt | 36 +++++++++ .../kotlin/sealed/SealedSubclassMapper.java | 31 +++++++ .../kotlin/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ 7 files changed, 215 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index f279193f6a..1f7026c127 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -7,6 +7,7 @@ - Proper primary constructor detection - Data classes with multiple constructors - Data classes with all default parameters + - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes ### Enhancements diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 313ff38125..8fb10090c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -47,6 +47,7 @@ import kotlin.metadata.Attributes; import kotlin.metadata.KmClass; import kotlin.metadata.KmConstructor; +import kotlin.metadata.Modality; import kotlin.metadata.jvm.JvmExtensionsKt; import kotlin.metadata.jvm.JvmMethodSignature; import kotlin.metadata.jvm.KotlinClassMetadata; @@ -1850,6 +1851,10 @@ public boolean isEnumSet() { * return true if this type is a java 17+ sealed class */ public boolean isSealed() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.isSealedClass(); + } return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals ); } @@ -1858,6 +1863,10 @@ public boolean isSealed() { */ @SuppressWarnings( "unchecked" ) public List getPermittedSubclasses() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.getPermittedSubclasses(); + } if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { return emptyList(); } @@ -1882,6 +1891,11 @@ public boolean isDataClass() { return Attributes.isData( kotlinClassMetadata.getKmClass() ); } + @Override + public boolean isSealedClass() { + return Attributes.getModality( kotlinClassMetadata.getKmClass() ) == Modality.SEALED; + } + @Override public ExecutableElement determinePrimaryConstructor(List constructors) { if ( constructors.size() == 1 ) { @@ -1928,6 +1942,18 @@ public ExecutableElement determinePrimaryConstructor(List con return null; } + @Override + public List getPermittedSubclasses() { + List sealedSubclassNames = kotlinClassMetadata.getKmClass().getSealedSubclasses(); + List permittedSubclasses = new ArrayList<>( sealedSubclassNames.size() ); + for ( String sealedSubclassName : sealedSubclassNames ) { + Type subclassType = typeFactory.getType( sealedSubclassName.replace( '/', '.' ) ); + permittedSubclasses.add( subclassType.getTypeMirror() ); + } + + return permittedSubclasses; + } + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { StringBuilder signature = new StringBuilder( "(" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java index d58ab75c79..66a6fafc02 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java @@ -7,6 +7,7 @@ import java.util.List; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; /** * Information about a type in case it's a Kotlin type. @@ -17,5 +18,9 @@ public interface KotlinMetadata { boolean isDataClass(); + boolean isSealedClass(); + ExecutableElement determinePrimaryConstructor(List constructors); + + List getPermittedSubclasses(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt new file mode 100644 index 0000000000..5a9084a685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed + +sealed class VehicleDto { + var name: String? = null + var maker: String? = null + + class BikeDto : VehicleDto() { + var numberOfGears: Int = 0 + } + + class CarDto : VehicleDto() { + var manual: Boolean = false + } + +} + +sealed class MotorDto : VehicleDto() { + var cc: Int = 0 + + class DavidsonDto : MotorDto() { + var numberOfExhausts: Int = 0 + } + + class HarleyDto : MotorDto() { + var engineDb: Int = 0 + } +} + +class VehicleCollectionDto { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt new file mode 100644 index 0000000000..b380bac231 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt @@ -0,0 +1,36 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed + +sealed class Vehicle { + var name: String? = null + var vehicleManufacturingCompany: String? = null + + class Bike : Vehicle() { + var numberOfGears: Int = 0 + } + + class Car : Vehicle() { + var manual: Boolean = false + } + +} + +sealed class Motor : Vehicle() { + var cc: Int = 0 + + class Davidson : Motor() { + var numberOfExhausts: Int = 0 + } + + class Harley : Motor() { + var engineDb: Int = 0 + } +} + +class VehicleCollection { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..bde7dc24cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java @@ -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.ap.test.kotlin.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f7162ec3a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SealedSubclassMapper.class, +}) +@WithKotlinSources({ + "Dtos.kt", + "Entities.kt" +}) +@WithKotlin +public class SealedSubclassTest { + + @ProcessorTest + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} From 9923f78d91a8dae76f210c5de2ac0013baa078b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 30 Jan 2026 12:25:56 +0100 Subject: [PATCH 046/125] #674 Add support for `Optional` (#3971) MapStruct now fully supports Optional as both source and target types: * `Optional` to `Optional` - Both source and target wrapped in `Optional` * `Optional` to Non-`Optional` - Unwrapping `Optional` values * Non-`Optional` to `Optional` - Wrapping values in `Optional` * Always assume that optionals are not `null`, i.e. we can always call `isPresent()` / `isEmpty()` on an `Optional` * Add support for conversions e.g. `Optional` -> `String`, `Optional` -> `String` etc. --------- Co-authored-by: Ken Wang --- NEXT_RELEASE_CHANGELOG.md | 5 + copyright.txt | 3 +- .../mapstruct/NullValueMappingStrategy.java | 6 +- .../NullValuePropertyMappingStrategy.java | 5 +- ...apter-10-advanced-mapping-options.asciidoc | 17 +- .../chapter-3-defining-a-mapper.asciidoc | 415 +++++++++++ .../chapter-5-data-type-conversions.asciidoc | 8 + .../ap/internal/conversion/Conversions.java | 35 + .../OptionalWrapperConversionProvider.java | 97 +++ .../conversion/TypeToOptionalConversion.java | 44 ++ .../ap/internal/model/BeanMappingMethod.java | 160 +++- .../model/FromOptionalTypeConversion.java | 113 +++ .../model/LifecycleMethodResolver.java | 33 +- .../internal/model/MappingBuilderContext.java | 11 + .../ap/internal/model/MappingMethod.java | 6 +- .../model/NestedPropertyMappingMethod.java | 63 +- .../model/PresenceCheckMethodResolver.java | 6 +- .../ap/internal/model/PropertyMapping.java | 16 +- .../model/ToOptionalTypeConversion.java | 119 +++ .../model/assignment/OptionalGetWrapper.java | 35 + .../model/beanmapping/SourceReference.java | 10 + .../model/common/ConversionContext.java | 7 + .../common/DefaultConversionContext.java | 5 + .../ap/internal/model/common/Parameter.java | 26 +- .../ap/internal/model/common/Type.java | 23 +- .../model/presence/OptionalPresenceCheck.java | 77 ++ .../ap/internal/model/source/Method.java | 9 - .../source/selector/SelectionContext.java | 21 +- .../processor/DefaultVersionInformation.java | 7 + .../processor/MapperCreationProcessor.java | 1 + .../creation/MappingResolverImpl.java | 13 +- .../internal/version/VersionInformation.java | 2 + .../ap/internal/model/BeanMappingMethod.ftl | 81 +- .../model/FromOptionalTypeConversion.ftl | 16 + .../model/ToOptionalTypeConversion.ftl | 21 + .../model/assignment/OptionalGetWrapper.ftl | 15 + .../model/assignment/UpdateWrapper.ftl | 4 +- .../ap/internal/model/macro/CommonMacros.ftl | 28 +- .../model/presence/OptionalPresenceCheck.ftl | 19 + .../OptionalEnumToIntegerConversionTest.java | 73 ++ .../_enum/OptionalEnumToIntegerMapper.java | 19 + .../_enum/OptionalEnumToIntegerSource.java | 31 + .../bignumbers/BigDecimalOptionalMapper.java | 19 + .../bignumbers/BigDecimalOptionalSource.java | 139 ++++ .../bignumbers/BigIntegerOptionalMapper.java | 19 + .../bignumbers/BigIntegerOptionalSource.java | 131 ++++ .../bignumbers/BigNumbersConversionTest.java | 150 ++++ .../Java8OptionalTimeConversionTest.java | 466 ++++++++++++ .../conversion/java8time/OptionalSource.java | 160 ++++ .../java8time/OptionalSourceTargetMapper.java | 70 ++ ...ErroneousKitchenDrawerOptionalMapper1.java | 23 + ...ErroneousKitchenDrawerOptionalMapper3.java | 23 + ...ErroneousKitchenDrawerOptionalMapper4.java | 23 + ...ErroneousKitchenDrawerOptionalMapper5.java | 23 + ...ErroneousKitchenDrawerOptionalMapper6.java | 20 + .../lossy/KitchenDrawerOptionalMapper2.java | 23 + .../lossy/KitchenDrawerOptionalMapper6.java | 23 + .../conversion/lossy/LossyConversionTest.java | 94 +++ .../OversizedKitchenDrawerOptionalDto.java | 84 +++ .../lossy/VerySpecialNumberMapper.java | 6 + .../ByteWrapperOptionalSource.java | 121 +++ .../nativetypes/DoubleOptionalSource.java | 121 +++ .../DoubleWrapperOptionalSource.java | 121 +++ .../FloatWrapperOptionalSource.java | 121 +++ .../nativetypes/IntOptionalSource.java | 121 +++ .../nativetypes/IntWrapperOptionalSource.java | 121 +++ .../nativetypes/LongOptionalSource.java | 121 +++ .../LongWrapperOptionalSource.java | 121 +++ .../NumberOptionalConversionTest.java | 357 +++++++++ .../OptionalNumberConversionMapper.java | 52 ++ .../ShortWrapperOptionalSource.java | 121 +++ .../OptionalBeforeAfterMapper.java | 76 ++ .../beforeafter/OptionalBeforeAfterTest.java | 25 + .../ap/test/optional/beforeafter/Source.java | 87 +++ .../ap/test/optional/beforeafter/Target.java | 86 +++ .../optional/builder/OptionalBuilderTest.java | 31 + .../builder/SimpleOptionalBuilderMapper.java | 67 ++ .../optional/custom/CustomOptionalMapper.java | 63 ++ .../optional/custom/CustomOptionalTest.java | 60 ++ .../OptionalDifferentTypesMapper.java | 18 + .../OptionalDifferentTypesTest.java | 204 +++++ .../test/optional/differenttypes/Source.java | 89 +++ .../test/optional/differenttypes/Target.java | 84 +++ .../optional/lifecycle/MappingContext.java | 108 +++ .../lifecycle/OptionalLifecycleTest.java | 165 +++++ .../lifecycle/OptionalToOptionalMapper.java | 27 + .../OptionalToOptionalWithBuilderMapper.java | 24 + .../lifecycle/OptionalToTypeMapper.java | 27 + .../OptionalToTypeMultiSourceMapper.java | 27 + .../ap/test/optional/lifecycle/Source.java | 21 + .../ap/test/optional/lifecycle/Target.java | 39 + .../ap/test/optional/nested/Artist.java | 81 ++ .../optional/nested/OptionalNestedMapper.java | 23 + ...ptionalNestedPresenceCheckFirstMapper.java | 52 ++ .../OptionalNestedPresenceCheckMapper.java | 44 ++ .../optional/nested/OptionalNestedTest.java | 148 ++++ .../ap/test/optional/nested/Source.java | 76 ++ .../ap/test/optional/nested/Target.java | 48 ++ .../test/optional/nested/TargetAggregate.java | 49 ++ .../OptionalNullCheckAlwaysMapper.java | 19 + .../OptionalNullCheckAlwaysTest.java | 84 +++ .../test/optional/nullcheckalways/Source.java | 40 + .../test/optional/nullcheckalways/Target.java | 40 + .../nullvalue/OptionalDefaultMapperTest.java | 2 - .../nullvalue/SimpleConstructorMapper.java | 29 - .../sametype/OptionalSameTypeMapper.java | 18 + .../sametype/OptionalSameTypeTest.java | 186 +++++ .../ap/test/optional/sametype/Source.java | 74 ++ .../ap/test/optional/sametype/Target.java | 70 ++ .../optional/simple/OptionalSimpleTest.java | 110 +++ .../simple/OptionalToOptionalMapper.java | 23 + ...ionalToOptionalNullValueDefaultMapper.java | 25 + .../optional/simple/OptionalToTypeMapper.java | 23 + .../OptionalToTypeNullValueDefaultMapper.java | 24 + .../OptionalToTypeWithMappingMapper.java | 67 ++ .../ap/test/optional/simple/Source.java | 21 + .../ap/test/optional/simple/Target.java | 22 + .../optional/simple/TypeToOptionalMapper.java | 23 + .../optional/update/OptionalUpdateMapper.java | 21 + ...alUpdateNullValuePropertyIgnoreMapper.java | 23 + ...pdateNullValuePropertyToDefaultMapper.java | 23 + .../optional/update/OptionalUpdateTest.java | 117 +++ .../ap/test/optional/update/Source.java | 37 + .../ap/test/optional/update/Target.java | 37 + .../testutil/assertions/JavaFileAssert.java | 2 +- .../OptionalSourceTargetMapperImpl.java | 701 ++++++++++++++++++ .../OptionalBeforeAfterMapperImpl.java | 113 +++ .../OptionalDifferentTypesMapperImpl.java | 89 +++ .../nested/OptionalNestedMapperImpl.java | 71 ++ ...nalNestedPresenceCheckFirstMapperImpl.java | 117 +++ ...OptionalNestedPresenceCheckMapperImpl.java | 100 +++ .../OptionalNullCheckAlwaysMapperImpl.java | 38 + .../sametype/OptionalSameTypeMapperImpl.java | 55 ++ 133 files changed, 8860 insertions(+), 132 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/TargetAggregate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Target.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalSimpleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeWithMappingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/TypeToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyIgnoreMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyToDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Target.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapperImpl.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 1f7026c127..59fed4fed3 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,11 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: + - `Optional` to `Optional` - Both source and target wrapped in `Optional` + - `Optional` to Non-`Optional` - Unwrapping `Optional` values + - Non-`Optional` to `Optional` - Wrapping values in `Optional` + - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. * Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - Single field data classes diff --git a/copyright.txt b/copyright.txt index 71d47a796a..bd0034b076 100644 --- a/copyright.txt +++ b/copyright.txt @@ -38,6 +38,7 @@ 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 +Ken Wang - https://github.com/ro0sterjam Kevin Grüneberg - https://github.com/kevcodez Lukas Lazar - https://github.com/LukeLaz Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis @@ -70,4 +71,4 @@ 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 +Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java index 519a28bd81..4cece83031 100644 --- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -13,8 +13,9 @@ public enum NullValueMappingStrategy { /** - * If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no - * alternative strategy is configured globally, for given mapper or method. + * If {@code null} is passed to a mapping method, {@code null} will be returned, unless the return type is + * {@link java.util.Optional Optional}, in which case an empty optional will be returned. + * That's the default behavior if no alternative strategy is configured globally, for given mapper or method. */ RETURN_NULL, @@ -28,6 +29,7 @@ public enum NullValueMappingStrategy { * case. *

  • For iterable mapping methods an empty collection will be returned.
  • *
  • For map mapping methods an empty map will be returned.
  • + *
  • For optional mapping methods an empty optional will be returned.
  • * */ RETURN_DEFAULT; diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 556d4253b1..a58eed8a2a 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -27,7 +27,9 @@ public enum NullValuePropertyMappingStrategy { /** - * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. + * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null} + * or {@link java.util.Optional#empty() Optional.empty()} in case the target type is an + * {@link java.util.Optional Optional}. */ SET_TO_NULL, @@ -36,6 +38,7 @@ public enum NullValuePropertyMappingStrategy { *

    * This means: *

      + *
    1. For {@code Optional} MapStruct generates an {@code Optional.empty()}
    2. *
    3. For {@code List} MapStruct generates an {@code ArrayList}
    4. *
    5. For {@code Map} a {@code HashMap}
    6. *
    7. For arrays an empty array
    8. diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index b5c116ffd3..43f8fc38a0 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -204,7 +204,8 @@ The mechanism is also present on iterable mapping and map mapping. `@IterableMap [[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. +MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. +By default `null` will be returned, or `Optional.empty()` if the return type is `Optional`. 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: @@ -243,7 +244,7 @@ How the value of the `NullValueMappingStrategy` is applied is the same as in <> for detailed examples and behavior with Optional types. +==== + [[checking-source-property-for-null-arguments]] === Controlling checking result for 'null' properties in bean mapping diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 81980a35ac..96c76fc20d 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -660,6 +660,421 @@ public class PersonMapperImpl implements PersonMapper { ---- ==== +[[mapping-with-optional]] +=== Using Optional + +MapStruct provides comprehensive support for Java's `Optional` type, allowing you to use `Optional` as both source and target types in your mappings. +MapStruct handles all common `Optional` mapping scenarios, including conversions, nested properties, update mappings, and integration with builders and constructors. + +[[optional-basic-behavior]] +==== Basic Optional Behavior + +When using `Optional` as a source type, MapStruct performs presence checks using `Optional.isEmpty()` / `!Optional.isPresent()` instead of `null` checks. +When using `Optional` as a target type, MapStruct returns `Optional.empty()` instead of `null` when the source is absent. + +[[optional-mapping-scenarios]] +==== Optional Mapping Scenarios + +MapStruct supports three primary mapping scenarios with `Optional`: + +1. `Optional` to `Optional` - Both source and target are wrapped in `Optional` +2. `Optional` to Non-`Optional` - Source is `Optional`, target is a regular type +3. Non-`Optional` to `Optional` - Source is a regular type, target is `Optional` + +.Example mapper showing all Optional scenarios +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductMapper { + + Optional map(Optional product); + + ProductDto unwrap(Optional product); + + Optional wrap(Product product); +} +---- +==== + +.Generated code for `Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional product) { + if ( product.isEmpty() ) { + return Optional.empty(); + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +.Generated code for `Optional` to Non-`Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public ProductDto unwrap(Optional product) { + if ( product.isEmpty() ) { + return null; + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return productDto; +} +---- +==== + +.Generated code for Non-`Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional wrap(Product product) { + if ( product == null ) { + return Optional.empty(); + } + + ProductDto productDto = new ProductDto(); + + productDto.setName( product.getName() ); + productDto.setPrice( product.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +[[optional-properties]] +==== Mapping Optional Properties + +MapStruct handles `Optional` properties within your beans seamlessly. +When mapping `Optional` properties, MapStruct generates appropriate presence checks before accessing the values. + +.Example with `Optional` properties +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Car { + private Optional make; + private Optional numberOfSeats; + private Person driver; + + // getters omitted for brevity +} + +public class CarDto { + private String manufacturer; + private int seatCount; + private PersonDto driver; + + // getters and setters omitted for brevity +} + +@Mapper +public interface CarMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "seatCount", source = "numberOfSeats") + CarDto carToCarDto(Car car); +} +---- +==== + +.Code generated by MapStruct for `Optional` properties +==== +[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.getMake().isPresent() ) { + carDto.setManufacturer( car.getMake().get() ); + } + if ( car.getNumberOfSeats().isPresent() ) { + carDto.setSeatCount( car.getNumberOfSeats().get() ); + } + carDto.setDriver( personToPersonDto( car.getDriver() ) ); + + return carDto; + } + + protected PersonDto personToPersonDto(Person person) { + //... + } +} +---- +==== + +[[optional-update-mappings]] +==== Update Mappings with Optional + +`Optional` works seamlessly with update mappings using `@MappingTarget`. +When the source `Optional` is present, the target object is updated with the unwrapped value. +When the source `Optional` is empty, the behavior depends on the null value property mapping strategy. + +.Update mapping with an `Optional` source +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductUpdateMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} + +public class ProductUpdateDto { + private Optional name; + private Optional price; + + // getters omitted +} + +public class Product { + private String name; + private BigDecimal price; + + // getters and setters omitted +} +---- +==== + +.Generated update method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public void updateProduct(ProductUpdateDto dto, Product product) { + if ( dto == null ) { + return; + } + + if ( dto.getName().isPresent() ) { + product.setName( dto.getName().get() ); + } + if ( dto.getPrice().isPresent() ) { + product.setPrice( dto.getPrice().get() ); + } +} +---- +==== + +[[optional-null-value-strategies]] +==== Null Value Mapping Strategies + +When working with `Optional` types, MapStruct's null value mapping strategies behave as follows: + +* For `Optional` return types: `NullValueMappingStrategy.RETURN_DEFAULT` will return `Optional.empty()` instead of `null` +* For `Optional` properties: When a source property `Optional` is empty, `NullValuePropertyMappingStrategy` determines whether to skip the property, set it to null/empty, or use a default value +* `IGNORE` strategy: When a source `Optional` is empty, the target property is not updated +* `SET_TO_NULL` strategy: When a source `Optional` is empty, the target property is set to `null` (for non-`Optional` targets) or `Optional.empty()` (for `Optional` targets) +* `SET_TO_DEFAULT` strategy: When a source `Optional` is empty, the target property is set to its default value + +.Using null value strategies with `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ProductMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} +---- +==== + +With the `IGNORE` strategy, when Optional properties are empty, they won't overwrite existing target values. + +[[optional-builders-constructors]] +==== Optional with Builders and Constructors + +MapStruct's Optional support works seamlessly with both builder patterns and constructor-based mappings. + +.Optional with builders +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface PersonMapper { + Optional map(Optional person); +} + +public class PersonDto { + private final String name; + private final Integer age; + + private PersonDto(Builder builder) { + this.name = builder.name; + this.age = builder.age; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private Integer age; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder age(Integer age) { + this.age = age; + return this; + } + + public PersonDto build() { + return new PersonDto(this); + } + } + + // getters omitted +} +---- +==== + +.Generated code using builder +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional person) { + if ( person.isEmpty() ) { + return Optional.empty(); + } + + Person personValue = person.get(); + + PersonDto.Builder builder = PersonDto.builder(); + + builder.name( personValue.getName() ); + builder.age( personValue.getAge() ); + + return Optional.of( builder.build() ); +} +---- +==== + +.Optional with constructors +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class PersonDto { + private final String name; + private final Integer age; + + public PersonDto(String name, Integer age) { + this.name = name; + this.age = age; + } + + // getters omitted +} +---- +==== + +When the target type uses constructors and has `Optional` properties, MapStruct will properly handle the `Optional` parameters. + +[[optional-complex-mappings]] +==== Complex Optional Mappings + +MapStruct can handle complex scenarios where `Optional` is combined with nested object mappings, collections, and custom mapping methods. + +.Complex nested Optional mappings +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface OrderMapper { + + OrderDto map(Order order); + + // MapStruct will automatically use this for nested mappings + CustomerDto map(Customer customer); +} + +public class Order { + private Optional customer; + private Optional
      shippingAddress; + + // getters omitted +} + +public class OrderDto { + private Optional customer; + private AddressDto shippingAddress; + + // getters and setters omitted +} +---- +==== + +When mapping nested objects that are wrapped in Optional, MapStruct will: + +1. Check if the source `Optional` is present +2. Extract the value from the `Optional` +3. Map the extracted value to the target type +4. Wrap the result in an `Optional` if the target is also Optional + +[[optional-best-practices]] +==== Best Practices and Limitations + +[NOTE] +==== +While MapStruct fully supports `Optional` for target properties (public fields or setter parameters), the Java community generally recommends using `Optional` only for return types (source properties / getters). +Consider your team's coding standards when deciding whether to use `Optional` for target properties. +==== + +[WARNING] +==== +Avoid using `null` when returning `Optional` values to prevent `NullPointerException`. +MapStruct assumes that if you're using `Optional`, the `Optional` itself is not null. +==== + [[mapping-map-to-bean]] === Mapping Map to Bean diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index ad49fe2960..9da7ff75c1 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -133,6 +133,14 @@ public interface CarMapper { * 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. +[NOTE] +==== +All the above conversions also work when the source or target type is wrapped in `java.util.Optional`, `java.util.OptionalDouble`, `java.util.OptionalLong` or `java.util.OptionalInt`. +For example, `Optional` can be converted to `String`, `int` can be converted to `Optional`, +or `Optional` can be converted to `Integer`. +The same conversion rules apply, with MapStruct automatically handling the wrapping and unwrapping of `Optional` values. +==== + [[mapping-object-references]] === Mapping object references diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 5090c903f3..076cd2f42e 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -333,6 +333,41 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro } public ConversionProvider getConversion(Type sourceType, Type targetType) { + if ( sourceType.isOptionalType() ) { + if ( targetType.isOptionalType() ) { + // We cannot convert optional to optional + return null; + } + Type sourceBaseType = sourceType.getOptionalBaseType(); + if ( sourceBaseType.equals( targetType ) ) { + // Optional -> Type + return TypeToOptionalConversion.OPTIONAL_TO_TYPE_CONVERSION; + } + + ConversionProvider conversionProvider = getInternalConversion( sourceBaseType, targetType ); + if ( conversionProvider != null ) { + return inverse( new OptionalWrapperConversionProvider( conversionProvider ) ); + } + + } + else if ( targetType.isOptionalType() ) { + // Type -> Optional + Type targetBaseType = targetType.getOptionalBaseType(); + if ( targetBaseType.equals( sourceType )) { + return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; + } + ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); + if ( conversionProvider != null ) { + return new OptionalWrapperConversionProvider( conversionProvider ); + } + return null; + + } + + return getInternalConversion( sourceType, targetType ); + } + + private ConversionProvider getInternalConversion(Type sourceType, Type targetType) { if ( sourceType.isEnumType() && ( targetType.equals( stringType ) || targetType.getBoxedEquivalent().equals( integerType ) ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java new file mode 100644 index 0000000000..2b3e1bd960 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.List; + +import org.mapstruct.ap.internal.model.FromOptionalTypeConversion; +import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.ToOptionalTypeConversion; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +/** + * @author Filip Hrisafov + */ +public class OptionalWrapperConversionProvider implements ConversionProvider { + + private final ConversionProvider conversionProvider; + + public OptionalWrapperConversionProvider(ConversionProvider conversionProvider) { + this.conversionProvider = conversionProvider; + } + + @Override + public Assignment to(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new ToOptionalTypeConversion( conversionContext.getTargetType(), assignment ); + } + + @Override + public Assignment from(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new FromOptionalTypeConversion( conversionContext.getSourceType(), assignment ); + } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperMethods( conversionContext ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperFields( conversionContext ); + } + + private static class OptionalConversionContext implements ConversionContext { + + private final ConversionContext delegate; + + private OptionalConversionContext(ConversionContext delegate) { + this.delegate = delegate; + } + + @Override + public Type getTargetType() { + return resolveType( delegate.getTargetType() ); + } + + @Override + public Type getSourceType() { + return resolveType( delegate.getSourceType() ); + } + + private Type resolveType(Type type) { + if ( type.isOptionalType() ) { + return type.getOptionalBaseType(); + } + return type; + } + + @Override + public String getDateFormat() { + return delegate.getDateFormat(); + } + + @Override + public String getNumberFormat() { + return delegate.getNumberFormat(); + } + + @Override + public String getLocale() { + return delegate.getLocale(); + } + + @Override + public TypeFactory getTypeFactory() { + return delegate.getTypeFactory(); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java new file mode 100644 index 0000000000..ad488d1a8f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -0,0 +1,44 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; + +/** + * @author Filip Hrisafov + */ +public class TypeToOptionalConversion extends SimpleConversion { + + static final TypeToOptionalConversion TYPE_TO_OPTIONAL_CONVERSION = new TypeToOptionalConversion(); + static final ConversionProvider OPTIONAL_TO_TYPE_CONVERSION = inverse( TYPE_TO_OPTIONAL_CONVERSION ); + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().asRawType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTargetType().asRawType() ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + StringBuilder sb = new StringBuilder(".get"); + Type optionalBaseType = conversionContext.getSourceType().getOptionalBaseType(); + if ( optionalBaseType.isPrimitive() ) { + sb.append( "As" ).append( Strings.capitalize( optionalBaseType.getName() ) ); + } + return sb.append( "()" ).toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 086773dbe9..c6fc7753f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -99,9 +100,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final BuilderType returnTypeBuilder; private final MethodReference finalizerMethod; private final String finalizedResultName; + private final String optionalResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithOptionalReturnType; private final Type subclassExhaustiveException; + private final Map sourceParametersReassignments; private final MappingReferences mappingReferences; @@ -119,6 +123,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final Map sourceParametersReassignments = new HashMap<>(); private MappingReferences mappingReferences; private List targetThisReferences; @@ -176,10 +181,18 @@ public BeanMappingMethod build() { if ( selectionParameters != null && selectionParameters.getResultType() != null ) { // This is a user-defined return type, which means we need to do some extra checks for it userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeImpl = userDefinedReturnType; returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); } else { - returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + Type methodReturnType = method.getReturnType(); + if ( methodReturnType.isOptionalType() ) { + returnTypeImpl = methodReturnType.getOptionalBaseType(); + } + else { + returnTypeImpl = methodReturnType; + } + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( returnTypeImpl, builder ); } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set @@ -195,7 +208,6 @@ public BeanMappingMethod build() { } } else if ( userDefinedReturnType != null ) { - returnTypeImpl = userDefinedReturnType; initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; @@ -205,7 +217,6 @@ else if ( userDefinedReturnType != null ) { } } else if ( !method.isUpdateMethod() ) { - returnTypeImpl = method.getReturnType(); initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) @@ -266,12 +277,25 @@ else if ( !method.isUpdateMethod() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || - sourceParameter.getType().isMapType() ) { + Type sourceParameterType = sourceParameter.getType(); + if ( sourceParameterType.isOptionalType() ) { + String sourceParameterValueName = Strings.getSafeVariableName( + sourceParameter.getName() + "Value", + existingVariableNames + ); + existingVariableNames.add( sourceParameterValueName ); + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParametersReassignments.put( + sourceParameter.getName(), + new Parameter( sourceParameterValueName, sourceParameter.getName(), sourceParameterType ) + ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() || + sourceParameterType.isMapType() ) { continue; } - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); + Map readAccessors = sourceParameterType.getPropertyReadAccessors(); unprocessedSourceProperties.putAll( readAccessors ); } @@ -362,12 +386,24 @@ else if ( !method.isUpdateMethod() ) { ctx, existingVariableNames ); + + Supplier> additionalAfterMappingParameterBindingsProvider = () -> + sourceParametersReassignments.values() + .stream() + .map( parameter -> method.getSourceParameters().size() == 1 ? + ParameterBinding.fromParameter( parameter ) : + ParameterBinding.fromTypeAndName( + parameter.getType(), + parameter.getOriginalName() + ".get()" + ) ) + .collect( Collectors.toList() ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, resultTypeToMap, selectionParameters, ctx, - existingVariableNames + existingVariableNames, + Collections::emptyList ); if ( method instanceof ForgedMethod ) { @@ -409,12 +445,15 @@ else if ( !method.isUpdateMethod() ) { if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { finalizeMethod = getFinalizerMethod(); - Type actualReturnType = method.getReturnType(); + Type finalizerReturnType = method.getReturnType(); + if ( finalizerReturnType.isOptionalType() ) { + finalizerReturnType = finalizerReturnType.getOptionalBaseType(); + } beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget( LifecycleMethodResolver.beforeMappingMethods( method, - actualReturnType, + finalizerReturnType, selectionParameters, ctx, existingVariableNames @@ -424,14 +463,33 @@ else if ( !method.isUpdateMethod() ) { afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( method, - actualReturnType, + finalizerReturnType, + selectionParameters, + ctx, + existingVariableNames, + additionalAfterMappingParameterBindingsProvider + ) ); + + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + } + + List afterMappingReferencesWithOptionalReturnType = new ArrayList<>(); + if ( method.getReturnType().isOptionalType() ) { + afterMappingReferencesWithOptionalReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + method.getReturnType(), selectionParameters, ctx, - existingVariableNames + existingVariableNames, + additionalAfterMappingParameterBindingsProvider ) ); - keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType ); - keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType ); + keepMappingReferencesUsingTarget( + afterMappingReferencesWithOptionalReturnType, + method.getReturnType() + ); + } Map presenceChecksByParameter = new LinkedHashMap<>(); @@ -461,11 +519,13 @@ else if ( !method.isUpdateMethod() ) { afterMappingMethods, beforeMappingReferencesWithFinalizedReturnType, afterMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithOptionalReturnType, finalizeMethod, mappingReferences, subclasses, presenceChecksByParameter, - subclassExhaustiveExceptionType + subclassExhaustiveExceptionType, + sourceParametersReassignments ); } @@ -609,8 +669,17 @@ private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { * builder is not assignable to the return type (so without building). */ private boolean isBuilderRequired() { - return returnTypeBuilder != null - && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() ); + if ( returnTypeBuilder == null ) { + return false; + } + if ( method.isUpdateMethod() ) { + // when @MappingTarget annotated parameter is the same type as the return type. + return !method.getResultType().isAssignableTo( method.getReturnType() ); + } + else { + // For non-update methods a builder is required when returnTypeBuilder is set + return true; + } } private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) { @@ -1510,6 +1579,7 @@ else if ( mapping.getJavaExpression() != null ) { if ( sourceRef != null ) { // sourceRef == null is not considered an error here if ( sourceRef.isValid() ) { + Parameter sourceParameter = sourceRef.getParameter(); // targetProperty == null can occur: we arrived here because we want as many errors // as possible before we stop analysing @@ -1518,7 +1588,8 @@ else if ( mapping.getJavaExpression() != null ) { .sourceMethod( method ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourcePropertyName( mapping.getSourceName() ) - .sourceReference( sourceRef ) + .sourceReference( sourceRef.withParameter( + sourceParametersReassignments.get( sourceParameter.getName() ) ) ) .selectionParameters( mapping.getSelectionParameters() ) .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) @@ -1530,7 +1601,6 @@ else if ( mapping.getJavaExpression() != null ) { .options( mapping ) .build(); handledTargets.add( targetPropertyName ); - Parameter sourceParameter = sourceRef.getParameter(); unprocessedSourceParameters.remove( sourceParameter ); // If the source parameter was directly mapped if ( sourceRef.getPropertyEntries().isEmpty() ) { @@ -1743,20 +1813,26 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri SourceReference sourceRef = null; - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { + Type sourceParameterType = sourceParameter.getType(); + Parameter sourceParameterToUse = sourceParameter; + if ( sourceParameterType.isOptionalType() ) { + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParameterToUse = sourceParametersReassignments.get( sourceParameter.getName() ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() ) { return sourceRef; } - ReadAccessor sourceReadAccessor = sourceParameter.getType() + ReadAccessor sourceReadAccessor = sourceParameterType .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = - sourceParameter.getType().getPresenceChecker( targetPropertyName ); + sourceParameterType.getPresenceChecker( targetPropertyName ); - DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); + DeclaredType declaredSourceType = (DeclaredType) sourceParameterType.getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); - sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) + sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameterToUse ) .type( returnType ) .readAccessor( sourceReadAccessor ) .presenceChecker( sourcePresenceChecker ) @@ -2033,11 +2109,14 @@ private BeanMappingMethod(Method method, List afterMappingReferences, List beforeMappingReferencesWithFinalizedReturnType, List afterMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithOptionalReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, Map presenceChecksByParameter, - Type subclassExhaustiveException) { + Type subclassExhaustiveException, + Map sourceParametersReassignments + ) { super( method, annotations, @@ -2057,14 +2136,21 @@ private BeanMappingMethod(Method method, this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); existingVariableNames.add( this.finalizedResultName ); + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "ResultOptional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } else { this.finalizedResultName = null; + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "Optional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } this.mappingReferences = mappingReferences; this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType; this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithOptionalReturnType = afterMappingReferencesWithOptionalReturnType; // initialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. @@ -2099,6 +2185,7 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { } this.returnTypeToConstruct = returnTypeToConstruct; this.subclassMappings = subclassMappings; + this.sourceParametersReassignments = sourceParametersReassignments; } public Type getSubclassExhaustiveException() { @@ -2121,6 +2208,18 @@ public String getFinalizedResultName() { return finalizedResultName; } + public Type getFinalizedReturnType() { + Type returnType = getReturnType(); + if ( returnType.isOptionalType() ) { + return returnType.getOptionalBaseType(); + } + return returnType; + } + + public String getOptionalResultName() { + return optionalResultName; + } + public List getBeforeMappingReferencesWithFinalizedReturnType() { return beforeMappingReferencesWithFinalizedReturnType; } @@ -2129,6 +2228,10 @@ public List getAfterMappingReferencesWithFinal return afterMappingReferencesWithFinalizedReturnType; } + public List getAfterMappingReferencesWithOptionalReturnType() { + return afterMappingReferencesWithOptionalReturnType; + } + public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); @@ -2188,6 +2291,10 @@ public Set getImportTypes() { types.addAll( reference.getImportTypes() ); } + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithOptionalReturnType ) { + types.addAll( reference.getImportTypes() ); + } + return types; } @@ -2215,6 +2322,10 @@ public List getSourceParametersNotNeedingPresenceCheck() { .collect( Collectors.toList() ); } + public Parameter getSourceParameterReassignment(Parameter parameter) { + return sourceParametersReassignments.get( parameter.getName() ); + } + private boolean needsPresenceCheck(Parameter parameter) { if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) { return false; @@ -2277,5 +2388,4 @@ public boolean equals(Object obj) { return true; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java new file mode 100644 index 0000000000..f8c596a368 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java @@ -0,0 +1,113 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model; + +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class FromOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type optionalType; + + public FromOptionalTypeConversion(Type optionalType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.optionalType = optionalType; + } + + @Override + public Set getImportTypes() { + return conversionAssignment.getImportTypes(); + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + this.conversionAssignment.setAssignment( new OptionalGetWrapper( assignment, optionalType ) ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return conversionAssignment.toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index cfe4f9f8b7..ada9dac470 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -6,11 +6,14 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; @@ -63,13 +66,16 @@ public static List afterMappingMethods(Method Type alternativeTarget, SelectionParameters selectionParameters, MappingBuilderContext ctx, - Set existingVariableNames) { + Set existingVariableNames, + Supplier> parameterBindingsProvider) { return collectLifecycleCallbackMethods( method, alternativeTarget, selectionParameters, filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), ctx, - existingVariableNames ); + existingVariableNames, + parameterBindingsProvider + ); } /** @@ -131,13 +137,34 @@ private static List getAllAvailableMethods(Method method, List collectLifecycleCallbackMethods( Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, MappingBuilderContext ctx, Set existingVariableNames) { + return collectLifecycleCallbackMethods( + method, + targetType, + selectionParameters, + callbackMethods, + ctx, + existingVariableNames, + Collections::emptyList + ); + } + + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames, + Supplier> parameterBindingsProvider) { MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, - SelectionContext.forLifecycleMethods( method, targetType, selectionParameters, ctx.getTypeFactory() ) + SelectionContext.forLifecycleMethods( + method, + targetType, + selectionParameters, + ctx.getTypeFactory(), + parameterBindingsProvider + ) ); return toLifecycleCallbackMethodRefs( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index 95c229c6e6..1ebf99ffeb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -30,6 +30,7 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Services; import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.spi.EnumMappingStrategy; import org.mapstruct.ap.spi.EnumTransformationStrategy; import org.mapstruct.ap.spi.MappingExclusionProvider; @@ -109,6 +110,7 @@ Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory descrip private final ElementUtils elementUtils; private final TypeUtils typeUtils; private final FormattingMessager messager; + private final VersionInformation versionInformation; private final AccessorNamingUtils accessorNaming; private final EnumMappingStrategy enumMappingStrategy; private final Map enumTransformationStrategies; @@ -126,6 +128,7 @@ public MappingBuilderContext(TypeFactory typeFactory, ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, + VersionInformation versionInformation, AccessorNamingUtils accessorNaming, EnumMappingStrategy enumMappingStrategy, Map enumTransformationStrategies, @@ -138,6 +141,7 @@ public MappingBuilderContext(TypeFactory typeFactory, this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.versionInformation = versionInformation; this.accessorNaming = accessorNaming; this.enumMappingStrategy = enumMappingStrategy; this.enumTransformationStrategies = enumTransformationStrategies; @@ -190,6 +194,10 @@ public FormattingMessager getMessager() { return messager; } + public VersionInformation getVersionInformation() { + return versionInformation; + } + public AccessorNamingUtils getAccessorNaming() { return accessorNaming; } @@ -264,6 +272,9 @@ public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType * @return {@code true} if the type is not excluded from the {@link MappingExclusionProvider} */ private boolean canGenerateAutoSubMappingFor(Type type) { + if ( "java.util.Optional".equals( type.getFullyQualifiedName() ) ) { + return !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getOptionalBaseType().getTypeElement() ); + } return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java index 599ddf1d35..0d04f5b6d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java @@ -97,7 +97,11 @@ else if ( getResultType().isArrayType() ) { return name; } else { - String name = getSafeVariableName( getResultType().getName(), existingVarNames ); + Type resultType = getResultType(); + if ( resultType.isOptionalType() ) { + resultType = resultType.getOptionalBaseType(); + } + String name = getSafeVariableName( resultType.getName(), existingVarNames ); existingVarNames.add( name ); return name; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 25c9f8fc03..ed841ba5bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -17,6 +17,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -71,21 +72,58 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); + Type previousPropertyType = sourceParameter.getType(); for ( int i = 0; i < propertyEntries.size(); i++ ) { PropertyEntry propertyEntry = propertyEntries.get( i ); - PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); - - if ( i > 0 ) { - // If this is not the first property entry, - // then we might need to combine the presence check with a null check of the previous property - if ( presenceCheck != null ) { - presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( - new NullPresenceCheck( previousPropertyName, true ), - presenceCheck - ) ); + PresenceCheck presenceCheck; + + if ( previousPropertyType.isOptionalType() ) { + String optionalValueSafeName = Strings.getSafeVariableName( + previousPropertyName + "Value", + existingVariableNames + ); + existingVariableNames.add( optionalValueSafeName ); + + presenceCheck = getPresenceCheck( propertyEntry, optionalValueSafeName ); + + String optionalValueSource = previousPropertyName + ".get()"; + boolean doesNotNeedFollowUpProperty = false; + if ( i == propertyEntries.size() - 1 ) { + // If this is the last property, and we do not have a presence check, + // then we do not need to assign the optional value + // e.g., we need to generate .get().getXxx(); + doesNotNeedFollowUpProperty = presenceCheck == null; + if ( doesNotNeedFollowUpProperty ) { + optionalValueSource += "." + propertyEntry.getReadAccessor().getReadValueSource(); + } } - else { - presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + Type optionalBaseType = previousPropertyType.getOptionalBaseType(); + safePropertyEntries.add( new SafePropertyEntry( + optionalBaseType, + optionalValueSafeName, + optionalValueSource, + new OptionalPresenceCheck( previousPropertyName, ctx.getVersionInformation(), true ) + ) ); + if ( doesNotNeedFollowUpProperty ) { + break; + } + previousPropertyName = optionalValueSafeName; + + } + else { + presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } } } @@ -101,6 +139,7 @@ public NestedPropertyMappingMethod build() { thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); previousPropertyName = safeName; + previousPropertyType = propertyEntry.getType(); } method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 049e4caf37..784342b8d7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -87,7 +88,10 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { - if ( !sourceParameter.getType().isPrimitive() ) { + if ( sourceParameter.getType().isOptionalType() ) { + return new OptionalPresenceCheck( sourceParameter.getName(), ctx.getVersionInformation() ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { return new NullPresenceCheck( sourceParameter.getName() ); } return null; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 08e8adbd1b..622ba9cef0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -38,6 +38,7 @@ import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.model.source.DelegatingOptions; import org.mapstruct.ap.internal.model.source.MappingControl; @@ -290,7 +291,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get return new PropertyMapping( sourcePropertyName, targetPropertyName, - rightHandSide.getSourceParameterName(), + sourceReference.getParameter().getOriginalName(), targetWriteAccessor.getSimpleName(), targetReadAccessor, targetType, @@ -703,15 +704,26 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc String variableName = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); + Type variableType = propertyEntry.getType(); for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { - presenceChecks.add( new NullPresenceCheck( variableName ) ); + if ( variableType.isOptionalType() ) { + presenceChecks.add( new OptionalPresenceCheck( + variableName, + ctx.getVersionInformation() + ) ); + variableName = variableName + ".get()"; + } + else { + presenceChecks.add( new NullPresenceCheck( variableName ) ); + } presenceChecks.add( new SuffixPresenceCheck( variableName, entry.getPresenceChecker().getPresenceCheckSuffix() ) ); variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()"; + variableType = entry.getType(); } else { break; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java new file mode 100644 index 0000000000..5925540e10 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -0,0 +1,119 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class ToOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type targetType; + + public ToOptionalTypeConversion(Type targetType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.targetType = targetType; + } + + public Type getTargetType() { + return targetType; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( conversionAssignment.getImportTypes() ); + importTypes.add( targetType ); + return importTypes; + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + conversionAssignment.setAssignment( assignment ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return targetType.getName() + ".of( " + conversionAssignment.toString() + " )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java new file mode 100644 index 0000000000..9064640bb4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +/** + * @author Filip Hrisafov + */ +public class OptionalGetWrapper extends AssignmentWrapper { + + private final Type optionalType; + + public OptionalGetWrapper(Assignment decoratedAssignment, Type optionalType) { + super( decoratedAssignment, false ); + this.optionalType = optionalType; + } + + public Type getOptionalType() { + return optionalType; + } + + @Override + public String toString() { + if ( optionalType.getFullyQualifiedName().equals( "java.util.Optional" ) ) { + return getAssignment() + ".get()"; + } + return getAssignment() + ".getAs" + Strings.capitalize( optionalType.getOptionalBaseType().getName() ) + "()"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 73ec84b6b7..c37b12e311 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -325,6 +325,9 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; Type noBoundsType = newType.withoutBounds(); + if ( noBoundsType.isOptionalType() ) { + noBoundsType = noBoundsType.getOptionalBaseType(); + } ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); if ( readAccessor != null ) { PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); @@ -465,4 +468,11 @@ public List push(TypeFactory typeFactory, FormattingMessager me return result; } + public SourceReference withParameter(Parameter parameter) { + if ( parameter == null || getParameter() == parameter ) { + return this; + } + return new SourceReference( parameter, getPropertyEntries(), isValid() ); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index 96d3d6fe78..d3a29e30a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -21,6 +21,13 @@ public interface ConversionContext { */ Type getTargetType(); + /** + * Returns the source type of this conversion. + * + * @return The source type of this conversion. + */ + Type getSourceType(); + /** * Returns the date format if this conversion or built-in method is from String to a date type (e.g. {@link Date}) * or vice versa. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index 159f1663e2..4e5eed47a8 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -61,6 +61,11 @@ public Type getTargetType() { return targetType; } + @Override + public Type getSourceType() { + return sourceType; + } + @Override public String getNumberFormat() { return numberFormat; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index aaab7f46ca..c5091869a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -51,12 +51,13 @@ private Parameter(Element element, Type type, boolean varArgs) { this.varArgs = varArgs; } - private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + private Parameter(String name, String originalName, Type type, boolean mappingTarget, boolean targetType, + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; - this.originalName = name; + this.originalName = originalName; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; @@ -67,7 +68,11 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false, false ); + this( name, name, type ); + } + + public Parameter(String name, String originalName, Type type) { + this( name, originalName, type, false, false, false, false, false, false ); } public Element getElement() { @@ -141,6 +146,20 @@ public boolean isSourceParameter() { !isTargetPropertyName(); } + public Parameter withName(String name) { + return new Parameter( + name, + this.name, + type, + mappingTarget, + targetType, + mappingContext, + sourcePropertyName, + targetPropertyName, + varArgs + ); + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -176,6 +195,7 @@ public static Parameter forElementAndType(VariableElement element, Type paramete public static Parameter forForgedMappingTarget(Type parameterType) { return new Parameter( + "mappingTarget", "mappingTarget", parameterType, true, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8fb10090c2..29bb3cd906 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -397,11 +397,32 @@ private boolean isType(Class type) { return type.getName().equals( getFullyQualifiedName() ); } - private boolean isOptionalType() { + public boolean isOptionalType() { return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || isType( OptionalLong.class ); } + public Type getOptionalBaseType() { + if ( isType( Optional.class ) ) { + return getTypeParameters().get( 0 ); + } + + if ( isType( OptionalInt.class ) ) { + return typeFactory.getType( int.class ); + } + + if ( isType( OptionalDouble.class ) ) { + return typeFactory.getType( double.class ); + } + + if ( isType( OptionalLong.class ) ) { + return typeFactory.getType( long.class ); + } + + throw new IllegalStateException( "getOptionalBaseType should only be called for Optional types." ); + + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java new file mode 100644 index 0000000000..d727c30f24 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java @@ -0,0 +1,77 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.version.VersionInformation; + +/** + * Presence checker for {@link java.util.Optional} types. + * + * @author Ken Wang + */ +public class OptionalPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final VersionInformation versionInformation; + private final boolean negate; + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation) { + this( sourceReference, versionInformation, false ); + } + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation, boolean negate) { + this.sourceReference = sourceReference; + this.versionInformation = versionInformation; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public VersionInformation getVersionInformation() { + return versionInformation; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new OptionalPresenceCheck( sourceReference, versionInformation, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + OptionalPresenceCheck that = (OptionalPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index ba32b12b46..d9c9ce41fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -182,15 +182,6 @@ default ConditionMethodOptions getConditionOptions() { return ConditionMethodOptions.empty(); } - /** - * - * @return true when @MappingTarget annotated parameter is the same type as the return type. The method has - * to be an update method in order for this to be true. - */ - default boolean isMappingTargetAssignableToReturnType() { - return isUpdateMethod() && getResultType().isAssignableTo( getReturnType() ); - } - /** * @return the first source type, intended for mapping methods from single source to target */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 84bd04d7db..79351ae868 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -106,8 +106,9 @@ public static SelectionContext forMappingMethods(Method mappingMethod, Type sour } public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType, - SelectionParameters selectionParameters, - TypeFactory typeFactory) { + SelectionParameters selectionParameters, + TypeFactory typeFactory, + Supplier> additionalParameterBindingsProvider) { SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters ); return new SelectionContext( null, @@ -115,12 +116,16 @@ public static SelectionContext forLifecycleMethods(Method mappingMethod, Type ta mappingMethod, targetType, mappingMethod.getResultType(), - () -> getAvailableParameterBindingsFromMethod( - mappingMethod, - targetType, - criteria.getSourceRHS(), - typeFactory - ) + () -> { + List parameterBindings = getAvailableParameterBindingsFromMethod( + mappingMethod, + targetType, + criteria.getSourceRHS(), + typeFactory + ); + parameterBindings.addAll( additionalParameterBindingsProvider.get() ); + return parameterBindings; + } ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index 055bfe6095..0457a66da4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -41,6 +41,7 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast11; private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -54,6 +55,7 @@ public class DefaultVersionInformation implements VersionInformation { this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast11 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 4; this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @@ -82,6 +84,11 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast11() { + return sourceVersionAtLeast11; + } + @Override public boolean isSourceVersionAtLeast19() { return sourceVersionAtLeast19; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index ba24e868aa..03e8bf977f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -119,6 +119,7 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L elementUtils, typeUtils, messager, + versionInformation, accessorNaming, context.getEnumMappingStrategy(), context.getEnumTransformationStrategies(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index ba903b6048..2a36f6b890 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -464,7 +464,7 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy Assignment conversion = conversionProvider.to( ctx ); if ( conversion != null ) { - return new ConversionAssignment( sourceType, targetType, conversionProvider.to( ctx ) ); + return new ConversionAssignment( sourceType, targetType, conversion ); } return null; } @@ -673,7 +673,16 @@ Assignment getAssignment() { void reportMessageWhenNarrowing(FormattingMessager messager, ResolvingAttempt attempt) { - if ( NativeTypes.isNarrowing( sourceType.getFullyQualifiedName(), targetType.getFullyQualifiedName() ) ) { + Type source = sourceType; + if ( sourceType.isOptionalType() ) { + source = sourceType.getOptionalBaseType(); + } + + Type target = targetType; + if ( targetType.isOptionalType() ) { + target = targetType.getOptionalBaseType(); + } + if ( NativeTypes.isNarrowing( source.getFullyQualifiedName(), target.getFullyQualifiedName() ) ) { ReportingPolicyGem policy = attempt.mappingMethod.getOptions().getMapper().typeConversionPolicy(); if ( policy == ReportingPolicyGem.WARN ) { report( messager, attempt, Message.CONVERSION_LOSSY_WARNING ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 5e1972fcae..411e11bf9e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -21,6 +21,8 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast11(); + boolean isSourceVersionAtLeast19(); boolean isEclipseJDTCompiler(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 059e2d77d4..4ad6ae20db 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -21,14 +21,22 @@ <#list beforeMappingReferencesWithFinalizedReturnType as callback> - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> <#if !callback_has_next> <#if !mapNullToDefault && !sourcePresenceChecks.empty> if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && ) { - return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; + <#if returnType.name == "void"> + return; + <#else> + <#if existingInstanceMapping> + <@createReturn applyOptionalAfterMapping=false>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /> + <#else> + return ${returnType.null}; + + } @@ -52,6 +60,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -71,6 +84,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -102,6 +120,11 @@ <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -117,6 +140,11 @@ <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -133,24 +161,24 @@ <#if returnType.name != "void"> - <#if finalizerMethod??> - <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> - <@includeModel object=returnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + <#if finalizerMethod??> + <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> + <@includeModel object=finalizedReturnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; - <#list afterMappingReferencesWithFinalizedReturnType as callback> - <#if callback_index = 0> + <#list afterMappingReferencesWithFinalizedReturnType as callback> + <#if callback_index = 0> - - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> - + + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> + - return ${finalizedResultName}; + <@createReturn>${finalizedResultName} + <#else> + <@createReturn>${resultName}.<@includeModel object=finalizerMethod /> + <#else> - return ${resultName}.<@includeModel object=finalizerMethod />; + <@createReturn>${resultName} - <#else> - return ${resultName}; - <#if hasSubclassMappings()> @@ -164,4 +192,27 @@ <#if exceptionType_has_next>, <#t> + +<#macro createReturn applyOptionalAfterMapping=true> +<#-- <@compress single_line=true>--> + <#if returnType.optionalType> + <#if (afterMappingReferencesWithOptionalReturnType?size > 0)> + <@includeModel object=returnType /> ${optionalResultName} = <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#list afterMappingReferencesWithOptionalReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=optionalResultName targetType=returnType/> + + + return ${optionalResultName}; + <#else> + return <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#else> + return <#nested/>; + +<#-- --> + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl new file mode 100644 index 0000000000..50ab97e5b4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.FromOptionalTypeConversion" --> +<@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl new file mode 100644 index 0000000000..48e20e7017 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ToOptionalTypeConversion" --> +<@compress single_line=true> +<@includeModel object=targetType.asRawType() />.of( <@_assignment/> ) +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl new file mode 100644 index 0000000000..7bbc089d7c --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper" --> +<@compress single_line=true> +<#if optionalType.optionalBaseType.isPrimitive()> +${assignment}.getAs${optionalType.optionalBaseType.name?cap_first}() +<#else> +${assignment}.get() + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index ea8eed2438..d8ed4b53f6 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -6,6 +6,8 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.UpdateWrapper" --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#import '../macro/CommonMacros.ftl' as lib > <@lib.handleExceptions> <#if includeSourceNullCheck> @@ -16,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 278b441aa9..9c4ad26e03 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -13,6 +13,8 @@ requires: caller to implement boolean:getIncludeSourceNullCheck() --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#macro handleSourceReferenceNullCheck> <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference @@ -23,7 +25,7 @@ } <@elseDefaultAssignment/> <#elseif includeSourceNullCheck || ext.defaultValueAssignment??> - if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}<#if sourceType.optionalType>.isPresent()<#else> != null ) { <#nested> } <@elseDefaultAssignment/> @@ -43,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } @@ -156,7 +158,7 @@ Performs a default assignment with a default value. <#if factoryMethod??> <@includeModel object=factoryMethod targetType=ext.targetType/> <#else> - <@constructTargetObject/> + <@constructTargetObject targetType=ext.targetType/> <#-- @@ -164,15 +166,18 @@ Performs a default assignment with a default value. purpose: Either call the constructor of the target object directly or of the implementing type. --> -<#macro constructTargetObject><@compress single_line=true> - <#if ext.targetType.implementationType??> - new <@includeModel object=ext.targetType.implementationType/>() - <#elseif ext.targetType.arrayType> - new <@includeModel object=ext.targetType.componentType/>[0] - <#elseif ext.targetType.sensibleDefault??> - ${ext.targetType.sensibleDefault} +<#-- @ftlvariable name="targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#macro constructTargetObject targetType><@compress single_line=true> + <#if targetType.implementationType??> + new <@includeModel object=targetType.implementationType/>() + <#elseif targetType.arrayType> + new <@includeModel object=targetType.componentType/>[0] + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.optionalType> + <@includeModel object=targetType.asRawType()/>.of( <@constructTargetObject targetType=targetType.optionalBaseType/> ) <#else> - new <@includeModel object=ext.targetType/>() + new <@includeModel object=targetType/>() <#-- @@ -181,6 +186,7 @@ Performs a default assignment with a default value. purpose: assignment for source local variables. The sourceLocalVarName replaces the sourceReference in the assignmentcall. --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Assignment" --> <#macro sourceLocalVarAssignment> <#if sourceLocalVarName??> <@includeModel object=sourceType/> ${sourceLocalVarName} = ${sourceReference}; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl new file mode 100644 index 0000000000..c321ce20ec --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl @@ -0,0 +1,19 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck" --> +<@compress single_line=true> + <#if isNegate()> + <#if versionInformation.isSourceVersionAtLeast11()> + ${sourceReference}.isEmpty() + <#else> + !${sourceReference}.isPresent() + + <#else> + ${sourceReference}.isPresent() + + \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java new file mode 100644 index 0000000000..e1e557a897 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java @@ -0,0 +1,73 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion._enum; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@WithClasses({ + OptionalEnumToIntegerSource.class, + EnumToIntegerTarget.class, + OptionalEnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class OptionalEnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + OptionalEnumToIntegerSource source = new OptionalEnumToIntegerSource(); + + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { + source.setEnumValue( Optional.of( value ) ); + + EnumToIntegerTarget target = OptionalEnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( value.ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerEnum[] enumValues = EnumToIntegerEnum.values(); + int numberOfValues = enumValues.length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).contains( enumValues[target.getEnumValue()] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEmpty(); + assertThat( source.getInvalidEnumValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java new file mode 100644 index 0000000000..d07d9b879c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalEnumToIntegerMapper { + OptionalEnumToIntegerMapper INSTANCE = Mappers.getMapper( OptionalEnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(OptionalEnumToIntegerSource source); + + OptionalEnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java new file mode 100644 index 0000000000..700c3198bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java @@ -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.ap.test.conversion._enum; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalEnumToIntegerSource { + private Optional enumValue = Optional.empty(); + + private Optional invalidEnumValue = Optional.empty(); + + public Optional getEnumValue() { + return enumValue; + } + + public void setEnumValue(Optional enumValue) { + this.enumValue = enumValue; + } + + public Optional getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Optional invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java new file mode 100644 index 0000000000..986de6a0ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigDecimalOptionalMapper { + + BigDecimalOptionalMapper INSTANCE = Mappers.getMapper( BigDecimalOptionalMapper.class ); + + BigDecimalTarget sourceToTarget(BigDecimalOptionalSource source); + + BigDecimalOptionalSource targetToSource(BigDecimalTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java new file mode 100644 index 0000000000..bf4e53e5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java @@ -0,0 +1,139 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import java.math.BigDecimal; +import java.util.Optional; + +public class BigDecimalOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + private Optional bigInteger; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } + + public Optional getBigInteger() { + return bigInteger; + } + + public void setBigInteger(Optional bigInteger) { + this.bigInteger = bigInteger; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java new file mode 100644 index 0000000000..d39b48c7e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigIntegerOptionalMapper { + + BigIntegerOptionalMapper INSTANCE = Mappers.getMapper( BigIntegerOptionalMapper.class ); + + BigIntegerTarget sourceToTarget(BigIntegerOptionalSource source); + + BigIntegerOptionalSource targetToSource(BigIntegerTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java new file mode 100644 index 0000000000..f4bb0f03c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java @@ -0,0 +1,131 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.bignumbers; + +import java.math.BigInteger; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class BigIntegerOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java index 69f87f3980..2ddddec02e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Optional; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; @@ -100,6 +101,79 @@ public void shouldApplyReverseBigIntegerConversions() { assertThat( source.getString() ).isEqualTo( new BigInteger( "13" ) ); } + @ProcessorTest + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyOptionalBigIntegerConversions() { + BigIntegerOptionalSource source = new BigIntegerOptionalSource(); + source.setB( Optional.of( new BigInteger( "1" ) ) ); + source.setBb( Optional.of( new BigInteger( "2" ) ) ); + source.setS( Optional.of( new BigInteger( "3" ) ) ); + source.setSs( Optional.of( new BigInteger( "4" ) ) ); + source.setI( Optional.of( new BigInteger( "5" ) ) ); + source.setIi( Optional.of( new BigInteger( "6" ) ) ); + source.setL( Optional.of( new BigInteger( "7" ) ) ); + source.setLl( Optional.of( new BigInteger( "8" ) ) ); + source.setF( Optional.of( new BigInteger( "9" ) ) ); + source.setFf( Optional.of( new BigInteger( "10" ) ) ); + source.setD( Optional.of( new BigInteger( "11" ) ) ); + source.setDd( Optional.of( new BigInteger( "12" ) ) ); + source.setString( Optional.of( new BigInteger( "13" ) ) ); + + BigIntegerTarget target = BigIntegerOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.0f ); + assertThat( target.getFf() ).isEqualTo( 10.0f ); + assertThat( target.getD() ).isEqualTo( 11.0d ); + assertThat( target.getDd() ).isEqualTo( 12.0d ); + assertThat( target.getString() ).isEqualTo( "13" ); + } + + @ProcessorTest + @IssueKey("21") + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyReverseOptionalBigIntegerConversions() { + BigIntegerTarget target = new BigIntegerTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13" ); + + BigIntegerOptionalSource source = BigIntegerOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigInteger( "1" ) ); + assertThat( source.getBb() ).contains( new BigInteger( "2" ) ); + assertThat( source.getS() ).contains( new BigInteger( "3" ) ); + assertThat( source.getSs() ).contains( new BigInteger( "4" ) ); + assertThat( source.getI() ).contains( new BigInteger( "5" ) ); + assertThat( source.getIi() ).contains( new BigInteger( "6" ) ); + assertThat( source.getL() ).contains( new BigInteger( "7" ) ); + assertThat( source.getLl() ).contains( new BigInteger( "8" ) ); + assertThat( source.getF() ).contains( new BigInteger( "9" ) ); + assertThat( source.getFf() ).contains( new BigInteger( "10" ) ); + assertThat( source.getD() ).contains( new BigInteger( "11" ) ); + assertThat( source.getDd() ).contains( new BigInteger( "12" ) ); + assertThat( source.getString() ).contains( new BigInteger( "13" ) ); + } + @ProcessorTest @IssueKey("21") @WithClasses({ BigDecimalSource.class, BigDecimalTarget.class, BigDecimalMapper.class }) @@ -178,6 +252,82 @@ public void shouldApplyReverseBigDecimalConversions() { assertThat( source.getBigInteger() ).isEqualTo( new BigDecimal( "14" ) ); } + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyOptionalBigDecimalConversions() { + BigDecimalOptionalSource source = new BigDecimalOptionalSource(); + source.setB( Optional.of( new BigDecimal( "1.45" ) ) ); + source.setBb( Optional.of( new BigDecimal( "2.45" ) ) ); + source.setS( Optional.of( new BigDecimal( "3.45" ) ) ); + source.setSs( Optional.of( new BigDecimal( "4.45" ) ) ); + source.setI( Optional.of( new BigDecimal( "5.45" ) ) ); + source.setIi( Optional.of( new BigDecimal( "6.45" ) ) ); + source.setL( Optional.of( new BigDecimal( "7.45" ) ) ); + source.setLl( Optional.of( new BigDecimal( "8.45" ) ) ); + source.setF( Optional.of( new BigDecimal( "9.45" ) ) ); + source.setFf( Optional.of( new BigDecimal( "10.45" ) ) ); + source.setD( Optional.of( new BigDecimal( "11.45" ) ) ); + source.setDd( Optional.of( new BigDecimal( "12.45" ) ) ); + source.setString( Optional.of( new BigDecimal( "13.45" ) ) ); + source.setBigInteger( Optional.of( new BigDecimal( "14.45" ) ) ); + + BigDecimalTarget target = BigDecimalOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.45f ); + assertThat( target.getFf() ).isEqualTo( 10.45f ); + assertThat( target.getD() ).isEqualTo( 11.45d ); + assertThat( target.getDd() ).isEqualTo( 12.45d ); + assertThat( target.getString() ).isEqualTo( "13.45" ); + assertThat( target.getBigInteger() ).isEqualTo( new BigInteger( "14" ) ); + } + + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyReverseOptionalBigDecimalConversions() { + BigDecimalTarget target = new BigDecimalTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13.45" ); + target.setBigInteger( new BigInteger( "14" ) ); + + BigDecimalOptionalSource source = BigDecimalOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigDecimal( "1" ) ); + assertThat( source.getBb() ).contains( new BigDecimal( "2" ) ); + assertThat( source.getS() ).contains( new BigDecimal( "3" ) ); + assertThat( source.getSs() ).contains( new BigDecimal( "4" ) ); + assertThat( source.getI() ).contains( new BigDecimal( "5" ) ); + assertThat( source.getIi() ).contains( new BigDecimal( "6" ) ); + assertThat( source.getL() ).contains( new BigDecimal( "7" ) ); + assertThat( source.getLl() ).contains( new BigDecimal( "8" ) ); + assertThat( source.getF() ).contains( new BigDecimal( "9.0" ) ); + assertThat( source.getFf() ).contains( new BigDecimal( "10.0" ) ); + assertThat( source.getD() ).contains( new BigDecimal( "11.0" ) ); + assertThat( source.getDd() ).contains( new BigDecimal( "12.0" ) ); + assertThat( source.getString() ).contains( new BigDecimal( "13.45" ) ); + assertThat( source.getBigInteger() ).contains( new BigDecimal( "14" ) ); + } + @ProcessorTest @IssueKey("1009") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java new file mode 100644 index 0000000000..1a9bb50b67 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java @@ -0,0 +1,466 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Optional; +import java.util.TimeZone; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for conversions to/from Java 8 date and time types wrapped in {@link Optional}. + */ +@WithClasses({ OptionalSource.class, Target.class, OptionalSourceTargetMapper.class }) +public class Java8OptionalTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalSourceTargetMapper.class ); + } + + @ProcessorTest + public void testDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + } + + @ProcessorTest + public void testLocalDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + } + + @ProcessorTest + public void testLocalDateToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + } + + @ProcessorTest + public void testLocalTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testSourceToTargetMappingForStrings() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + + // with given format + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + + // and now with default mappings + target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testStringToDateTime() { + String dateTimeAsString = "01.01.2014 00:00 UTC"; + Target target = new Target(); + target.setZonedDateTime( dateTimeAsString ); + ZonedDateTime sourceDateTime = + ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDateTime() { + String dateTimeAsString = "01.01.2014 00:00"; + Target target = new Target(); + target.setLocalDateTime( dateTimeAsString ); + LocalDateTime sourceDateTime = + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDate() { + String dateTimeAsString = "01.01.2014"; + Target target = new Target(); + target.setLocalDate( dateTimeAsString ); + LocalDate sourceDate = + LocalDate.of( 2014, 1, 1 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDate() ).contains( sourceDate ); + } + + @ProcessorTest + public void testStringToLocalTime() { + String dateTimeAsString = "00:00"; + Target target = new Target(); + target.setLocalTime( dateTimeAsString ); + LocalTime sourceTime = + LocalTime.of( 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalTime() ).contains( sourceTime ); + } + + @ProcessorTest + public void testTargetToSourceNullMapping() { + Target target = new Target(); + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).isEmpty(); + assertThat( src.getLocalDate() ).isEmpty(); + assertThat( src.getLocalDateTime() ).isEmpty(); + assertThat( src.getLocalTime() ).isEmpty(); + } + + @ProcessorTest + public void testTargetToSourceMappingForStrings() { + Target target = new Target(); + + target.setZonedDateTime( "01.01.2014 00:00 UTC" ); + target.setLocalDateTime( "01.01.2014 00:00" ); + target.setLocalDate( "01.01.2014" ); + target.setLocalTime( "00:00" ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src.getZonedDateTime() ).contains( + ZonedDateTime.of( + LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) + ) ); + assertThat( src.getLocalDateTime() ).contains( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + assertThat( src.getLocalDate() ).contains( LocalDate.of( 2014, 1, 1 ) ); + assertThat( src.getLocalTime() ).contains( LocalTime.of( 0, 0 ) ); + } + + @ProcessorTest + public void testCalendarMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForCalendarConversion( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target.getForCalendarConversion() ).isNotNull(); + assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( + TimeZone.getTimeZone( + "UTC" ) ); + assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( + dateTime.getMonthValue() - 1 ); + assertThat( target.getForCalendarConversion().get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( target.getForCalendarConversion().get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( target.getForCalendarConversion().get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForCalendarConversion() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("UTC") + public void testZonedDateTimeToDateMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForDateConversionWithZonedDateTime( Optional.of( dateTime ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithZonedDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithZonedDateTime() ).contains( dateTime ); + } + + @ProcessorTest + public void testInstantToDateMapping() { + Instant instant = Instant.ofEpochMilli( 1539366615000L ); + + OptionalSource source = new OptionalSource(); + source.setForDateConversionWithInstant( Optional.ofNullable( instant ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + Date date = target.getForDateConversionWithInstant(); + assertThat( date ).isNotNull(); + assertThat( date.getTime() ).isEqualTo( 1539366615000L ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDateConversionWithInstant() ).contains( instant ); + } + + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + OptionalSource source = new OptionalSource(); + source.setForLocalDateTimeConversionWithLocalDate( Optional.of( localDate ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateTimeToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); + source.setForDateConversionWithLocalDateTime( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDateTime() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToSqlDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForSqlDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForSqlDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForSqlDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForSqlDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + public void testInstantToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.ofNullable( Instant.ofEpochSecond( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); + } + + @ProcessorTest + public void testInstantToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).contains( Instant.EPOCH ); + } + + @ProcessorTest + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testPeriodToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.ofNullable( Period.ofDays( 42 ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @ProcessorTest + public void testPeriodToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).contains( Period.of( 1, 2, 3 ) ); + } + + @ProcessorTest + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testDurationToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.ofNullable( Duration.ofMinutes( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @ProcessorTest + public void testDurationToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @ProcessorTest + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).contains( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @ProcessorTest + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java new file mode 100644 index 0000000000..cf94f31869 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java @@ -0,0 +1,160 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalSource { + + private Optional zonedDateTime = Optional.empty(); + + private Optional localDateTime = Optional.empty(); + + private Optional localDate = Optional.empty(); + + private Optional localTime = Optional.empty(); + + private Optional forCalendarConversion = Optional.empty(); + + private Optional forDateConversionWithZonedDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDate = Optional.empty(); + + private Optional forSqlDateConversionWithLocalDate = Optional.empty(); + + private Optional forDateConversionWithInstant = Optional.empty(); + + private Optional forLocalDateTimeConversionWithLocalDate = Optional.empty(); + + private Optional forInstantConversionWithString = Optional.empty(); + + private Optional forPeriodConversionWithString = Optional.empty(); + + private Optional forDurationConversionWithString = Optional.empty(); + + public Optional getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(Optional dateTime) { + this.zonedDateTime = dateTime; + } + + public Optional getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(Optional localDateTime) { + this.localDateTime = localDateTime; + } + + public Optional getLocalDate() { + return localDate; + } + + public void setLocalDate(Optional localDate) { + this.localDate = localDate; + } + + public Optional getLocalTime() { + return localTime; + } + + public void setLocalTime(Optional localTime) { + this.localTime = localTime; + } + + public Optional getForCalendarConversion() { + return forCalendarConversion; + } + + public void setForCalendarConversion(Optional forCalendarConversion) { + this.forCalendarConversion = forCalendarConversion; + } + + public Optional getForDateConversionWithZonedDateTime() { + return forDateConversionWithZonedDateTime; + } + + public void setForDateConversionWithZonedDateTime(Optional forDateConversionWithZonedDateTime) { + this.forDateConversionWithZonedDateTime = forDateConversionWithZonedDateTime; + } + + public Optional getForDateConversionWithLocalDateTime() { + return forDateConversionWithLocalDateTime; + } + + public void setForDateConversionWithLocalDateTime(Optional forDateConversionWithLocalDateTime) { + this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; + } + + public Optional getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(Optional forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public Optional getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(Optional forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Optional getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Optional forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public Optional getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate( + Optional forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public Optional getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Optional forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Optional getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Optional forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Optional getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Optional forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java new file mode 100644 index 0000000000..a266309b61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java @@ -0,0 +1,70 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalSourceTargetMapper { + + String DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm z"; + + String LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm"; + + String LOCAL_DATE_FORMAT = "dd.MM.yyyy"; + + String LOCAL_TIME_FORMAT = "HH:mm"; + + OptionalSourceTargetMapper INSTANCE = Mappers.getMapper( OptionalSourceTargetMapper.class ); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTarget(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetDefaultMapping(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + Target sourceToTargetDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + Target sourceToTargetLocalDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + Target sourceToTargetLocalDateMapped(OptionalSource source); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetLocalTimeMapped(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSource(Target target); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + OptionalSource targetToSourceDateTimeMapped(Target target); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + OptionalSource targetToSourceLocalDateTimeMapped(Target target); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + OptionalSource targetToSourceLocalDateMapped(Target target); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSourceLocalTimeMapped(Target target); + + @InheritInverseConfiguration(name = "sourceToTarget") + OptionalSource targetToSourceDefaultMapping(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java new file mode 100644 index 0000000000..d83b368bce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper1 { + + ErroneousKitchenDrawerOptionalMapper1 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper1.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfForks", source = "numberOfForks" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java new file mode 100644 index 0000000000..7d1824a5b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR, uses = VerySpecialNumberMapper.class ) +public interface ErroneousKitchenDrawerOptionalMapper3 { + + ErroneousKitchenDrawerOptionalMapper3 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper3.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfSpoons", source = "numberOfSpoons" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java new file mode 100644 index 0000000000..edd33c71d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper4 { + + ErroneousKitchenDrawerOptionalMapper4 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper4.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "depth", source = "depth" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java new file mode 100644 index 0000000000..bad921a7c2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper5 { + + ErroneousKitchenDrawerOptionalMapper5 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper5.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "length", source = "length" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..acd2e0876f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java new file mode 100644 index 0000000000..92862d3320 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface KitchenDrawerOptionalMapper2 { + + KitchenDrawerOptionalMapper2 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper2.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfKnifes", source = "numberOfKnifes" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..2b3e982584 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java @@ -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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN, uses = VerySpecialNumberMapper.class ) +public interface KitchenDrawerOptionalMapper6 { + + KitchenDrawerOptionalMapper6 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper6.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "height", source = "height" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index 629f727273..604ff9bffa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -22,6 +22,7 @@ */ @WithClasses({ OversizedKitchenDrawerDto.class, + OversizedKitchenDrawerOptionalDto.class, RegularKitchenDrawerEntity.class, VerySpecialNumber.class, VerySpecialNumberMapper.class, @@ -63,6 +64,19 @@ public void testNoErrorCase() { public void testConversionFromLongToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper1.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"OptionalLong numberOfForks\". It has a possibly lossy conversion from " + + "OptionalLong to int.") + }) + public void testConversionFromOptionalLongToInt() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper2.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -76,6 +90,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper2.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper2.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"Optional numberOfKnifes\" has a possibly lossy conversion " + + "from Optional to Integer.") + }) + public void testConversionFromOptionalBigIntegerToInteger() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -89,6 +116,19 @@ public void testConversionFromBigIntegerToInteger() { public void testConversionFromStringToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"Optional drawerId\". It has a possibly lossy conversion from " + + "Optional to int.") + }) + public void testConversionFromOptionalStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -102,6 +142,19 @@ public void testConversionFromStringToInt() { public void test2StepConversionFromBigIntegerToLong() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper3.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Optional numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") + }) + public void test2StepConversionFromOptionalBigIntegerToLong() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper4.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -115,6 +168,20 @@ public void test2StepConversionFromBigIntegerToLong() { public void testConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper4.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional depth\". " + + "It has a possibly lossy conversion from Optional to float.") + }) + public void testConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper5.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -128,6 +195,20 @@ public void testConversionFromDoubleToFloat() { public void testConversionFromBigDecimalToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper5.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional length\". " + + "It has a possibly lossy conversion from Optional to Float.") + }) + public void testConversionFromOptionalBigDecimalToFloat() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -140,6 +221,19 @@ public void testConversionFromBigDecimalToFloat() { public void test2StepConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"OptionalDouble height\" has a possibly lossy conversion from " + + "OptionalDouble to float.") + }) + public void test2StepConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ListMapper.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java new file mode 100644 index 0000000000..f214fecd1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java @@ -0,0 +1,84 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalLong; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OversizedKitchenDrawerOptionalDto { + + /* yes, its a big drawer */ + private OptionalLong numberOfForks; + private Optional numberOfKnifes; + private Optional numberOfSpoons; + private Optional depth; + private Optional length; + private OptionalDouble height; + private Optional drawerId; + + public OptionalLong getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(OptionalLong numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public Optional getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(Optional numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Optional getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(Optional numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public Optional getDepth() { + return depth; + } + + public void setDepth(Optional depth) { + this.depth = depth; + } + + public Optional getLength() { + return length; + } + + public void setLength(Optional length) { + this.length = length; + } + + public OptionalDouble getHeight() { + return height; + } + + public void setHeight(OptionalDouble height) { + this.height = height; + } + + public Optional getDrawerId() { + return drawerId; + } + + public void setDrawerId(Optional drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java index d49a263deb..00b543c728 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.conversion.lossy; import java.math.BigInteger; +import java.util.Optional; /** * @author Sjaak Derksen @@ -19,4 +20,9 @@ VerySpecialNumber fromFloat(float f) { BigInteger toBigInteger(VerySpecialNumber v) { return new BigInteger( "10" ); } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + BigInteger toBigInteger(Optional v) { + return new BigInteger( "10" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java new file mode 100644 index 0000000000..a92b029b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ByteWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java new file mode 100644 index 0000000000..e469c44390 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalDouble; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleOptionalSource { + + private OptionalDouble b = OptionalDouble.empty(); + private OptionalDouble bb = OptionalDouble.empty(); + private OptionalDouble s = OptionalDouble.empty(); + private OptionalDouble ss = OptionalDouble.empty(); + private OptionalDouble i = OptionalDouble.empty(); + private OptionalDouble ii = OptionalDouble.empty(); + private OptionalDouble l = OptionalDouble.empty(); + private OptionalDouble ll = OptionalDouble.empty(); + private OptionalDouble f = OptionalDouble.empty(); + private OptionalDouble ff = OptionalDouble.empty(); + private OptionalDouble d = OptionalDouble.empty(); + private OptionalDouble dd = OptionalDouble.empty(); + + public OptionalDouble getB() { + return b; + } + + public void setB(OptionalDouble b) { + this.b = b; + } + + public OptionalDouble getBb() { + return bb; + } + + public void setBb(OptionalDouble bb) { + this.bb = bb; + } + + public OptionalDouble getS() { + return s; + } + + public void setS(OptionalDouble s) { + this.s = s; + } + + public OptionalDouble getSs() { + return ss; + } + + public void setSs(OptionalDouble ss) { + this.ss = ss; + } + + public OptionalDouble getI() { + return i; + } + + public void setI(OptionalDouble i) { + this.i = i; + } + + public OptionalDouble getIi() { + return ii; + } + + public void setIi(OptionalDouble ii) { + this.ii = ii; + } + + public OptionalDouble getL() { + return l; + } + + public void setL(OptionalDouble l) { + this.l = l; + } + + public OptionalDouble getLl() { + return ll; + } + + public void setLl(OptionalDouble ll) { + this.ll = ll; + } + + public OptionalDouble getF() { + return f; + } + + public void setF(OptionalDouble f) { + this.f = f; + } + + public OptionalDouble getFf() { + return ff; + } + + public void setFf(OptionalDouble ff) { + this.ff = ff; + } + + public OptionalDouble getD() { + return d; + } + + public void setD(OptionalDouble d) { + this.d = d; + } + + public OptionalDouble getDd() { + return dd; + } + + public void setDd(OptionalDouble dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java new file mode 100644 index 0000000000..ecf0d9bc41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java new file mode 100644 index 0000000000..f20e6fa5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class FloatWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java new file mode 100644 index 0000000000..6543b7bc0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalInt; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntOptionalSource { + + private OptionalInt b = OptionalInt.empty(); + private OptionalInt bb = OptionalInt.empty(); + private OptionalInt s = OptionalInt.empty(); + private OptionalInt ss = OptionalInt.empty(); + private OptionalInt i = OptionalInt.empty(); + private OptionalInt ii = OptionalInt.empty(); + private OptionalInt l = OptionalInt.empty(); + private OptionalInt ll = OptionalInt.empty(); + private OptionalInt f = OptionalInt.empty(); + private OptionalInt ff = OptionalInt.empty(); + private OptionalInt d = OptionalInt.empty(); + private OptionalInt dd = OptionalInt.empty(); + + public OptionalInt getB() { + return b; + } + + public void setB(OptionalInt b) { + this.b = b; + } + + public OptionalInt getBb() { + return bb; + } + + public void setBb(OptionalInt bb) { + this.bb = bb; + } + + public OptionalInt getS() { + return s; + } + + public void setS(OptionalInt s) { + this.s = s; + } + + public OptionalInt getSs() { + return ss; + } + + public void setSs(OptionalInt ss) { + this.ss = ss; + } + + public OptionalInt getI() { + return i; + } + + public void setI(OptionalInt i) { + this.i = i; + } + + public OptionalInt getIi() { + return ii; + } + + public void setIi(OptionalInt ii) { + this.ii = ii; + } + + public OptionalInt getL() { + return l; + } + + public void setL(OptionalInt l) { + this.l = l; + } + + public OptionalInt getLl() { + return ll; + } + + public void setLl(OptionalInt ll) { + this.ll = ll; + } + + public OptionalInt getF() { + return f; + } + + public void setF(OptionalInt f) { + this.f = f; + } + + public OptionalInt getFf() { + return ff; + } + + public void setFf(OptionalInt ff) { + this.ff = ff; + } + + public OptionalInt getD() { + return d; + } + + public void setD(OptionalInt d) { + this.d = d; + } + + public OptionalInt getDd() { + return dd; + } + + public void setDd(OptionalInt dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java new file mode 100644 index 0000000000..b1243d9c1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java new file mode 100644 index 0000000000..1df7baaec9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongOptionalSource { + + private OptionalLong b = OptionalLong.empty(); + private OptionalLong bb = OptionalLong.empty(); + private OptionalLong s = OptionalLong.empty(); + private OptionalLong ss = OptionalLong.empty(); + private OptionalLong i = OptionalLong.empty(); + private OptionalLong ii = OptionalLong.empty(); + private OptionalLong l = OptionalLong.empty(); + private OptionalLong ll = OptionalLong.empty(); + private OptionalLong f = OptionalLong.empty(); + private OptionalLong ff = OptionalLong.empty(); + private OptionalLong d = OptionalLong.empty(); + private OptionalLong dd = OptionalLong.empty(); + + public OptionalLong getB() { + return b; + } + + public void setB(OptionalLong b) { + this.b = b; + } + + public OptionalLong getBb() { + return bb; + } + + public void setBb(OptionalLong bb) { + this.bb = bb; + } + + public OptionalLong getS() { + return s; + } + + public void setS(OptionalLong s) { + this.s = s; + } + + public OptionalLong getSs() { + return ss; + } + + public void setSs(OptionalLong ss) { + this.ss = ss; + } + + public OptionalLong getI() { + return i; + } + + public void setI(OptionalLong i) { + this.i = i; + } + + public OptionalLong getIi() { + return ii; + } + + public void setIi(OptionalLong ii) { + this.ii = ii; + } + + public OptionalLong getL() { + return l; + } + + public void setL(OptionalLong l) { + this.l = l; + } + + public OptionalLong getLl() { + return ll; + } + + public void setLl(OptionalLong ll) { + this.ll = ll; + } + + public OptionalLong getF() { + return f; + } + + public void setF(OptionalLong f) { + this.f = f; + } + + public OptionalLong getFf() { + return ff; + } + + public void setFf(OptionalLong ff) { + this.ff = ff; + } + + public OptionalLong getD() { + return d; + } + + public void setD(OptionalLong d) { + this.d = d; + } + + public OptionalLong getDd() { + return dd; + } + + public void setDd(OptionalLong dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java new file mode 100644 index 0000000000..bed8ab6b59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java new file mode 100644 index 0000000000..0857b20473 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java @@ -0,0 +1,357 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ByteTarget.class, + ByteWrapperOptionalSource.class, + ByteWrapperTarget.class, + ShortTarget.class, + ShortWrapperOptionalSource.class, + ShortWrapperTarget.class, + IntOptionalSource.class, + IntTarget.class, + IntWrapperOptionalSource.class, + IntWrapperTarget.class, + LongOptionalSource.class, + LongTarget.class, + LongWrapperOptionalSource.class, + LongWrapperTarget.class, + FloatWrapperOptionalSource.class, + FloatWrapperTarget.class, + DoubleOptionalSource.class, + DoubleTarget.class, + DoubleWrapperOptionalSource.class, + DoubleWrapperTarget.class, + OptionalNumberConversionMapper.class +}) +public class NumberOptionalConversionTest { + + @ProcessorTest + public void shouldApplyByteWrapperConversions() { + ByteWrapperOptionalSource source = new ByteWrapperOptionalSource(); + source.setB( Optional.of( (byte) 1 ) ); + source.setBb( Optional.of( (byte) 2 ) ); + source.setS( Optional.of( (byte) 3 ) ); + source.setSs( Optional.of( (byte) 4 ) ); + source.setI( Optional.of( (byte) 5 ) ); + source.setIi( Optional.of( (byte) 6 ) ); + source.setL( Optional.of( (byte) 7 ) ); + source.setLl( Optional.of( (byte) 8 ) ); + source.setF( Optional.of( (byte) 9 ) ); + source.setFf( Optional.of( (byte) 10 ) ); + source.setD( Optional.of( (byte) 11 ) ); + source.setDd( Optional.of( (byte) 12 ) ); + + ByteWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyShortWrapperConversions() { + ShortWrapperOptionalSource source = new ShortWrapperOptionalSource(); + source.setB( Optional.of( (short) 1 ) ); + source.setBb( Optional.of( (short) 2 ) ); + source.setS( Optional.of( (short) 3 ) ); + source.setSs( Optional.of( (short) 4 ) ); + source.setI( Optional.of( (short) 5 ) ); + source.setIi( Optional.of( (short) 6 ) ); + source.setL( Optional.of( (short) 7 ) ); + source.setLl( Optional.of( (short) 8 ) ); + source.setF( Optional.of( (short) 9 ) ); + source.setFf( Optional.of( (short) 10 ) ); + source.setD( Optional.of( (short) 11 ) ); + source.setDd( Optional.of( (short) 12 ) ); + + ShortWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntConversions() { + IntOptionalSource source = new IntOptionalSource(); + source.setB( OptionalInt.of( 1 ) ); + source.setBb( OptionalInt.of( 2 ) ); + source.setS( OptionalInt.of( 3 ) ); + source.setSs( OptionalInt.of( 4 ) ); + source.setI( OptionalInt.of( 5 ) ); + source.setIi( OptionalInt.of( 6 ) ); + source.setL( OptionalInt.of( 7 ) ); + source.setLl( OptionalInt.of( 8 ) ); + source.setF( OptionalInt.of( 9 ) ); + source.setFf( OptionalInt.of( 10 ) ); + source.setD( OptionalInt.of( 11 ) ); + source.setDd( OptionalInt.of( 12 ) ); + + IntTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntWrapperConversions() { + IntWrapperOptionalSource source = new IntWrapperOptionalSource(); + source.setB( Optional.of( 1 ) ); + source.setBb( Optional.of( 2 ) ); + source.setS( Optional.of( 3 ) ); + source.setSs( Optional.of( 4 ) ); + source.setI( Optional.of( 5 ) ); + source.setIi( Optional.of( 6 ) ); + source.setL( Optional.of( 7 ) ); + source.setLl( Optional.of( 8 ) ); + source.setF( Optional.of( 9 ) ); + source.setFf( Optional.of( 10 ) ); + source.setD( Optional.of( 11 ) ); + source.setDd( Optional.of( 12 ) ); + + IntWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongConversions() { + LongOptionalSource source = new LongOptionalSource(); + source.setB( OptionalLong.of( 1 ) ); + source.setBb( OptionalLong.of( 2 ) ); + source.setS( OptionalLong.of( 3 ) ); + source.setSs( OptionalLong.of( 4 ) ); + source.setI( OptionalLong.of( 5 ) ); + source.setIi( OptionalLong.of( 6 ) ); + source.setL( OptionalLong.of( 7 ) ); + source.setLl( OptionalLong.of( 8 ) ); + source.setF( OptionalLong.of( 9 ) ); + source.setFf( OptionalLong.of( 10 ) ); + source.setD( OptionalLong.of( 11 ) ); + source.setDd( OptionalLong.of( 12 ) ); + + LongTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongWrapperConversions() { + LongWrapperOptionalSource source = new LongWrapperOptionalSource(); + source.setB( Optional.of( (long) 1 ) ); + source.setBb( Optional.of( (long) 2 ) ); + source.setS( Optional.of( (long) 3 ) ); + source.setSs( Optional.of( (long) 4 ) ); + source.setI( Optional.of( (long) 5 ) ); + source.setIi( Optional.of( (long) 6 ) ); + source.setL( Optional.of( (long) 7 ) ); + source.setLl( Optional.of( (long) 8 ) ); + source.setF( Optional.of( (long) 9 ) ); + source.setFf( Optional.of( (long) 10 ) ); + source.setD( Optional.of( (long) 11 ) ); + source.setDd( Optional.of( (long) 12 ) ); + + LongWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyFloatWrapperConversions() { + FloatWrapperOptionalSource source = new FloatWrapperOptionalSource(); + source.setB( Optional.of( 1f ) ); + source.setBb( Optional.of( 2f ) ); + source.setS( Optional.of( 3f ) ); + source.setSs( Optional.of( 4f ) ); + source.setI( Optional.of( 5f ) ); + source.setIi( Optional.of( 6f ) ); + source.setL( Optional.of( 7f ) ); + source.setLl( Optional.of( 8f ) ); + source.setF( Optional.of( 9f ) ); + source.setFf( Optional.of( 10f ) ); + source.setD( Optional.of( 11f ) ); + source.setDd( Optional.of( 12f ) ); + + FloatWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleConversions() { + DoubleOptionalSource source = new DoubleOptionalSource(); + source.setB( OptionalDouble.of( 1 ) ); + source.setBb( OptionalDouble.of( 2 ) ); + source.setS( OptionalDouble.of( 3 ) ); + source.setSs( OptionalDouble.of( 4 ) ); + source.setI( OptionalDouble.of( 5 ) ); + source.setIi( OptionalDouble.of( 6 ) ); + source.setL( OptionalDouble.of( 7 ) ); + source.setLl( OptionalDouble.of( 8 ) ); + source.setF( OptionalDouble.of( 9 ) ); + source.setFf( OptionalDouble.of( 10 ) ); + source.setD( OptionalDouble.of( 11 ) ); + source.setDd( OptionalDouble.of( 12 ) ); + + DoubleTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleWrapperConversions() { + DoubleWrapperOptionalSource source = new DoubleWrapperOptionalSource(); + source.setB( Optional.of( 1d ) ); + source.setBb( Optional.of( 2d ) ); + source.setS( Optional.of( 3d ) ); + source.setSs( Optional.of( 4d ) ); + source.setI( Optional.of( 5d ) ); + source.setIi( Optional.of( 6d ) ); + source.setL( Optional.of( 7d ) ); + source.setLl( Optional.of( 8d ) ); + source.setF( Optional.of( 9d ) ); + source.setFf( Optional.of( 10d ) ); + source.setD( Optional.of( 11d ) ); + source.setDd( Optional.of( 12d ) ); + + DoubleWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + @IssueKey("229") + public void wrapperToPrimitiveIsNullSafe() { + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ByteWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new DoubleWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ShortWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new IntWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new FloatWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new LongWrapperOptionalSource() ) ) + .isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java new file mode 100644 index 0000000000..94472586fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalNumberConversionMapper { + + OptionalNumberConversionMapper INSTANCE = Mappers.getMapper( OptionalNumberConversionMapper.class ); + + ByteWrapperTarget sourceToTarget(ByteWrapperOptionalSource source); + + ByteWrapperOptionalSource targetToSource(ByteWrapperTarget target); + + ShortWrapperTarget sourceToTarget(ShortWrapperOptionalSource source); + + ShortWrapperOptionalSource targetToSource(ShortWrapperTarget target); + + IntTarget sourceToTarget(IntOptionalSource source); + + IntOptionalSource targetToSource(IntTarget target); + + IntWrapperTarget sourceToTarget(IntWrapperOptionalSource source); + + IntWrapperOptionalSource targetToSource(IntWrapperTarget target); + + LongTarget sourceToTarget(LongOptionalSource source); + + LongOptionalSource targetToSource(LongTarget target); + + LongWrapperTarget sourceToTarget(LongWrapperOptionalSource source); + + LongWrapperOptionalSource targetToSource(LongWrapperTarget target); + + FloatWrapperTarget sourceToTarget(FloatWrapperOptionalSource source); + + FloatWrapperOptionalSource targetToSource(FloatWrapperTarget target); + + DoubleTarget sourceToTarget(DoubleOptionalSource source); + + DoubleOptionalSource targetToSource(DoubleTarget target); + + DoubleWrapperTarget sourceToTarget(DoubleWrapperOptionalSource source); + + DoubleWrapperOptionalSource targetToSource(DoubleWrapperTarget target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java new file mode 100644 index 0000000000..16acf6c69b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ShortWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java new file mode 100644 index 0000000000..30dca2cf39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java @@ -0,0 +1,76 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalBeforeAfterMapper { + + OptionalBeforeAfterMapper INSTANCE = Mappers.getMapper( OptionalBeforeAfterMapper.class ); + + Target toTarget(Source source); + + @BeforeMapping + default void beforeDeepOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeDeepOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterDeepOptionalSourceWithNonOptionalTarget(@MappingTarget Target.SubType target, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithOptionalTarget(@MappingTarget Optional target, + Optional source) { + } + + @AfterMapping + default void afterDeepNonOptionalSourceOptionalTarget(@MappingTarget Optional target, + Source.SubType source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterShallowOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterShallowOptionalSourceWithNonOptionalTarget(@MappingTarget String target, + Optional source) { + } + + @AfterMapping + default void afterShallowNonOptionalSourceOptionalTarget(@MappingTarget Optional target, String source) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java new file mode 100644 index 0000000000..ba1a7b09d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java @@ -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.ap.test.optional.beforeafter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + OptionalBeforeAfterMapper.class, Source.class, Target.class +}) +class OptionalBeforeAfterTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalBeforeAfterMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java new file mode 100644 index 0000000000..1284bca7ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java @@ -0,0 +1,87 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional deepOptionalToOptional; + private final Optional deepOptionalToNonOptional; + private final SubType deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final Optional shallowOptionalToNonOptional; + private final String shallowNonOptionalToOptional; + + public Source(Optional deepOptionalToOptional, Optional deepOptionalToNonOptional, + SubType deepNonOptionalToOptional, Optional shallowOptionalToOptional, + Optional shallowOptionalToNonOptional, String shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public Optional getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public SubType getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public Optional getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public String getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java new file mode 100644 index 0000000000..592e3338fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java @@ -0,0 +1,86 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional deepOptionalToOptional; + private final SubType deepOptionalToNonOptional; + private final Optional deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final String shallowOptionalToNonOptional; + private final Optional shallowNonOptionalToOptional; + + public Target(Optional deepOptionalToOptional, SubType deepOptionalToNonOptional, + Optional deepNonOptionalToOptional, Optional shallowOptionalToOptional, + String shallowOptionalToNonOptional, Optional shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public SubType getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public Optional getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public String getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public Optional getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java new file mode 100644 index 0000000000..9dd4b01256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java @@ -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.ap.test.optional.builder; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class OptionalBuilderTest { + + @ProcessorTest + @WithClasses(SimpleOptionalBuilderMapper.class) + void simpleOptionalBuilder() { + Optional targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( null ); + assertThat( targetOpt ).isEmpty(); + + targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( new SimpleOptionalBuilderMapper.Source( "test" ) ); + assertThat( targetOpt ).isNotEmpty(); + SimpleOptionalBuilderMapper.Target target = targetOpt.get(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java new file mode 100644 index 0000000000..5f24aa0cae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java @@ -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.ap.test.optional.builder; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleOptionalBuilderMapper { + + SimpleOptionalBuilderMapper INSTANCE = Mappers.getMapper( SimpleOptionalBuilderMapper.class ); + + Optional map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + private Target(TargetBuilder builder) { + this.value = builder.value; + } + + public String getValue() { + return value; + } + + public static TargetBuilder builder() { + return new TargetBuilder(); + } + + } + + class TargetBuilder { + + private String value; + + public TargetBuilder value(String value) { + this.value = value; + return this; + } + + public Target build() { + return new Target(this); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java new file mode 100644 index 0000000000..a6f8c155d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java @@ -0,0 +1,63 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.custom; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@SuppressWarnings("OptionalAssignedToNull") +public interface CustomOptionalMapper { + + CustomOptionalMapper INSTANCE = Mappers.getMapper( CustomOptionalMapper.class ); + + Target map(Source source); + + void update(@MappingTarget Target target, Source source); + + @Condition + default boolean isPresent(Optional optional) { + return optional != null; + } + + default E unwrapFromOptional(Optional optional) { + return optional == null + ? null + : optional.orElse( null ); + } + + class Target { + private String value = "initial"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Source { + private final Optional value; + + public Source(Optional value) { + this.value = value; + } + + public Optional getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java new file mode 100644 index 0000000000..51991ac478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java @@ -0,0 +1,60 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.custom; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(CustomOptionalMapper.class) +class CustomOptionalTest { + + @ProcessorTest + void shouldUseCustomMethodsWhenMapping() { + CustomOptionalMapper.Target target = CustomOptionalMapper.INSTANCE. + map( new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + void shouldUseCustomMethodsWhenUpdating() { + CustomOptionalMapper.Target target = new CustomOptionalMapper.Target(); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = new CustomOptionalMapper.Target(); + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java new file mode 100644 index 0000000000..5a276b4745 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalDifferentTypesMapper { + + OptionalDifferentTypesMapper INSTANCE = Mappers.getMapper( OptionalDifferentTypesMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java new file mode 100644 index 0000000000..74ab85e174 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java @@ -0,0 +1,204 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalDifferentTypesMapper.class, Source.class, Target.class +}) +class OptionalDifferentTypesTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalDifferentTypesMapper.class ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( new Source.SubType( "some value" ) ), Optional.empty(), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( Optional.empty(), Optional.of( new Source.SubType( "some value" ) ), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getConstructorOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( Optional.empty(), Optional.empty(), new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void optionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void optionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToOptional( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.publicOptionalToNonOptional; + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = new Source.SubType( "some value" ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java new file mode 100644 index 0000000000..0c0648000a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java @@ -0,0 +1,89 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.empty(); + private Optional optionalToNonOptional = Optional.empty(); + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source() { + this( Optional.empty(), Optional.empty(), null ); + } + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java new file mode 100644 index 0000000000..4f2b21c8a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java @@ -0,0 +1,84 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.of( new SubType( "initial" ) ); + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.of( new SubType( "initial" ) ); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java new file mode 100644 index 0000000000..3624efb00e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java @@ -0,0 +1,108 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeWithoutParameters() { + invokedMethods.add( "beforeWithoutParameters" ); + } + + @BeforeMapping + public void beforeWithOptionalSource(Optional source) { + invokedMethods.add( "beforeWithOptionalSource" ); + } + + @BeforeMapping + public void beforeWithSource(Source source) { + invokedMethods.add( "beforeWithSource" ); + } + + @BeforeMapping + public void beforeWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "beforeWithTargetType" ); + } + + @BeforeMapping + public void beforeWithBuilderTargetType(@TargetType Class targetBuilderClass) { + invokedMethods.add( "beforeWithBuilderTargetType" ); + } + + @BeforeMapping + public void beforeWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "beforeWithOptionalTarget" ); + } + + @BeforeMapping + public void beforeWithTarget(@MappingTarget Target target) { + invokedMethods.add( "beforeWithTarget" ); + } + + @BeforeMapping + public void beforeWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "beforeWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithoutParameters() { + invokedMethods.add( "afterWithoutParameters" ); + } + + @AfterMapping + public void afterWithOptionalSource(Optional source) { + invokedMethods.add( "afterWithOptionalSource" ); + } + + @AfterMapping + public void afterWithSource(Source source) { + invokedMethods.add( "afterWithSource" ); + } + + @AfterMapping + public void afterWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithTargetType" ); + } + + @AfterMapping + public void afterWithBuilderTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithBuilderTargetType" ); + } + + @AfterMapping + public void afterWithTarget(@MappingTarget Target target) { + invokedMethods.add( "afterWithTarget" ); + } + + @AfterMapping + public void afterWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "afterWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "afterWithOptionalTarget" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java new file mode 100644 index 0000000000..c80dfcb6b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java @@ -0,0 +1,165 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + MappingContext.class, + Source.class, + Target.class, +}) +public class OptionalLifecycleTest { + + @ProcessorTest + @WithClasses(OptionalToOptionalMapper.class) + void optionalToOptional() { + MappingContext context = new MappingContext(); + + OptionalToOptionalMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToOptionalWithBuilderMapper.class) + void optionalToOptionalWithBuilder() { + MappingContext context = new MappingContext(); + + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType", + "beforeWithTargetBuilder", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithBuilderTargetType", + "afterWithTarget", + "afterWithTargetBuilder", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMapper.class) + void optionalToType() { + MappingContext context = new MappingContext(); + + OptionalToTypeMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMultiSourceMapper.class) + void optionalToTypeMultiSource() { + MappingContext context = new MappingContext(); + + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.of( new Source() ), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), "Test", context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java new file mode 100644 index 0000000000..d7c5d08173 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalMapper { + + OptionalToOptionalMapper INSTANCE = Mappers.getMapper( OptionalToOptionalMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java new file mode 100644 index 0000000000..8cfab7fd00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java @@ -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.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalWithBuilderMapper { + + OptionalToOptionalWithBuilderMapper INSTANCE = Mappers.getMapper( OptionalToOptionalWithBuilderMapper.class ); + + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java new file mode 100644 index 0000000000..6a6f3e096e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMapper { + + OptionalToTypeMapper INSTANCE = Mappers.getMapper( OptionalToTypeMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java new file mode 100644 index 0000000000..d672f2ec05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMultiSourceMapper { + + OptionalToTypeMultiSourceMapper INSTANCE = Mappers.getMapper( OptionalToTypeMultiSourceMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, String otherValue, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java new file mode 100644 index 0000000000..c7005c6ca6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java new file mode 100644 index 0000000000..d5f68a1844 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static class Builder { + + private String value; + + public Builder value(String value) { + this.value = value; + return this; + } + + public Target build() { + Target target = new Target(); + target.setValue( value ); + return target; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java new file mode 100644 index 0000000000..e6ce6faa65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java @@ -0,0 +1,81 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.nested; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Artist { + private final String name; + private final Label label; + + public Artist(String name, Label label) { + this.name = name; + this.label = label; + } + + public boolean hasName() { + return name != null; + } + + public String getName() { + return name; + } + + public boolean hasLabel() { + return label != null; + } + + public Optional
    *

    @@ -446,13 +445,11 @@ * If not possible, MapStruct will try to apply a user defined mapping method. * * - *

    * *

  • other *

    * MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method, * type conversion method or built-in conversion. - *

    *

  • * *

    diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java index f5463b9026..4d635d8aa3 100644 --- a/core/src/main/java/org/mapstruct/SubclassMapping.java +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java @@ -73,12 +73,17 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Experimental public @interface SubclassMapping { + /** + * The source subclass to check for before using the default mapping as fallback. + * * @return the source subclass to check for before using the default mapping as fallback. */ Class source(); /** + * The target subclass to map the source to. + * * @return the target subclass to map the source to. */ Class target(); diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java index 2ddafaf516..d6aac264d4 100644 --- a/core/src/main/java/org/mapstruct/SubclassMappings.java +++ b/core/src/main/java/org/mapstruct/SubclassMappings.java @@ -51,6 +51,8 @@ public @interface SubclassMappings { /** + * The subclassMappings that should be applied. + * * @return the subclassMappings to apply. */ SubclassMapping[] value(); diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index bb593f53d1..95d4c7d5ce 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -44,6 +44,11 @@ @Retention(RetentionPolicy.CLASS) public @interface ValueMappings { + /** + * The value mappings that should be applied. + * + * @return the value mappings + */ ValueMapping[] value(); } diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java index c44797602a..1cb5bf2bb1 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControl.java +++ b/core/src/main/java/org/mapstruct/control/MappingControl.java @@ -99,8 +99,16 @@ @MappingControl( MappingControl.Use.COMPLEX_MAPPING ) public @interface MappingControl { + /** + * The type of mapping control that should be used. + * + * @return What should be used for the mapping control + */ Use value(); + /** + * Defines the options that can be used for the mapping control. + */ enum Use { /** diff --git a/core/src/main/java/org/mapstruct/control/MappingControls.java b/core/src/main/java/org/mapstruct/control/MappingControls.java index a3efef661f..c1663e551e 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControls.java +++ b/core/src/main/java/org/mapstruct/control/MappingControls.java @@ -21,5 +21,10 @@ @Target(ElementType.ANNOTATION_TYPE) public @interface MappingControls { + /** + * The mapping controls that should be applied to the annotated class. + * + * @return The mapping controls that should be applied to the annotated class. + */ MappingControl[] value(); } diff --git a/core/src/main/java/org/mapstruct/util/Experimental.java b/core/src/main/java/org/mapstruct/util/Experimental.java index a945a87d7c..5fd5eb3b5c 100644 --- a/core/src/main/java/org/mapstruct/util/Experimental.java +++ b/core/src/main/java/org/mapstruct/util/Experimental.java @@ -17,5 +17,11 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface Experimental { + + /** + * The reason why the feature is considered experimental. + * + * @return the reason why the feature is considered experimental. + */ String value() default ""; } diff --git a/processor/pom.xml b/processor/pom.xml index 01b8cbe263..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -172,6 +172,13 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java index 31d4d27585..b392281bf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java @@ -46,6 +46,8 @@ public interface ConversionProvider { Assignment from(ConversionContext conversionContext); /** + * Retrieves any helper methods required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any helper methods when required. @@ -53,6 +55,8 @@ public interface ConversionProvider { List getRequiredHelperMethods(ConversionContext conversionContext); /** + * Retrieves any fields required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any fields when required. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java index 99797f898c..3f32994734 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ConversionUtils.currency; /** + * Conversion between {@link Currency} and {@link String}. + * * @author Darren Rambaud */ public class CurrencyToStringConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java index 2b3e1bd960..db9480662d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -17,6 +17,9 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; /** + * A conversion provider that wraps / unwraps the underlying conversion in Optional. + * e.g., For conversion from {@code Optional} to {@code Integer}. + * * @author Filip Hrisafov */ public class OptionalWrapperConversionProvider implements ConversionProvider { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java index ad488d1a8f..dbab6bc4ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; /** + * Conversion between {@link java.util.Optional Optional} and its base type. + * * @author Filip Hrisafov */ public class TypeToOptionalConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 3b6e3c1686..80b900bb57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -19,6 +19,8 @@ /** * An abstract builder that can be reused for building {@link MappingMethod}(s). * + * @param the builder itself that needs to be used for chaining + * @param the method that the builder builds * @author Filip Hrisafov */ public abstract class AbstractMappingMethodBuilder, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java index 3f0b2123ae..cb9289e057 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -50,6 +50,8 @@ import static javax.lang.model.util.ElementFilter.methodsIn; /** + * Helper class which is responsible for collecting all additional annotations that should be added. + * * @author Ben Zegveld * @since 1.5 */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java index 5614376ea4..8f60b052b9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A method in a generated type that represents a setter with annotations. + * * @author Lucas Resch */ public class AnnotatedSetter extends GeneratedTypeMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java index 9b7912f752..9b0507f399 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate builder finisher method. + * * @author Filip Hrisafov */ public class BuilderFinisherMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java index f8c596a368..e1c320e16b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from an optional source to it's value. + * * @author Filip Hrisafov */ public class FromOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java index f02cc16b2c..8d6a999665 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java @@ -8,6 +8,8 @@ import org.mapstruct.ap.internal.model.common.ModelElement; /** + * Base class for methods available in a generated type. + * * @author Filip Hrisafov */ public abstract class GeneratedTypeMethod extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java index 879a76efbe..29904ff416 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that is based on a {@link MethodReference}. + * * @author Filip Hrisafov */ public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 89f295981f..8b526b9177 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -26,6 +26,7 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate object factory method. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 784342b8d7..2de61089ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -25,6 +25,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Factory for creating {@link PresenceCheck}s. + * * @author Filip Hrisafov */ public final class PresenceCheckMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java index 727518dad8..0d97c1a523 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents a service entry for the service loader file. + * * @author Christophe Labouisse on 14/07/2015. */ public class ServicesEntry extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java index 5925540e10..45bbc9d4b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from a source to an optional of the source. + * * @author Filip Hrisafov */ public class ToOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java index 6ecde886bf..f242b51f47 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents an annotation element. + * * @author Ben Zegveld */ public class AnnotationElement extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java index a334312007..eaa2fe68ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Decorates the assignment as an {@link Enum} constant access. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java index 9064640bb4..6c405c871d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -10,6 +10,8 @@ import org.mapstruct.ap.internal.util.Strings; /** + * Decorates the assignment as an {@link java.util.Optional#get()} call. + * * @author Filip Hrisafov */ public class OptionalGetWrapper extends AssignmentWrapper { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index b94bb82efd..9300f683ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -14,6 +14,9 @@ import org.mapstruct.ap.spi.BuilderInfo; /** + * Represents the information about a builder. + * How it can be constructed, the type it is building etc. + * * @author Filip Hrisafov */ public class BuilderType { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java index 48cee4bbe5..7ef818630a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java @@ -10,6 +10,7 @@ import javax.lang.model.element.Element; /** + * Represent information about the configured formatting. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java index f8844edcdd..78357affb8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java @@ -9,6 +9,8 @@ import java.util.Set; /** + * A {@link PresenceCheck} that negates the result of another presence check. + * * @author Filip Hrisafov */ public class NegatePresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java index 6bb191214f..d71f67a2a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if all the given presence checks are present. + * * @author Filip Hrisafov */ public class AllPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java index 0b5770e42f..5f8fac9623 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if any of the given presence checks are present. + * * @author Filip Hrisafov */ public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java index e100be7e79..0c7c132023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls a Java expression. + * * @author Filip Hrisafov */ public class JavaExpressionPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java index 3496486e1d..2da4ae4023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java @@ -14,6 +14,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A presence check that checks if the source reference is null. + * * @author Filip Hrisafov */ public class NullPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java index 0f75fbba69..d35e89fce2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls the suffix on the source reference. + * * @author Filip Hrisafov */ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java index 936d049af8..83e2bf2819 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -23,6 +23,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Represents a condition configuration as configured via {@code @Condition}. + * * @author Filip Hrisafov */ public class ConditionOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java index 85734c3083..41b74aa13a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java @@ -21,6 +21,8 @@ import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_NO_ELEMENTS; /** + * Represents an enum mapping as configured via {@code @EnumMapping}. + * * @author Filip Hrisafov */ public class EnumMappingOptions extends DelegatingOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java index 50ea5df6e0..7d0ab3a7ec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java @@ -10,6 +10,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Utility class for mapping methods. + * * @author Filip Hrisafov */ public final class MappingMethodUtils { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java index e2ccc3f476..df9c8e0b0c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import org.mapstruct.ap.internal.util.XmlConstants; /** + * Base class for built-in methods for converting from a particular type to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java index c8cd89169b..3461538c41 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Calendar} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class CalendarToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java index 036edd83a2..fb556e68a1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Date} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class DateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java index d0b06300c4..ef57e03175 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.util.JodaTimeConstants; /** + * A built-in method for converting from Joda {@code DateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java index 80fa9ba326..285ebb29fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java index 37c89ef360..8163aa2fb2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDate} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java index b29c7aec54..4fca4e0ed3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java index 178a77b5fa..b52c7fb586 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Andrei Arlou */ public class LocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java index 2e2544ade0..e2ec7d7696 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDate} to {@code XMLGregorianCalendar}. + * * @author Gunnar Morling */ public class LocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java index 9f0d2ad207..635853d0fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link String} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class StringToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java index 03927fd869..e52b306af0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java index 38f76a66ee..fd3d9a33a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Date}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java index ab6658bfc5..93ec3e8f96 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import java.util.Calendar; import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; @@ -16,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code DateTime} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java index f45488755c..9a9c1561fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDate}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java index af7fd9e9fd..a8d36a13cf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDateTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java index 3725691134..b26450d560 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * Conversion from {@code XMLGregorianCalendar} to Joda {@code LocalTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java index 2c9ced3413..7bcd5902a5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDate}. + * * @author Gunnar Morling */ public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java index b676bd03a4..21b4c57854 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDateTime}. + * * @author Andrei Arlou */ public class XmlGregorianCalendarToLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java index c932da6675..e293a9c80b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java @@ -18,6 +18,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link String}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToString extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java index 27a39cd2b7..d9e39b388e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link ZonedDateTime} to {@code XMLGregorianCalendar}. + * * @author Christian Bandowski */ public class ZonedDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 23af0a55f9..87ec3b4dc1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.source.Method; /** + * A {@link MethodSelector} that selects the most specific result type. + * * @author Filip Hrisafov */ public class MostSpecificResultTypeSelector implements MethodSelector { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java index 7acb1af210..1702a8d4d6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java @@ -14,6 +14,8 @@ /** * A selected method with additional metadata that might be required for further usage of the selected method. * + * @param the type of the method + * * @author Andreas Gudian */ public class SelectedMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java index d35b2c0dc6..fedbe997a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.internal.option; /** + * The different compiler mapping options that are available in MapStruct. + * * @author Filip Hrisafov */ public enum MappingOption { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java index 72a44d106f..67ce6e10ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java @@ -34,6 +34,11 @@ import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.methodsIn; +/** + * MapStruct specific abstract implementation of {@link ElementUtils}. + * This allows us to provide different implementations for different compilers and / or + * to allow us for easier implementation of using the module system. + */ public abstract class AbstractElementUtilsDecorator implements ElementUtils { private final Elements delegate; @@ -312,6 +317,8 @@ private TypeElement asTypeElement(TypeMirror mirror) { } /** + * Checks whether the {@code executable} does not have a private modifier. + * * @param executable the executable to check * @return {@code true}, iff the executable does not have a private modifier */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java index f2328a91cc..96768daf56 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java @@ -16,6 +16,11 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with meta annotations. + * + * @param The type of the processed annotation + * @param The type of the underlying holder for the processed annotation + * * @author Filip Hrisafov */ public abstract class MetaAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java index accae77718..76126bf488 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java @@ -16,6 +16,12 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with repeatable annotations. + * + * @param The singular annotation type + * @param The multiple annotation type + * @param The underlying holder for the processed annotations + * * @author Ben Zegveld */ public abstract class RepeatableAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java index 9b2981d7c5..5ebc835ba1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} which delegates all calls to another {@link Accessor}. + * * @author Filip Hrisafov */ public abstract class DelegateAccessor implements Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java index cc974b939f..d96788b7f7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java @@ -8,6 +8,8 @@ import javax.lang.model.element.ExecutableElement; /** + * Accessor for presence checks. + * * @author Filip Hrisafov */ public interface PresenceCheckAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 31edf3a6da..4be3c26ff9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} that can be used for reading a property from a bean. + * * @author Filip Hrisafov */ public interface ReadAccessor extends Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java index 608c1b21cf..aa93936662 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java @@ -6,6 +6,9 @@ package org.mapstruct.ap.internal.util.accessor; /** + * {@link ReadAccessor} that delegates to another {@link Accessor} and requires an implementation of + * {@link #getSimpleName()} + * * @author Filip Hrisafov */ public abstract class ReadDelegateAccessor extends DelegateAccessor implements ReadAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 511504c937..e7529dcd08 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -188,7 +188,6 @@ private boolean isAdderWithUpperCase4thCharacter(ExecutableElement method) { * {@link #getElementName(ExecutableElement) }. *

    * The calling MapStruct code guarantees there's only one argument. - *

    * * @param method to be analyzed * diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index afcee7d247..333a11dbcc 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -317,7 +317,7 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type *

    * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * {@code builderElement} should be ignored, i.e. not checked for build elements. - *

    + * * @param builderElement the element for the builder * @param typeElement the element for the type that is being built * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java index 62d6c32809..11e1257be1 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java @@ -10,6 +10,8 @@ import javax.lang.model.util.Types; /** + * The default implementation of the {@link EnumMappingStrategy} service provider interface. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java index 9dae81cda2..2a1cacfb1f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -15,7 +15,6 @@ *

    * 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 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 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java index abb6e9cb4f..226251dfab 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that prepends a prefix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java index d3f0c515b3..dec7af6de4 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a prefix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java index cf239600d4..3b76354fa7 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a suffix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java index 4cd92b9af8..1f4eb89217 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that appends a suffix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java index 7b8f374cf7..93bccf51d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java @@ -4,4 +4,7 @@ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ +/** + * Utility classes for the SPI package. + */ package org.mapstruct.ap.spi.util; From d59e6a04aa3fb4bb1ca313959d4eecc9891287c9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:09:36 +0100 Subject: [PATCH 050/125] Cleanup checks for JRE prior 21 in processor test module The processor test module is compiled using Java 21. Therefore, all tests that were configured to run prior Java 21 are no longer executed and can be safely removed --- .../conversion/date/DateConversionTest.java | 75 ------------------- .../jodatime/JodaConversionTest.java | 32 -------- .../runner/JdkCompilingExtension.java | 33 -------- 3 files changed, 140 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index bc90228206..346f34a054 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -13,9 +13,6 @@ import java.util.GregorianCalendar; import java.util.List; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; @@ -40,38 +37,6 @@ public class DateConversionTest { @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversions() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "06.07.2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); - } - - @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionsWithCustomLocale() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -85,8 +50,6 @@ public void shouldApplyDateFormatForConversionsJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -100,42 +63,6 @@ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { } @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMapping() { - Target target = new Target(); - target.setDate( "06.07.2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMappingWithCustomLocale() { - Target target = new Target(); - target.setDate( "juillet 06, 2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { Target target = new Target(); target.setDate( "06.07.2013" ); @@ -151,8 +78,6 @@ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { Target target = new Target(); target.setDate( "juillet 06, 2013" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java index 8574c7a6be..d447c455ff 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java @@ -13,8 +13,6 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -71,8 +69,6 @@ public void testLocalTimeToString() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_21) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -98,34 +94,6 @@ public void testSourceToTargetMappingForStrings() { assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); } - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void testSourceToTargetMappingForStringsJdk11() { - Source src = new Source(); - src.setLocalTime( new LocalTime( 0, 0 ) ); - src.setLocalDate( new LocalDate( 2014, 1, 1 ) ); - src.setLocalDateTime( new LocalDateTime( 2014, 1, 1, 0, 0 ) ); - src.setDateTime( new DateTime( 2014, 1, 1, 0, 0, 0, DateTimeZone.UTC ) ); - - // with given format - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( src ); - - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); - - // and now with default mappings - target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); - } - @ProcessorTest public void testStringToDateTime() { String dateTimeAsString = "01.01.2014 00:00 UTC"; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 115618382c..1e9a1bc22f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -12,9 +12,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.processing.Processor; -import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; @@ -23,10 +21,8 @@ import javax.tools.StandardLocation; import javax.tools.ToolProvider; -import org.junit.jupiter.api.condition.JRE; import org.mapstruct.ap.MappingProcessor; import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; /** * Extension that uses the JDK compiler to compile. @@ -140,35 +136,6 @@ private static List asFiles(List paths) { return classpath; } - /** - * The JDK 8 compiler needs some special treatment for the diagnostics. - * See comment in the function. - */ - @Override - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - if ( JRE.currentVersion() != JRE.JAVA_8 ) { - // The JDK 8+ compilers report all ERROR diagnostics properly. Also when there are multiple per line. - return expectedDiagnostics; - } - List filtered = new ArrayList<>(expectedDiagnostics.size()); - - // The JDK 8 compiler only reports the first message of kind ERROR that is reported for one source file line, - // so we filter out the surplus diagnostics. The input list is already sorted by file name and line number, - // with the order for the diagnostics in the same line being kept at the order as given in the test. - DiagnosticDescriptor previous = null; - for ( DiagnosticDescriptor diag : expectedDiagnostics ) { - if ( diag.getKind() != Kind.ERROR - || previous == null - || !previous.getSourceFileName().equals( diag.getSourceFileName() ) - || !Objects.equals( previous.getLine(), diag.getLine() ) ) { - filtered.add( diag ); - previous = diag; - } - } - - return filtered; - } - private static FilteringParentClassLoader newFilteringClassLoaderForJdk() { return new FilteringParentClassLoader( "kotlin.", From 73f042bdc5906a3d5a0774c6535dabce64a83799 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:25:06 +0100 Subject: [PATCH 051/125] Use explicit version instead of JRE#OTHER to disable on Java 27-ea `@DisabledOnJre(JRE.OTHER)` no longer disables the test if the JRE is unknown. --- .../org/mapstruct/itest/tests/MavenIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 4596efd630..60784d206a 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -89,7 +89,7 @@ void jsr330Test() { @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokBuilderTest() { } @@ -98,7 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokModuleTest() { } @@ -155,7 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void kotlinDataTest() { } From 6708e3592539ec5b4f94c09643c039e359b78eae Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 19:49:02 +0100 Subject: [PATCH 052/125] Remove testing with no longer maintained org.bsc.maven:maven-processor-plugin The last release of the plugin was in April 2024. In addition to that, the maven-compiler-plugin is de-facto the standard for compiling Java. Therefore, we are removing testing with the maven-processor-plugin. --- .../ProcessorInvocationInterceptor.java | 23 +++++------- .../testutil/extension/ProcessorTest.java | 5 +-- integrationtest/src/test/resources/pom.xml | 37 ------------------- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 85ce64952f..39cd5fdae6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -136,21 +136,16 @@ private void addAdditionalCliArguments(Verifier verifier) private void configureProcessor(Verifier verifier) { ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); String compilerId = processor.getCompilerId(); - if ( compilerId != null ) { - String profile = processor.getProfile(); - if ( profile == null ) { - profile = "generate-via-compiler-plugin"; + String profile = processor.getProfile(); + if ( profile == null ) { + profile = "generate-via-compiler-plugin"; + } + verifier.addCliOption( "-P" + profile ); + verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); } - verifier.addCliOption( "-P" + profile ); - verifier.addCliOption( "-Dcompiler-id=" + compilerId ); - if ( processor == ProcessorTest.ProcessorType.JAVAC ) { - if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { - verifier.addCliOption( "-Dmaven.compiler.proc=full" ); - } - } - } - else { - verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); } } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java index 95480eee9d..d5b4860d5b 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java @@ -46,9 +46,7 @@ enum ProcessorType { JAVAC( "javac" ), JAVAC_WITH_PATHS( "javac", JRE.OTHER, "generate-via-compiler-plugin-with-annotation-processor-paths" ), - ECLIPSE_JDT( "jdt", JRE.JAVA_8 ), - - PROCESSOR_PLUGIN( null, JRE.JAVA_8 ); + ECLIPSE_JDT( "jdt", JRE.JAVA_8 ); private final String compilerId; private final JRE max; @@ -111,7 +109,6 @@ ProcessorType[] processorTypes() default { ProcessorType.JAVAC, ProcessorType.JAVAC_WITH_PATHS, ProcessorType.ECLIPSE_JDT, - ProcessorType.PROCESSOR_PLUGIN }; /** diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c8a19228f0..c2ece3b38d 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -97,43 +97,6 @@ - - generate-via-processor-plugin - - false - - - - - org.bsc.maven - maven-processor-plugin - 3.3.3 - - - process - generate-sources - - process - - - - - \${project.build.directory}/generated-sources/mapstruct - - org.mapstruct.ap.MappingProcessor - - - - - ${project.groupId} - mapstruct-processor - ${mapstruct.version} - - - - - - debug-forked-javac From df97305df070ee5a33233a9072bd4c38d8d99e53 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 12:09:53 +0100 Subject: [PATCH 053/125] #3940 Use deterministic order for supporting fields and methods --- .../ap/internal/model/BeanMappingMethod.java | 4 ++-- .../ap/internal/model/SupportingField.java | 2 +- .../creation/MappingResolverImpl.java | 7 ++++--- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- .../OptionalSourceTargetMapperImpl.java | 20 +++++++++---------- .../java8time/SourceTargetMapperImpl.java | 20 +++++++++---------- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index c6fc7753f2..d67b550ad4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -301,8 +301,8 @@ else if ( !method.isUpdateMethod() ) { } // get bean mapping (when specified as annotation ) - this.missingIgnoredSourceProperties = new HashSet<>(); - this.redundantIgnoredSourceProperties = new HashSet<>(); + this.missingIgnoredSourceProperties = new LinkedHashSet<>(); + this.redundantIgnoredSourceProperties = new LinkedHashSet<>(); if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { // Get source properties explicitly mapped using @Mapping annotations Set mappedSourceProperties = method.getOptions().getMappings().stream() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java index caf6d6160e..2cac1ef351 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java @@ -47,7 +47,7 @@ public static void addAllFieldsIn(Set supportingMapping for ( SupportingMappingMethod supportingMappingMethod : supportingMappingMethods ) { Field field = supportingMappingMethod.getSupportingField(); if ( field != null ) { - targets.add( supportingMappingMethod.getSupportingField() ); + targets.add( field ); } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 2a36f6b890..67cdc09fc9 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -97,12 +98,12 @@ public class MappingResolverImpl implements MappingResolver { * Private methods which are not present in the original mapper interface and are added to map certain property * types. */ - private final Set usedSupportedMappings = new HashSet<>(); + private final Set usedSupportedMappings = new LinkedHashSet<>(); /** * Private fields which are added to map certain property types. */ - private final Set usedSupportedFields = new HashSet<>(); + private final Set usedSupportedFields = new LinkedHashSet<>(); public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtils, TypeUtils typeUtils, TypeFactory typeFactory, List sourceModel, @@ -199,7 +200,7 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; - this.supportingMethodCandidates = new HashSet<>(); + this.supportingMethodCandidates = new LinkedHashSet<>(); this.selectionCriteria = criteria; this.positionHint = positionHint; this.forger = forger; diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java index ccf8042e6b..941bd929a7 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -509,16 +509,16 @@ public Map targetToSourceWithCustomLocale(Map targetToSourceWithCustomLocale(Map Date: Sun, 1 Feb 2026 12:41:20 +0100 Subject: [PATCH 054/125] #1830 Add support for NullValuePropertyMappingStrategy.CLEAR strategy Using this strategy will clear target collections / maps when the source property is null --- NEXT_RELEASE_CHANGELOG.md | 1 + .../NullValuePropertyMappingStrategy.java | 9 +- ...apter-10-advanced-mapping-options.asciidoc | 3 + .../NullValuePropertyMappingStrategyGem.java | 3 +- ...nceSetterWrapperForCollectionsAndMaps.java | 27 ++-- .../model/source/MapperConfigOptions.java | 2 +- .../internal/model/source/MapperOptions.java | 2 +- ...anceSetterWrapperForCollectionsAndMaps.ftl | 8 +- .../nullvaluepropertymapping/clear/Bean.java | 40 ++++++ .../clear/BeanDTO.java | 40 ++++++ .../clear/BeanDTOWithId.java | 19 +++ .../clear/BeanMapper.java | 34 +++++ .../clear/BeanMapperWithStrategyOnMapper.java | 25 ++++ .../clear/NestedBean.java | 18 +++ .../NullValuePropertyMappingClearTest.java | 116 ++++++++++++++++++ 15 files changed, 329 insertions(+), 18 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 59fed4fed3..476f3e025d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -22,6 +22,7 @@ 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) +* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index a58eed8a2a..0ab30d5271 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -56,5 +56,12 @@ public enum NullValuePropertyMappingStrategy { * If a source bean property equals {@code null} the target bean property will be ignored and retain its * existing value. */ - IGNORE; + IGNORE, + + /** + * If a source bean property equals {@code null} the target bean property will be cleared. + * This strategy is only applicable to target properties that are of type {@link java.util.Collection Collection} + * or {@link java.util.Map Map}. + */ + CLEAR; } diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 43f8fc38a0..34f1e19817 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -254,6 +254,9 @@ For all other objects an new instance is created. Please note that a default con 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. +3. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the target collection or map will be cleared when the source property is `null`. +This strategy only applies to `Collection` and `Map` target properties. + 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] diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java index 77e4aa48d6..93c98b73ac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java @@ -15,5 +15,6 @@ public enum NullValuePropertyMappingStrategyGem { SET_TO_NULL, SET_TO_DEFAULT, - IGNORE; + IGNORE, + CLEAR; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java index 2dd7f81a0b..875368b014 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -5,19 +5,18 @@ */ package org.mapstruct.ap.internal.model.assignment; -import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; - import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; -import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; /** * This wrapper handles the situation where an assignment is done for an update method. @@ -34,8 +33,8 @@ public class ExistingInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { - private final boolean includeElseBranch; - private final boolean mapNullToDefault; + private final NullValuePropertyMappingStrategyGem nvpms; + private final NullValueCheckStrategyGem nvcs; private final Type targetType; public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, @@ -53,9 +52,9 @@ public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAs typeFactory, fieldAssignment ); - this.mapNullToDefault = SET_TO_DEFAULT == nvpms; + this.nvcs = nvcs; + this.nvpms = nvpms; this.targetType = targetType; - this.includeElseBranch = ALWAYS != nvcs && IGNORE != nvpms; } @Override @@ -68,11 +67,15 @@ public Set getImportTypes() { } public boolean isIncludeElseBranch() { - return includeElseBranch; + return nvcs != ALWAYS && nvpms != IGNORE; } public boolean isMapNullToDefault() { - return mapNullToDefault; + return nvpms == NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; + } + + public boolean isMapNullToClear() { + return nvpms == NullValuePropertyMappingStrategyGem.CLEAR; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index d606658878..2766faa64c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -8,8 +8,8 @@ import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 9c2203efd5..21cb7813c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -12,8 +12,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index 95662c4c79..8e771f0ddb 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -15,8 +15,12 @@ ${ext.targetBeanName}.${ext.targetReadAccessorName}.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && includeElseBranch>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; - } + <#if mapNullToClear> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + <#else > + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; + + } } else { diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java new file mode 100644 index 0000000000..545ddffe8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class Bean { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java new file mode 100644 index 0000000000..9529da8113 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java @@ -0,0 +1,40 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class BeanDTO { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java new file mode 100644 index 0000000000..152a872d82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +public class BeanDTOWithId extends BeanDTO { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java new file mode 100644 index 0000000000..4d453579ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(target = "list", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", source = "bean.map", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO mapWithBeanMapping(Bean source, @MappingTarget BeanDTO target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java new file mode 100644 index 0000000000..0e26b91b5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java @@ -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.ap.test.nullvaluepropertymapping.clear; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) +public interface BeanMapperWithStrategyOnMapper { + + BeanMapperWithStrategyOnMapper INSTANCE = Mappers.getMapper( BeanMapperWithStrategyOnMapper.class ); + + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list") + @Mapping(target = "map", source = "bean.map") + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java new file mode 100644 index 0000000000..448645c107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +public class NestedBean { + private Bean bean; + + public Bean getBean() { + return bean; + } + + public void setBean(Bean bean) { + this.bean = bean; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java new file mode 100644 index 0000000000..20cbcdc2b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java @@ -0,0 +1,116 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.nullvaluepropertymapping.clear; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Bean.class, + BeanDTO.class, + NestedBean.class, +}) +class NullValuePropertyMappingClearTest { + + @ProcessorTest + @WithClasses(BeanMapper.class) + void generatedMapperMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.mapWithBeanMapping( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + + @ProcessorTest + @WithClasses(BeanMapperWithStrategyOnMapper.class) + void generatedMapperWithMappingDefinedInConfigMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapperWithStrategyOnMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapperWithStrategyOnMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + +} From a5614dd798dbb6c07ed41e21708eaa2ecd153aad Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 19:41:07 +0100 Subject: [PATCH 055/125] Prepare release notes and update copyright.txt --- NEXT_RELEASE_CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++----- copyright.txt | 14 +++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 476f3e025d..c4ba49e24d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -13,7 +13,7 @@ - Data classes with multiple constructors - Data classes with all default parameters - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes - +* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` ### Enhancements @@ -21,22 +21,59 @@ * 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) +* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. * Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) - - +* Use deterministic order for supporting fields and methods (#3940) +* Support `@AnnotatedWith` on decorators (#3659) +* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) +* Behaviour change: Warning when the target has no target properties (#1140) +* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) +* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) ### Bugs * Improve error message when mapping non-iterable to array (#3786) +* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) +* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. +* Support generic `@Context` (#3711) +* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) +* Properly recognize the type of public generic fields (#3807) +* Fix method in Record is treated as a fluent setter (#3886) +* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) +* Fix Compiler error when mapping an object named `Override` (#3905) ### Documentation +* General Improvements + * Javadoc + * Typos in comments + * Small code refactorings + ### Build +* Move Windows and MacOS builds outside of the main workflow +* Update release to release using the new Maven Central Portal +* Skip codecov coverage on forks +* Improve testing support for Kotlin + ### Behaviour Change -#### Warning when the target has no target properties +#### Warning when the target has no target properties (#1140) 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. + +#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) + +With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. +The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. + +#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) + +With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. + +#### Mark `String` to `Number` as lossy conversion (#3848) + +With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. +This is similar to what is happening when mapping `long` to `int`, etc. +The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/copyright.txt b/copyright.txt index bd0034b076..356deb904d 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,12 +1,14 @@ Contributors ============ +Aleksey Ivashin - https://github.com/xumk 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 +Cause Chung - https://github.com/cuzfrog Christian Bandowski - https://github.com/chris922 Chris DeLashmutt - https://github.com/cdelashmutt-pivotal Christian Kosmowski - https://github.com/ckosmowski @@ -15,6 +17,7 @@ Christophe Labouisse - https://github.com/ggtools Ciaran Liedeman - https://github.com/cliedeman Cindy Wang - https://github.com/birdfriend Cornelius Dirmeier - https://github.com/cornzy +Dennis Melzer - https://github.com/ David Feinblum - https://github.com/dvfeinblum Darren Rambaud - https://github.com/xyzst Dekel Pilli - https://github.com/dekelpilli @@ -53,7 +56,11 @@ Pavel Makhov - https://github.com/streetturtle Peter Larson - https://github.com/pjlarson Remko Plantenga - https://github.com/sonata82 Remo Meier - https://github.com/remmeier +Roel Mangelschots - https://github.com/rmschots +Ritesh Chopade - https://github.com/codeswithritesh Richard Lea - https://github.com/chigix +Roman Obolonskyi - https://github.com/Obolrom +Samil Can - https://github.com/SamilCan Saheb Preet Singh - https://github.com/sahebpreet Samuel Wright - https://github.com/samwright Sebastian Haberey - https://github.com/sebastianhaberey @@ -65,10 +72,15 @@ Taras Mychaskiw - https://github.com/twentylemon Thibault Duperron - https://github.com/Zomzog Tomáš Poledný - https://github.com/Saljack Tobias Meggendorfer - https://github.com/incaseoftrouble +Tran Ngoc Nhan - https://github.com/ 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 \ No newline at end of file +Xiu Hong Kooi - https://github.com/kooixh +Yang Tang - https://github.com/tangyang9464 +Zegveld - https://github.com/Zegveld +znight1020 - https://github.com/znight1020 +zral - https://github.com/zyberzebra From 14f9f6e2877e5ae1b28ce3b59e381628f72fa349 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:10:29 +0000 Subject: [PATCH 056/125] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f2b85d4969..ae877ef10c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:10:28Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From 561d5dad858a4ba621bcab1f50452ea3851c17eb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:56:00 +0100 Subject: [PATCH 057/125] Adjust release to use Maven Central Publishing Portal --- .github/workflows/release.yml | 2 ++ parent/pom.xml | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4dffe0586..a4feca94a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,8 @@ jobs: JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} + JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN: ${{ secrets.SONATYPE_CENTRAL_TOKEN }} JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} run: | diff --git a/parent/pom.xml b/parent/pom.xml index ae877ef10c..0043cfb44a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -880,24 +880,21 @@ - - - ALWAYS - https://oss.sonatype.org/service/local - https://oss.sonatype.org/content/repositories/snapshots/ - true - true + + + RELEASE + https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy - - - org.mapstruct - mapstruct-jdk8 - false - false - - - - + + + + org.mapstruct + mapstruct-jdk8 + false + false + + + From 6389f2a8056b01e792d02acee6dd1bf9ef3a3718 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:58:29 +0100 Subject: [PATCH 058/125] Revert "Releasing version 1.7.0.Beta1" This reverts commit 14f9f6e2877e5ae1b28ce3b59e381628f72fa349. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 0043cfb44a..01b97c10bf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:10:28Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From ab72ced6fb7a71591d51343493bf736379c61beb Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:59:33 +0000 Subject: [PATCH 059/125] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 01b97c10bf..f81ea2c5d1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:59:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From fc2d5e520c5bceff54c302641431465410bdf031 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:20 +0100 Subject: [PATCH 060/125] Move jReleaser artifactOverrides in the right place --- parent/pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index f81ea2c5d1..962839bf19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -885,15 +885,15 @@ RELEASE https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy + + + org.mapstruct + mapstruct-jdk8 + false + false + + - - - org.mapstruct - mapstruct-jdk8 - false - false - - From 744c398326e9fa281134330dafdbdfecf1653c02 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:32 +0100 Subject: [PATCH 061/125] Revert "Releasing version 1.7.0.Beta1" This reverts commit ab72ced6fb7a71591d51343493bf736379c61beb. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 962839bf19..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:59:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 84318113ae02adae62e1d92ee5b884d6b58337b1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:33 +0000 Subject: [PATCH 062/125] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..02fd68fcff 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T21:45:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From c7a0385903bfadfb8ef4779be2707b469fc2ace1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:02:16 +0000 Subject: [PATCH 063/125] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 69 --------------------------------------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 +-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 10 insertions(+), 79 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index c4ba49e24d..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,79 +1,10 @@ ### Features -* Support for Java 21 Sequenced Collections (#3240) -* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: - - `Optional` to `Optional` - Both source and target wrapped in `Optional` - - `Optional` to Non-`Optional` - Unwrapping `Optional` values - - Non-`Optional` to `Optional` - Wrapping values in `Optional` - - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. -* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - - Single field data classes - - Proper primary constructor detection - - Data classes with multiple constructors - - Data classes with all default parameters - - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes -* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` - ### 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. -* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. -* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) -* Use deterministic order for supporting fields and methods (#3940) -* Support `@AnnotatedWith` on decorators (#3659) -* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) -* Behaviour change: Warning when the target has no target properties (#1140) -* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) -* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) - ### Bugs -* Improve error message when mapping non-iterable to array (#3786) -* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) -* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. -* Support generic `@Context` (#3711) -* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) -* Properly recognize the type of public generic fields (#3807) -* Fix method in Record is treated as a fluent setter (#3886) -* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) -* Fix Compiler error when mapping an object named `Override` (#3905) - ### Documentation -* General Improvements - * Javadoc - * Typos in comments - * Small code refactorings - ### Build -* Move Windows and MacOS builds outside of the main workflow -* Update release to release using the new Maven Central Portal -* Skip codecov coverage on forks -* Improve testing support for Kotlin - -### Behaviour Change - -#### Warning when the target has no target properties (#1140) - -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. - -#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) - -With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. -The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. - -#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) - -With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. - -#### Mark `String` to `Number` as lossy conversion (#3848) - -With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. -This is similar to what is happening when mapping `long` to `int`, etc. -The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 02fd68fcff..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T21:45:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 5cf1a98d29975aa32fa4a6e2a78e2808bc8fdec9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 11:48:24 +0100 Subject: [PATCH 064/125] Fix location for Javadoc when generating distribution zip --- distribution/src/main/assembly/dist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml index f0c727f8da..e0bc00496b 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -76,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api From 06e27d575365147ed17a991c5cc90a1f63436eda Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:56:16 +0100 Subject: [PATCH 065/125] Remove unnecessary `keySet()` invocation (#3989) --- .../ap/internal/model/NestedTargetPropertyMappingHolder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 2b407e240c..f7b28b3f84 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -151,8 +151,7 @@ public NestedTargetPropertyMappingHolder build() { entryByTP.getValue(), groupedByTP.singleTargetReferences.get( targetProperty ) ); - boolean multipleSourceParametersForTP = - groupedBySourceParam.groupedBySourceParameter.keySet().size() > 1; + boolean multipleSourceParametersForTP = groupedBySourceParam.groupedBySourceParameter.size() > 1; // All not processed mappings that should have been applied to all are part of the unprocessed // defined targets From aff971d7b713bcc15eb750840b00369b61cb28f5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:13 +0100 Subject: [PATCH 066/125] Let GitHub determine whether or not the released version is the latest or not --- parent/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..ffa9952de5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -904,6 +904,11 @@ ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + legacy From e9ebec2307fed606fb58f60d2e2226caf9f47614 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:56 +0100 Subject: [PATCH 067/125] Factory method for LinkedHashMap and LinkedHashSet is always there for SequencedSet and SequencedMap (#3990) --- .../mapstruct/ap/internal/model/common/TypeFactory.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index c2646f63db..eb2c1dee07 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -152,15 +152,11 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_SET_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_MAP_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) ); this.loggingVerbose = loggingVerbose; From 15312d6e46fb7744cf63e90ec70f20f518190457 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:14:53 +0100 Subject: [PATCH 068/125] Fix self check in equals of Type (#3995) --- .../java/org/mapstruct/ap/internal/model/common/Type.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 29bb3cd906..8a91c80f03 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1303,8 +1303,8 @@ public boolean equals(Object obj) { Type other = (Type) obj; if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { - return ( this.hasExtendsBound() == this.hasExtendsBound() - || this.hasSuperBound() == this.hasSuperBound() ) + return ( this.hasExtendsBound() == other.hasExtendsBound() + || this.hasSuperBound() == other.hasSuperBound() ) && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); } else { From 588006727dea2beb8e0c6019e52bab95ec57993e Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:53:45 +0100 Subject: [PATCH 069/125] Improve performance of `Type.describe()` by removing regex matching (#3991) --- .../org/mapstruct/ap/internal/model/common/Type.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8a91c80f03..3e81cd622d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1331,7 +1331,7 @@ public String describe() { } else { // name allows for inner classes - String name = getFullyQualifiedName().replaceFirst( "^" + getPackageName() + ".", "" ); + String name = getNameKeepingInnerClasses(); List typeParams = getTypeParameters(); if ( typeParams.isEmpty() ) { return name; @@ -1343,6 +1343,15 @@ public String describe() { } } + private String getNameKeepingInnerClasses() { + String packageNamePrefix = getPackageName() + "."; + String fullyQualifiedName = getFullyQualifiedName(); + if (fullyQualifiedName.startsWith( packageNamePrefix ) ) { + return fullyQualifiedName.substring( packageNamePrefix.length() ); + } + return fullyQualifiedName; + } + /** * * @return an identification that can be used as part in a forged method name. From 4a75490e6d8b622083720bf60596680b49ce5e31 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:56:19 +0100 Subject: [PATCH 070/125] Simplified boolean logic in ValueMappingMethod by removing inversion (#4007) --- .../ap/internal/model/ValueMappingMethod.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java index 66b6a75961..46af8f0efd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java @@ -184,8 +184,8 @@ private List enumToEnumMapping(Method method, Type sourceType, Typ List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean targetErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean targetErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); if ( sourceErrorOccurred || targetErrorOccurred ) { return mappings; } @@ -268,8 +268,8 @@ private List enumToStringMapping(Method method, Type sourceType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean anyRemainingUsedError = !reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean anyRemainingUsedError = reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); if ( sourceErrorOccurred || anyRemainingUsedError ) { return mappings; } @@ -298,7 +298,7 @@ private List stringToEnumMapping(Method method, Type targetType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( targetType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); reportWarningIfAnyRemainingOrAnyUnMappedMissing( method ); if ( sourceErrorOccurred ) { return mappings; @@ -372,7 +372,7 @@ else if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { foundIncorrectMapping = true; } } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method method) { @@ -388,7 +388,7 @@ private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method meth ); foundIncorrectMapping = true; } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private void reportWarningIfAnyRemainingOrAnyUnMappedMissing(Method method) { @@ -462,7 +462,7 @@ else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != n ); } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private Type determineUnexpectedValueMappingException() { From 66625e61a4c4cbe72b58cb4187fde2cdf8e84fe4 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:01:38 +0100 Subject: [PATCH 071/125] Simplify fail in assertCheckstyleRules (#4003) --- .../org/mapstruct/ap/testutil/runner/CompilingExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index a02ab2fe47..2599e62c2f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -55,6 +55,7 @@ import org.xml.sax.InputSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; @@ -237,8 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { String errorLog = errorStream.toString( "UTF-8" ); - assertThat( true ).describedAs( "Expected checkstyle compliant output, but got errors:\n" + errorLog ) - .isEqualTo( false ); + fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } } From 876a62d282af0ca0c5b6bf21070c5441db95c9b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:04:23 +0100 Subject: [PATCH 072/125] Update license plugin (#3999) --- .../build.gradle | 5 ++ .../settings.gradle | 5 ++ parent/pom.xml | 72 ++++++++++--------- pom.xml | 10 ++- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle index e62a8d087f..0df032f0f2 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ plugins { id 'java' } diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle index f62a77ab7b..b6ca918e91 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle @@ -1 +1,6 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ rootProject.name = 'gradle-incremental-compilation-test' \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index ffa9952de5..ad95ffacf3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -509,9 +509,9 @@ ${kotlin.version} - com.mycila.maven-license-plugin - maven-license-plugin - 1.10.b1 + com.mycila + license-maven-plugin + 4.6 org.codehaus.mojo @@ -649,39 +649,43 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
      ${basedir}/../etc/license.txt
      true - - **/.idea/** - **/.mvn/** - **/build-config/checkstyle.xml - **/build-config/import-control.xml - copyright.txt - **/LICENSE.txt - **/mapstruct.xml - **/ci-settings.xml - **/eclipse-formatter-config.xml - **/forbidden-apis.txt - **/checkstyle-for-generated-sources.xml - **/nb-configuration.xml - **/junit-platform.properties - maven-settings.xml - readme.md - CONTRIBUTING.md - NEXT_RELEASE_CHANGELOG.md - .gitattributes - .gitignore - .factorypath - .checkstyle - *.yml - mvnw* - **/*.asciidoc - **/binding.xjb - **/*.flattened-pom.xml - + + +
      ${basedir}/../etc/license.txt
      + + **/.idea/** + **/.mvn/** + **/build-config/checkstyle.xml + **/build-config/import-control.xml + copyright.txt + **/LICENSE.txt + **/mapstruct.xml + **/ci-settings.xml + **/eclipse-formatter-config.xml + **/forbidden-apis.txt + **/checkstyle-for-generated-sources.xml + **/nb-configuration.xml + **/junit-platform.properties + maven-settings.xml + readme.md + CONTRIBUTING.md + NEXT_RELEASE_CHANGELOG.md + .gitattributes + .gitignore + .factorypath + .checkstyle + *.yml + mvnw* + **/*.asciidoc + **/binding.xjb + **/*.flattened-pom.xml + +
      +
      SLASHSTAR_STYLE SLASHSTAR_STYLE diff --git a/pom.xml b/pom.xml index f55f0955d3..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -35,10 +35,14 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
      etc/license.txt
      + + +
      etc/license.txt
      +
      +
      XML_STYLE SLASHSTAR_STYLE From f0d488712f76e02a51218e5eeaefb65bd2680a32 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:43:48 +0100 Subject: [PATCH 073/125] Use StandardCharsets.UTF_8 in tests (#4002) --- .../mapstruct/ap/testutil/runner/CompilingExtension.java | 3 ++- .../org/mapstruct/ap/testutil/runner/GeneratedSource.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 2599e62c2f..cb12be725c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -10,6 +10,7 @@ import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -237,7 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { - String errorLog = errorStream.toString( "UTF-8" ); + String errorLog = errorStream.toString( StandardCharsets.UTF_8 ); fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java index 18a53b4971..6785ea5cf2 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -6,9 +6,9 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; -import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -119,7 +119,7 @@ public JavaFileAssert forJavaFile(String path) { return new JavaFileAssert( new File( sourceOutputDir.get() + "/" + path ) ); } - private void handleFixtureComparison() throws UnsupportedEncodingException { + private void handleFixtureComparison() { for ( Class fixture : fixturesFor ) { String fixtureName = getMapperName( fixture ); URL expectedFile = getExpectedResource( fixtureName ); @@ -131,7 +131,7 @@ private void handleFixtureComparison() throws UnsupportedEncodingException { ) ); } else { - File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), "UTF-8" ) ); + File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), StandardCharsets.UTF_8 ) ); forMapper( fixture ).hasSameMapperContent( expectedResource ); } fixture.getPackage().getName(); From 1f3524570e362c34f4d0d7ce983c3596c83ad7c7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:25:03 +0100 Subject: [PATCH 074/125] Add missing self reference in GeneratedTypeBuilder (#4009) --- .../java/org/mapstruct/ap/internal/model/GeneratedType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 0bc3ec7bf0..4b39e92273 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -30,9 +30,9 @@ public abstract class GeneratedType extends ModelElement { private static final String JAVA_LANG_PACKAGE = "java.lang"; - protected abstract static class GeneratedTypeBuilder { + protected abstract static class GeneratedTypeBuilder> { - private T myself; + private final T myself; protected TypeFactory typeFactory; protected ElementUtils elementUtils; protected Options options; From bff3efd10640170a5ad10dea8a5fa0e4173a932a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Mar 2026 00:25:54 +0100 Subject: [PATCH 075/125] Remove obsolete override of AssertJ version in integration tests (#4015) --- integrationtest/src/test/resources/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c2ece3b38d..3053b211e1 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -26,8 +26,6 @@ ${mapstruct.version} - - 1.7.1 From 94da2c3f05af31ab9693056aab9785549212940b Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:30:40 +0100 Subject: [PATCH 076/125] Remove unsued methods in Fields leftover from c2e803403027f3fae92bd15b0ba50ab7df5063e6 (#4010) --- .../mapstruct/ap/internal/util/Fields.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java index 8e16a95274..b2f8ac1442 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java @@ -6,13 +6,7 @@ package org.mapstruct.ap.internal.util; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * Provides functionality around {@link VariableElement}s. @@ -35,17 +29,4 @@ static boolean isPublic(VariableElement method) { private static boolean isNotStatic(VariableElement method) { return !method.getModifiers().contains( Modifier.STATIC ); } - - private static TypeElement asTypeElement(TypeMirror mirror) { - return (TypeElement) ( (DeclaredType) mirror ).asElement(); - } - - private static boolean hasNonObjectSuperclass(TypeElement element) { - if ( element.getSuperclass().getKind() == TypeKind.ERROR ) { - throw new TypeHierarchyErroneousException( element ); - } - - return element.getSuperclass().getKind() == TypeKind.DECLARED - && !asTypeElement( element.getSuperclass() ).getQualifiedName().toString().equals( "java.lang.Object" ); - } } From ded3daa5fcad390d60fe04d4d8e9479466722d63 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:31:23 +0100 Subject: [PATCH 077/125] Upgrade Visitor6 to Visitor8 (#4011) --- .../src/main/java/org/mapstruct/ap/MappingProcessor.java | 4 ++-- .../mapstruct/ap/internal/util/AccessorNamingUtils.java | 8 ++++---- .../mapstruct/ap/spi/DefaultAccessorNamingStrategy.java | 8 ++++---- .../java/org/mapstruct/ap/spi/DefaultBuilderProvider.java | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index a66762d739..32e65c7047 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -30,7 +30,7 @@ import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementKindVisitor6; +import javax.lang.model.util.ElementKindVisitor8; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; @@ -402,7 +402,7 @@ private R process(ProcessorContext context, ModelElementProcessor p private TypeElement asTypeElement(Element element) { return element.accept( - new ElementKindVisitor6() { + new ElementKindVisitor8() { @Override public TypeElement visitTypeAsInterface(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java index 0f17946f72..8025acfeb1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java @@ -10,8 +10,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; @@ -84,7 +84,7 @@ public String getElementNameForAdder(Accessor adderMethod) { private static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -98,7 +98,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index e7529dcd08..a726e9691f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -14,8 +14,8 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; import kotlin.Metadata; @@ -273,7 +273,7 @@ public String getElementName(ExecutableElement adderMethod) { */ protected static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -287,7 +287,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 333a11dbcc..c1126fa9fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -21,8 +21,8 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; /** @@ -126,7 +126,7 @@ private TypeElement getTypeElement(DeclaredType declaredType) { } return declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; @@ -148,7 +148,7 @@ private DeclaredType getDeclaredType(TypeMirror type) { throw new TypeHierarchyErroneousException( type ); } return type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; From 0780d98fc2045092adbfe4417e46d853ecd946b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:00 +0100 Subject: [PATCH 078/125] Upgrade freemarker to 2.3.34 (#4012) --- parent/pom.xml | 2 +- .../org/mapstruct/ap/internal/model/Annotation.ftl | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index ad95ffacf3..88bfd5053c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -119,7 +119,7 @@ org.freemarker freemarker - 2.3.32 + 2.3.34 org.assertj diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index 81f3d2ba8b..0b6737fbe4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -7,12 +7,10 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> <#switch properties?size> - <#case 0> + <#on 0> @<@includeModel object=type/><#rt> - <#break> - <#case 1> + <#on 1> @<@includeModel object=type/>(<@includeModel object=properties[0]/>)<#rt> - <#break> <#default> @<@includeModel object=type/>( <#list properties as property> From e0fa28868d8e01ecdb9df23440464aa9f1f07832 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:41 +0100 Subject: [PATCH 079/125] Remove deprecated Number api usage from tests (#4013) --- .../ap/test/selection/twosteperror/ErroneousMapperMC.java | 2 +- .../mapstruct/ap/test/source/constants/SourceConstantsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java index 4b7dcf90a3..b6381cf6da 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java @@ -22,7 +22,7 @@ default BigDecimal methodX1(SourceType s) { } default Double methodX2(SourceType s) { - return new Double( s.t1 ); + return Double.valueOf( s.t1 ); } // CHECKSTYLE:OFF diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java index b331a2efba..1b1764fe56 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java @@ -47,7 +47,7 @@ public void shouldMapSameSourcePropertyToSeveralTargetProperties() throws ParseE assertThat( target.getStringConstant() ).isEqualTo( "stringConstant" ); assertThat( target.getEmptyStringConstant() ).isEqualTo( "" ); assertThat( target.getIntegerConstant() ).isEqualTo( 14 ); - assertThat( target.getLongWrapperConstant() ).isEqualTo( new Long( 3001L ) ); + assertThat( target.getLongWrapperConstant() ).isEqualTo( Long.valueOf( 3001L ) ); assertThat( target.getDateConstant() ).isEqualTo( getDate( "dd-MM-yyyy", "09-01-2014" ) ); assertThat( target.getNameConstants() ).isEqualTo( Arrays.asList( "jack", "jill", "tom" ) ); assertThat( target.getCountry() ).isEqualTo( CountryEnum.THE_NETHERLANDS ); From dab3eaf237eb025a0d59f997263fea515cb6db64 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:44:29 +0100 Subject: [PATCH 080/125] #3949: Support SET_TO_NULL for overloaded target methods, requiring a cast (#3988) --- .../model/CollectionAssignmentBuilder.java | 3 +- .../ap/internal/model/PropertyMapping.java | 34 ++++++- .../model/assignment/SetterWrapper.java | 24 ++++- .../model/assignment/UpdateWrapper.java | 9 +- .../ap/internal/model/common/Type.java | 2 +- .../model/assignment/UpdateWrapper.ftl | 2 +- .../ap/internal/model/macro/CommonMacros.ftl | 2 +- .../ap/test/bugs/_3949/DateSource.java | 14 +++ .../test/bugs/_3949/Issue3949ClassMapper.java | 29 ++++++ .../bugs/_3949/Issue3949InterfaceMapper.java | 29 ++++++ .../ap/test/bugs/_3949/Issue3949Test.java | 94 +++++++++++++++++++ .../ap/test/bugs/_3949/ParentSource.java | 12 +++ .../ap/test/bugs/_3949/ParentTarget.java | 25 +++++ .../bugs/_3949/ParentTargetInterface.java | 14 +++ .../ap/test/bugs/_3949/StringSource.java | 12 +++ .../ap/test/bugs/_3949/TargetDate.java | 33 +++++++ .../test/bugs/_3949/TargetDateInterface.java | 18 ++++ .../ap/test/bugs/_3949/TargetString.java | 34 +++++++ .../bugs/_3949/TargetStringInterface.java | 18 ++++ 19 files changed, 399 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 76e56cd976..db9e012d78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -163,7 +163,8 @@ public Assignment build() { targetType, true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + false ); } else if ( method.isUpdateMethod() && !targetImmutable ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 622ba9cef0..c120f035b2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -13,6 +13,8 @@ import java.util.Set; import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; @@ -454,7 +456,8 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { targetType, !rhs.isSourceReferenceParameter(), nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName() ); } else { @@ -471,7 +474,10 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { isFieldAssignment(), includeSourceNullCheck, includeSourceNullCheck && nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT ); + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType + ); } } @@ -547,12 +553,33 @@ else if ( result.getSourceType().isStreamType() ) { isFieldAssignment(), true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType ); } return result; } + private boolean hasTwoOrMoreSettersWithName() { + Element enclosingClass = this.targetWriteAccessor.getElement().getEnclosingElement(); + if ( enclosingClass == null || !( ElementKind.CLASS.equals( enclosingClass.getKind() ) + || ElementKind.INTERFACE.equals( enclosingClass.getKind() ) ) ) { + return false; + } + String simpleWriteAccessorName = this.targetWriteAccessor.getSimpleName(); + boolean firstMatchFound = false; + for ( Accessor setter : ctx.getTypeFactory().getType( enclosingClass.asType() ).getSetters() ) { + if ( setter.getSimpleName().equals( simpleWriteAccessorName ) ) { + if ( firstMatchFound ) { + return true; + } + firstMatchFound = true; + } + } + return false; + } + private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType, Assignment rhs) { return new CollectionAssignmentBuilder() @@ -991,6 +1018,7 @@ public PropertyMapping build() { targetType, false, false, + false, false ); } else { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java index 7be948c857..94b031c622 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.model.assignment; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; @@ -22,19 +24,25 @@ public class SetterWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; + private final Type nullCastType; public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault) { + boolean setExplicitlyToDefault, + boolean mustCastForNull, + Type nullCastType) { super( rhs, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; + this.nullCastType = nullCastType; } public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment ) { @@ -43,6 +51,8 @@ public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fi this.includeSourceNullCheck = false; this.setExplicitlyToNull = false; this.setExplicitlyToDefault = false; + this.mustCastForNull = false; + this.nullCastType = null; } @Override @@ -59,6 +69,15 @@ public List getThrownTypes() { return result; } + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isSetExplicitlyToNull() && isMustCastForNull() ) { + imported.add( nullCastType ); + } + return imported; + } + public boolean isSetExplicitlyToNull() { return setExplicitlyToNull; } @@ -71,4 +90,7 @@ public boolean isIncludeSourceNullCheck() { return includeSourceNullCheck; } + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index ff5089d6c2..c3d3f9c446 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -26,6 +26,7 @@ public class UpdateWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; public UpdateWrapper( Assignment decoratedAssignment, List thrownTypesToExclude, @@ -34,7 +35,8 @@ public UpdateWrapper( Assignment decoratedAssignment, Type targetType, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault ) { + boolean setExplicitlyToDefault, + boolean mustCastForNull) { super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; @@ -42,6 +44,7 @@ public UpdateWrapper( Assignment decoratedAssignment, this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; } private static Type determineImplType(Assignment factoryMethod, Type targetType) { @@ -100,4 +103,8 @@ public boolean isSetExplicitlyToNull() { public boolean isSetExplicitlyToDefault() { return setExplicitlyToDefault; } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 3e81cd622d..6287403ba3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1061,7 +1061,7 @@ private TypeMirror boxed(TypeMirror possiblePrimitive) { * * @return an unmodifiable list of all setters */ - private List getSetters() { + public List getSetters() { if ( setters == null ) { setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) ); } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index d8ed4b53f6..933612f579 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -18,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 9c4ad26e03..f409fda793 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -45,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull!false>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java new file mode 100644 index 0000000000..e542c388d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class DateSource { + public LocalDate getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java new file mode 100644 index 0000000000..135aa79056 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java @@ -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.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949ClassMapper { + + Issue3949ClassMapper INSTANCE = Mappers.getMapper( Issue3949ClassMapper.class ); + + void overwriteDate(@MappingTarget TargetDate target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetString target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDate target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetString target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTarget target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java new file mode 100644 index 0000000000..c25041ad2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java @@ -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.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949InterfaceMapper { + + Issue3949InterfaceMapper INSTANCE = Mappers.getMapper( Issue3949InterfaceMapper.class ); + + void overwriteDate(@MappingTarget TargetDateInterface target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetStringInterface target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDateInterface target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetStringInterface target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTargetInterface target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java new file mode 100644 index 0000000000..b6abee819c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java @@ -0,0 +1,94 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests if overloaded targets are correctly cast when set to null + * + * @author hduelme + */ +@IssueKey("3949") +@WithClasses({ + ParentSource.class, + ParentTargetInterface.class, + ParentTarget.class, + StringSource.class, + TargetStringInterface.class, + TargetString.class, + DateSource.class, + TargetDateInterface.class, + TargetDate.class +}) +public class Issue3949Test { + + @ProcessorTest + @WithClasses({ + Issue3949ClassMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForClass() { + TargetDate shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetString shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTarget parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949ClassMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + Issue3949InterfaceMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForInterface() { + TargetDateInterface shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetStringInterface shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTargetInterface parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949InterfaceMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java new file mode 100644 index 0000000000..551501aa24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public class ParentSource { + public ParentSource getChild() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java new file mode 100644 index 0000000000..3d8ca3b51b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java @@ -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.ap.test.bugs._3949; + +public class ParentTarget implements ParentTargetInterface { + private ParentTarget child; + + @Override + public ParentTarget getChild() { + return child; + } + + @Override + public void setChild(String child) { + throw new IllegalArgumentException(); + } + + @Override + public void setChild(ParentTarget child) { + this.child = child; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java new file mode 100644 index 0000000000..5311fb4c9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public interface ParentTargetInterface { + ParentTarget getChild(); + + void setChild(String child); + + void setChild(ParentTarget child); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java new file mode 100644 index 0000000000..11ba1419d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java @@ -0,0 +1,12 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +public class StringSource { + public String getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java new file mode 100644 index 0000000000..85b12a618d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetDate implements TargetDateInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public LocalDate getDate() { + return date; + } + + @Override + public String getString() { + return string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java new file mode 100644 index 0000000000..df28936914 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetDateInterface { + void setDate(LocalDate date); + + void setDate(String date); + + LocalDate getDate(); + + String getString(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java new file mode 100644 index 0000000000..b8cbbb2bbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetString implements TargetStringInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public String getDate() { + return string; + } + + @Override + public LocalDate getDateValue() { + return date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java new file mode 100644 index 0000000000..dd82231a51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetStringInterface { + void setDate(LocalDate date); + + void setDate(String date); + + String getDate(); + + LocalDate getDateValue(); +} From 6d61c3ab9ea577ef12c0ff2e77e723f946696791 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:34:11 +0100 Subject: [PATCH 081/125] #3972: Update maven compiler plugin (#4022) * Revert "Block plexus.snapshots repository in GitHub actions #3972" This reverts commit 9d75a48df5e40c1ed87d2f68d96fbb35807e3b91 and updates the maven compiler plugin to 3.15.0 --- .github/workflows/java-ea.yml | 3 --- .github/workflows/macos.yml | 3 --- .github/workflows/main.yml | 6 ------ .github/workflows/release.yml | 4 ---- .github/workflows/windows.yml | 3 --- parent/pom.xml | 2 +- 6 files changed, 1 insertion(+), 20 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index 70b81c40f1..fc3a4a0fb1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -17,8 +17,5 @@ jobs: with: website: jdk.java.net release: EA - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 98b4e156e0..833bb6ba39 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,8 +16,5 @@ jobs: with: distribution: 'zulu' java-version: 21 - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca66a22291..c4def2a89d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,9 +23,6 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' @@ -62,8 +59,5 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4feca94a0..666a74656d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,10 +26,6 @@ jobs: distribution: 'zulu' cache: maven - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - - name: Set release version id: version run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fc37af6a9d..bda38f8783 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,8 +16,5 @@ jobs: with: distribution: 'zulu' java-version: 21 - - uses: s4u/maven-settings-action@v4.0.0 - with: - mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw %MAVEN_ARGS% install diff --git a/parent/pom.xml b/parent/pom.xml index 88bfd5053c..c4ba63948d 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -413,7 +413,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 org.apache.maven.plugins From 0d3104908196b3ad22101dd5928ea5e6f4d85c88 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:35:47 +0100 Subject: [PATCH 082/125] Use multi-catch in generated code (#4021) --- .../model/assignment/Java8FunctionWrapper.ftl | 10 +++------- .../model/assignment/LocalVarWrapper.ftl | 10 +++------- .../ap/internal/model/macro/CommonMacros.ftl | 11 +++++++--- .../adder/SourceTargetMapperImpl.java | 20 ++++--------------- 4 files changed, 18 insertions(+), 33 deletions(-) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl index ce1fcdcd6c..4c3b6d4804 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl @@ -6,6 +6,7 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> <#assign sourceVarName><#if assignment.sourceLocalVarName?? >${assignment.sourceLocalVarName}<#else>${assignment.sourceReference} <#if (thrownTypes?size == 0) > <#compress> @@ -17,14 +18,9 @@ <#else> <#compress> ${sourceVarName} -> { - try { + <@lib.handleExceptions> return <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl index 97ea856ece..163a52896a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl @@ -6,18 +6,14 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.LocalVarWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> <#if (thrownTypes?size == 0) > <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName} = <@_assignment/>; <#else> <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName}; - try { + <@lib.handleExceptions> ${ext.targetWriteAccessorName} = <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + <#macro _assignment> <@includeModel object=assignment diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index f409fda793..e857a807af 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -104,11 +104,16 @@ try { <#nested> } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { + <@compress single_line=true>catch ( + <#list thrownTypes as exceptionType> + <#if exceptionType_index > 0> | + <@includeModel object=exceptionType/> + + e ) { + + throw new RuntimeException( e ); } - <#-- diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java index 823fbb1908..52d661c3f7 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/adder/SourceTargetMapperImpl.java @@ -59,10 +59,7 @@ public Source toSource(Target source) { try { source1.setPets( petMapper.toSourcePets( source.getPets() ) ); } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } @@ -82,10 +79,7 @@ public void toExistingTarget(Source source, Target target) { } } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } } @@ -161,10 +155,7 @@ public TargetViaTargetType toTargetViaTargetType(Source source) { } } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } @@ -184,10 +175,7 @@ public Target fromSingleElementSource(SingleElementSource source) { target.addPet( petMapper.toPet( source.getPet() ) ); } } - catch ( CatException e ) { - throw new RuntimeException( e ); - } - catch ( DogException e ) { + catch ( CatException | DogException e ) { throw new RuntimeException( e ); } From 2d51aa0695272b1198d6c4cbe3458ca3ecbdeb9f Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 23 Mar 2026 23:39:41 +0100 Subject: [PATCH 083/125] Refactor TypeFactory.getTypeParameters (#4020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves readability and avoids unnecessary overhead: * Stores `typeArguments` in a local variable (since calling `getTypeArguments()` is not cached in eclipse compiler) * Replace if/else with a ternary expression * Avoids repeated `getType(...)` calls --- .../ap/internal/model/common/TypeFactory.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index eb2c1dee07..606bbce69d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -523,15 +523,12 @@ private List getTypeParameters(TypeMirror mirror, boolean isImplementation } DeclaredType declaredType = (DeclaredType) mirror; - List typeParameters = new ArrayList<>( declaredType.getTypeArguments().size() ); + List typeArguments = declaredType.getTypeArguments(); + List typeParameters = new ArrayList<>( typeArguments.size() ); - for ( TypeMirror typeParameter : declaredType.getTypeArguments() ) { - if ( isImplementationType ) { - typeParameters.add( getType( typeParameter ).getTypeBound() ); - } - else { - typeParameters.add( getType( typeParameter ) ); - } + for ( TypeMirror typeParameter : typeArguments) { + Type type = getType( typeParameter ); + typeParameters.add( isImplementationType ? type.getTypeBound() : type ); } return typeParameters; From 9aea5a8e93c28fb824b59bd883b5c88bcb6c2e6d Mon Sep 17 00:00:00 2001 From: mk868 Date: Fri, 27 Mar 2026 07:44:34 +0100 Subject: [PATCH 084/125] #4018 Add URI to String built-in conversions (#4019) --- .../chapter-5-data-type-conversions.asciidoc | 3 + .../internal/conversion/ConversionUtils.java | 12 ++++ .../ap/internal/conversion/Conversions.java | 10 ++-- .../conversion/URIToStringConversion.java | 37 ++++++++++++ .../conversion/uri/URIConversionTest.java | 56 +++++++++++++++++++ .../ap/test/conversion/uri/URIMapper.java | 19 +++++++ .../ap/test/conversion/uri/URISource.java | 33 +++++++++++ .../ap/test/conversion/uri/URITarget.java | 30 ++++++++++ 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 9da7ff75c1..d95e6c7a62 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -127,6 +127,9 @@ public interface CarMapper { * Between `String` and `StringBuilder` +* Between `java.net.URI` and `String`. +** When converting from a `String`, the value needs to be a valid https://datatracker.ietf.org/doc/html/rfc2396[RFC 2396 URI] otherwise an `IllegalArgumentException` is thrown. + * 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. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 96960c4a11..ee052b5405 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; @@ -269,6 +270,17 @@ public static String uuid(ConversionContext conversionContext) { return typeReferenceName( conversionContext, UUID.class ); } + /** + * Name for {@link java.net.URI}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String uri(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, URI.class ); + } + /** * Name for {@link java.net.URL}. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 076cd2f42e..7b2f8702b1 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URI; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; @@ -201,7 +202,7 @@ public Conversions(TypeFactory typeFactory) { register( UUID.class, String.class, new UUIDToStringConversion() ); register( Locale.class, String.class, new LocaleToStringConversion() ); - registerURLConversion(); + registerJavaNetConversions(); } private void registerJodaConversions() { @@ -306,13 +307,14 @@ private void registerBigDecimalConversion(Class targetType) { } } - private void registerURLConversion() { - if ( isJavaURLAvailable() ) { + private void registerJavaNetConversions() { + if ( isJavaNetAvailable() ) { + register( URI.class, String.class, new URIToStringConversion() ); register( URL.class, String.class, new URLToStringConversion() ); } } - private boolean isJavaURLAvailable() { + private boolean isJavaNetAvailable() { return typeFactory.isTypeAvailable( "java.net.URL" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java new file mode 100644 index 0000000000..c25383ae05 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.conversion; + +import java.net.URI; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.uri; +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link java.net.URI} and {@link String}. + * + * @author Maciej Kucharczyk + */ +public class URIToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return uri( conversionContext ) + ".create( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URI.class ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java new file mode 100644 index 0000000000..d7d3ef4142 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java @@ -0,0 +1,56 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import java.net.URI; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.net.URI} and String. + * + * @author Maciej Kucharczyk + */ +@IssueKey("4018") +@WithClasses({ URISource.class, URITarget.class, URIMapper.class }) +public class URIConversionTest { + + @ProcessorTest + public void shouldApplyURIConversion() { + URISource source = new URISource(); + source.setUriA( URI.create( "https://mapstruct.org/" ) ); + + URITarget target = URIMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getUriA() ).isEqualTo( source.getUriA().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseURIConversion() { + URITarget target = new URITarget(); + target.setUriA( "https://mapstruct.org/" ); + + URISource source = URIMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getUriA() ).isEqualTo( URI.create( target.getUriA() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidURIString() { + URITarget target = new URITarget(); + target.setInvalidURI( "ht!tp://example.com" ); + + assertThatThrownBy( () -> URIMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( IllegalArgumentException.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java new file mode 100644 index 0000000000..cac94a4efa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface URIMapper { + + URIMapper INSTANCE = Mappers.getMapper( URIMapper.class ); + + URITarget sourceToTarget(URISource source); + + URISource targetToSource(URITarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java new file mode 100644 index 0000000000..502169fccb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java @@ -0,0 +1,33 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +import java.net.URI; + +/** + * @author Maciej Kucharczyk + */ +public class URISource { + private URI uriA; + + private URI invalidURI; + + public URI getUriA() { + return uriA; + } + + public void setUriA(URI uriA) { + this.uriA = uriA; + } + + public URI getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(URI invalidURI) { + this.invalidURI = invalidURI; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java new file mode 100644 index 0000000000..b221392127 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.uri; + +/** + * @author Maciej Kucharczyk + */ +public class URITarget { + private String uriA; + private String invalidURI; + + public String getUriA() { + return uriA; + } + + public void setUriA(String uriA) { + this.uriA = uriA; + } + + public String getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(String invalidURI) { + this.invalidURI = invalidURI; + } +} From 6a567516be5e2e38a7314d892b08468c7823c6db Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:06:09 +0100 Subject: [PATCH 085/125] Upgrade integration tests to junit5 (#4023) --- .../GradleIncrementalCompilationTest.java | 50 +++++++++---------- .../itest/auto/value/AutoValueMapperTest.java | 8 +-- .../src/test/resources/cdiTest/pom.xml | 8 +-- .../itest/cdi/CdiBasedMapperTest.java | 14 +++--- .../test/java/DefaultPackageTest.java | 6 +-- .../itest/textBlocks/TextBlocksTest.java | 6 +-- .../itest/externalbeanjar/ConversionTest.java | 6 +-- .../generator/pom.xml | 4 +- .../usage/pom.xml | 4 +- .../usage/FaultyAstModifyingTestTest.java | 6 +-- .../freebuilder/FreeBuilderMapperTest.java | 8 +-- .../mapstruct/itest/simple/AnimalTest.java | 8 +-- .../immutables/ImmutablesMapperTest.java | 8 +-- .../jaxb/JakartaJaxbBasedMapperTest.java | 8 +-- .../ap/test/bugs/_603/Issue603Test.java | 7 ++- .../ap/test/bugs/_636/Issue636Test.java | 6 +-- .../itest/java8/Java8MapperTest.java | 6 +-- .../itest/jaxb/JaxbBasedMapperTest.java | 8 +-- .../src/test/resources/jsr330Test/pom.xml | 4 ++ .../itest/jsr330/Jsr330BasedMapperTest.java | 16 +++--- .../itest/kotlin/data/KotlinDataTest.java | 8 +-- .../itest/lombok/LombokMapperTest.java | 8 +-- .../itest/lombok/LombokMapperTest.java | 8 +-- .../mapstruct/itest/modules/ModulesTest.java | 6 +-- .../mapstruct/itest/naming/NamingTest.java | 6 +-- integrationtest/src/test/resources/pom.xml | 5 +- .../itest/protobuf/ProtobufMapperTest.java | 8 +-- .../itest/records/module2/RecordsTest.java | 6 +-- .../itest/records/mapper/RecordsTest.java | 8 +-- .../mapstruct/itest/records/RecordsTest.java | 22 ++++---- .../records/nested/NestedRecordsTest.java | 6 +-- .../sealedsubclass/SealedSubclassTest.java | 10 ++-- .../itest/simple/ConversionTest.java | 18 +++---- .../src/test/resources/springTest/pom.xml | 4 ++ .../itest/spring/SpringBasedMapperTest.java | 16 +++--- .../superTypeGenerationTest/generator/pom.xml | 4 +- .../superTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedBasesTest.java | 8 +-- .../generator/pom.xml | 4 +- .../targetTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedTargetTypeTest.java | 8 +-- .../usesTypeGenerationTest/generator/pom.xml | 4 +- .../usesTypeGenerationTest/usage/pom.xml | 4 +- .../usage/GeneratedUsesTypeTest.java | 6 +-- parent/pom.xml | 7 +-- 45 files changed, 193 insertions(+), 190 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java index 3d496906c0..a12af7690f 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java @@ -13,47 +13,49 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; 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.Named; 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.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.runners.Parameterized.Parameters; +import static org.assertj.core.api.Assertions.assertThat; 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; +import static org.junit.jupiter.api.Assertions.assertEquals; /** *

      This is supposed to be run from the mapstruct root project folder. * Otherwise, use -Dmapstruct_root=path_to_project. */ @DisabledForJreRange(min = JRE.JAVA_11) -public class GradleIncrementalCompilationTest { +class GradleIncrementalCompilationTest { private static Path rootPath; - private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; - private static String compileTaskName = "compileJava"; + private static final String PROJECT_DIR = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; + private static final String COMPILE_TASK_NAME = "compileJava"; @TempDir - File testBuildDir; + private File testBuildDir; @TempDir - File testProjectDir; + private File testProjectDir; private GradleRunner runner; private File sourceDirectory; private List compileArgs; // Gradle compile task arguments - @Parameters(name = "Gradle {0}") - public static List gradleVersions() { - return Arrays.asList( "5.0", "6.0" ); + static Stream gradleVersions() { + return Stream.of( + Arguments.of( Named.of( "Gradle 5.0", "5.0" ) ), + Arguments.of( Named.of( "Gradle 6.0", "6.0" ) ) ); } private void replaceInFile(File file, CharSequence target, CharSequence replacement) throws IOException { @@ -68,15 +70,13 @@ private GradleRunner getRunner(String... additionalArguments) { } private void assertCompileOutcome(BuildResult result, TaskOutcome outcome) { - assertEquals( outcome, result.task( ":" + compileTaskName ).getOutcome() ); + assertEquals( outcome, result.task( ":" + COMPILE_TASK_NAME ).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 ) ) - ); + assertThat( result.getOutput() ) + .contains( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ); } private List buildCompileArgs() { @@ -85,11 +85,11 @@ private List buildCompileArgs() { // Inject the path to the folder containing the mapstruct-processor JAR String jarDirectoryArg = "-PmapstructRootPath=" + rootPath.toString(); - return Arrays.asList( compileTaskName, buildDirPropertyArg, jarDirectoryArg ); + return Arrays.asList( COMPILE_TASK_NAME, buildDirPropertyArg, jarDirectoryArg ); } @BeforeAll - public static void setupClass() throws Exception { + static void setupClass() { rootPath = Paths.get( System.getProperty( "mapstruct_root", "." ) ).toAbsolutePath(); } @@ -102,7 +102,7 @@ public void setup(String gradleVersion) throws IOException { testProjectDir.mkdirs(); } // Copy test project files to the temp dir - Path gradleProjectPath = rootPath.resolve( projectDir ); + Path gradleProjectPath = rootPath.resolve( PROJECT_DIR ); FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir ); compileArgs = buildCompileArgs(); sourceDirectory = new File( testProjectDir, "src/main/java" ); @@ -111,7 +111,7 @@ public void setup(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testBuildSucceeds(String gradleVersion) throws IOException { + void testBuildSucceeds(String gradleVersion) throws IOException { setup( gradleVersion ); // Make sure the test build setup actually compiles BuildResult buildResult = getRunner().build(); @@ -120,7 +120,7 @@ public void testBuildSucceeds(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testUpToDate(String gradleVersion) throws IOException { + void testUpToDate(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); BuildResult secondBuildResult = getRunner().build(); @@ -129,7 +129,7 @@ public void testUpToDate(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeConstant(String gradleVersion) throws IOException { + void testChangeConstant(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); // Change return value in class Target @@ -143,7 +143,7 @@ public void testChangeConstant(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeTargetField(String gradleVersion) throws IOException { + void testChangeTargetField(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); // Change target field in mapper interface @@ -157,7 +157,7 @@ public void testChangeTargetField(String gradleVersion) throws IOException { @ParameterizedTest @MethodSource("gradleVersions") - public void testChangeUnrelatedFile(String gradleVersion) throws IOException { + void testChangeUnrelatedFile(String gradleVersion) throws IOException { setup( gradleVersion ); getRunner().build(); File unrelatedFile = new File( sourceDirectory, "org/mapstruct/itest/gradle/lib/UnrelatedComponent.java" ); diff --git a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java index 8f640780f3..4ce5530a5d 100644 --- a/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java +++ b/integrationtest/src/test/resources/autoValueBuilderTest/src/test/java/org/mapstruct/itest/auto/value/AutoValueMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.auto.value; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class AutoValueMapperTest { +class AutoValueMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() .age( 33 ) .name( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml index 0b3c394e82..cb00fbaa01 100644 --- a/integrationtest/src/test/resources/cdiTest/pom.xml +++ b/integrationtest/src/test/resources/cdiTest/pom.xml @@ -30,8 +30,8 @@ javax.inject - org.jboss.arquillian.junit - arquillian-junit-container + org.jboss.arquillian.junit5 + arquillian-junit5-container test @@ -45,8 +45,8 @@ test - org.jboss.arquillian.junit - arquillian-junit-core + org.jboss.arquillian.junit5 + arquillian-junit5-core test diff --git a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java index 0c343fce27..4ede2eb78a 100644 --- a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java +++ b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java @@ -10,12 +10,12 @@ import javax.inject.Inject; import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit5.ArquillianExtension; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.cdi.other.DateMapper; /** @@ -23,8 +23,8 @@ * * @author Gunnar Morling */ -@RunWith( Arquillian.class ) -public class CdiBasedMapperTest { +@ExtendWith( ArquillianExtension.class ) +class CdiBasedMapperTest { @Inject private SourceTargetMapper mapper; @@ -45,7 +45,7 @@ public static JavaArchive createDeployment() { } @Test - public void shouldCreateCdiBasedMapper() { + void shouldCreateCdiBasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -56,7 +56,7 @@ public void shouldCreateCdiBasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java index ddc834cc8e..c3f84b6ab6 100644 --- a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java +++ b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java @@ -4,7 +4,7 @@ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -12,10 +12,10 @@ * @author Filip Hrisafov */ -public class DefaultPackageTest { +class DefaultPackageTest { @Test - public void shouldWorkCorrectlyInDefaultPackage() { + void shouldWorkCorrectlyInDefaultPackage() { DefaultPackageObject.CarDto carDto = DefaultPackageObject.CarMapper.INSTANCE.carToCarDto( new DefaultPackageObject.Car( "Morris", diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java index 42ed70b18e..504a961437 100644 --- a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java @@ -9,12 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class TextBlocksTest { +class TextBlocksTest { @Test - public void textBlockExpressionShouldWork() { + void textBlockExpressionShouldWork() { Car car = new Car(); car.setWheelPosition( new WheelPosition( "left" ) ); diff --git a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java index 8a18e9e4fc..af10a63428 100644 --- a/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java +++ b/integrationtest/src/test/resources/externalbeanjar/mapper/src/test/java/org/mapstruct/itest/externalbeanjar/ConversionTest.java @@ -8,15 +8,15 @@ import java.math.BigDecimal; import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.externalbeanjar.Source; import org.mapstruct.itest.externalbeanjar.Issue1121Mapper; import org.mapstruct.itest.externalbeanjar.Target; -public class ConversionTest { +class ConversionTest { @Test - public void shouldApplyConversions() { + void shouldApplyConversions() { Source source = new Source(); source.setBigDecimal( new BigDecimal( "42" ) ); diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml index 08390085dd..60b73b856c 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml @@ -27,8 +27,8 @@ provided - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml index 90c95aa83b..976664f88d 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java index 06bfa3c533..ed6a5c0d89 100644 --- a/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java +++ b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/usage/src/test/java/org/mapstruct/itest/faultyAstModifyingProcessor/usage/FaultyAstModifyingTestTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.faultyAstModifyingProcessor.usage; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Filip Hrisafov */ -public class FaultyAstModifyingTestTest { +class FaultyAstModifyingTestTest { @Test - public void testMapping() { + void testMapping() { Order order = new Order(); order.setItem( "my item" ); diff --git a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java index 5047451798..3b1caebf9c 100644 --- a/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java +++ b/integrationtest/src/test/resources/freeBuilderBuilderTest/src/test/java/org/mapstruct/itest/freebuilder/FreeBuilderMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.freebuilder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class FreeBuilderMapperTest { +class FreeBuilderMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() .setAge( 33 ) .setName( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java index a180b4df52..14dd576fe0 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java +++ b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.ap.test.ignore.AnimalMapper; import org.mapstruct.ap.test.ignore.Animal; @@ -18,10 +18,10 @@ * * @author Gunnar Morling */ -public class AnimalTest { +class AnimalTest { @Test - public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { Animal animal = new Animal( "Bruno", 100, 23, "black" ); AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); @@ -34,7 +34,7 @@ public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { } @Test - public void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() { + void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() { AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23, "black" ); Animal animal = AnimalMapper.INSTANCE.animalDtoToAnimal( animalDto ); diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java index 421954fc90..9990f4278c 100644 --- a/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java +++ b/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/test/java/org/mapstruct/itest/immutables/ImmutablesMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.immutables; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Eric Martineau */ -public class ImmutablesMapperTest { +class ImmutablesMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( ImmutablePerson.builder() .age( 33 ) .name( "Bob" ) @@ -32,7 +32,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java index b81c946d9c..afd5ba0289 100644 --- a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java @@ -18,7 +18,7 @@ import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory; import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType; import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType; @@ -28,10 +28,10 @@ * * @author Iaroslav Bogdanchikov */ -public class JakartaJaxbBasedMapperTest { +class JakartaJaxbBasedMapperTest { @Test - public void shouldMapJakartaJaxb() throws ParseException, JAXBException { + void shouldMapJakartaJaxb() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; @@ -81,7 +81,7 @@ public void shouldMapJakartaJaxb() throws ParseException, JAXBException { } @Test - public void underscores() throws ParseException, JAXBException { + void underscores() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java index c732b96f22..efe68a48ee 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_603/Issue603Test.java @@ -5,15 +5,14 @@ */ package org.mapstruct.ap.test.bugs._603; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class Issue603Test { +class Issue603Test { @Test - public void shouldMapDataFromJava8Interface() { + void shouldMapDataFromJava8Interface() { final Source source = new Source(); diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java index 99b7a515eb..837eb0b53a 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/ap/test/bugs/_636/Issue636Test.java @@ -5,14 +5,14 @@ */ package org.mapstruct.ap.test.bugs._636; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class Issue636Test { +class Issue636Test { @Test - public void shouldMapDataFromJava8Interface() { + void shouldMapDataFromJava8Interface() { final long idFoo = 123; final String idBar = "Bar456"; diff --git a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java index 556dd2f1e4..3f88889380 100644 --- a/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java +++ b/integrationtest/src/test/resources/java8Test/src/test/java/org/mapstruct/itest/java8/Java8MapperTest.java @@ -7,17 +7,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test for generation of Java8 based mapper implementations. * * @author Christian Schuster */ -public class Java8MapperTest { +class Java8MapperTest { @Test - public void shouldMapWithRepeatedMappingAnnotation() { + void shouldMapWithRepeatedMappingAnnotation() { Java8Mapper mapper = Java8Mapper.INSTANCE; Source source = new Source(); diff --git a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java index f54658ba06..4c81dc91d2 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java +++ b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java @@ -18,7 +18,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory; import org.mapstruct.itest.jaxb.xsd.test1.OrderType; import org.mapstruct.itest.jaxb.xsd.underscores.SubType; @@ -28,9 +28,9 @@ * * @author Sjaak Derksen */ -public class JaxbBasedMapperTest { +class JaxbBasedMapperTest { @Test - public void shouldMapJaxb() throws ParseException, JAXBException { + void shouldMapJaxb() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; @@ -80,7 +80,7 @@ public void shouldMapJaxb() throws ParseException, JAXBException { } @Test - public void underscores() throws ParseException, JAXBException { + void underscores() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; diff --git a/integrationtest/src/test/resources/jsr330Test/pom.xml b/integrationtest/src/test/resources/jsr330Test/pom.xml index 9a7dbcbc8a..e1cf959a90 100644 --- a/integrationtest/src/test/resources/jsr330Test/pom.xml +++ b/integrationtest/src/test/resources/jsr330Test/pom.xml @@ -18,6 +18,10 @@ jsr330Test jar + + + ${org.junit6.jupiter.version} + diff --git a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java index 525930f0ec..6cf9043472 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java +++ b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java @@ -8,13 +8,13 @@ import jakarta.inject.Inject; import jakarta.inject.Named; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.jsr330.Jsr330BasedMapperTest.SpringTestConfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -24,8 +24,8 @@ * @author Andreas Gudian */ @ContextConfiguration(classes = SpringTestConfig.class) -@RunWith(SpringJUnit4ClassRunner.class) -public class Jsr330BasedMapperTest { +@ExtendWith(SpringExtension.class) +class Jsr330BasedMapperTest { @Configuration @ComponentScan(basePackageClasses = Jsr330BasedMapperTest.class) public static class SpringTestConfig { @@ -43,7 +43,7 @@ public static class SpringTestConfig { private SecondDecoratedSourceTargetMapper secondDecoratedMapper; @Test - public void shouldInjectJsr330BasedMapper() { + void shouldInjectJsr330BasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -54,7 +54,7 @@ public void shouldInjectJsr330BasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -71,7 +71,7 @@ public void shouldInjectDecorator() { } @Test - public void shouldInjectSecondDecorator() { + void shouldInjectSecondDecorator() { Source source = new Source(); Target target = secondDecoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java index 513f080a8b..e46b76f5f0 100644 --- a/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java +++ b/integrationtest/src/test/resources/kotlinDataTest/src/test/java/org/mapstruct/itest/kotlin/data/KotlinDataTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.kotlin.data.CustomerDto; import org.mapstruct.itest.kotlin.data.CustomerEntity; import org.mapstruct.itest.kotlin.data.CustomerMapper; -public class KotlinDataTest { +class KotlinDataTest { @Test - public void shouldMapData() { + void shouldMapData() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -24,7 +24,7 @@ public void shouldMapData() { } @Test - public void shouldMapIntoData() { + void shouldMapIntoData() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java index 6053f294c3..060fb5a990 100644 --- a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.lombok; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; @@ -15,10 +15,10 @@ * * @author Eric Martineau */ -public class LombokMapperTest { +class LombokMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.foo() .age( 33 ) .name( "Bob" ) @@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java index f8b702896d..bdf8f46a57 100644 --- a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java +++ b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.lombok; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; @@ -15,10 +15,10 @@ * * @author Eric Martineau */ -public class LombokMapperTest { +class LombokMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( new Person( "Bob", 33, new Address( "Wild Drive" ) ) ); assertThat( personDto.getAge() ).isEqualTo( 33 ); assertThat( personDto.getName() ).isEqualTo( "Bob" ); @@ -27,7 +27,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); assertThat( person.getName() ).isEqualTo( "Bob" ); diff --git a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java index 805662c806..aae996c842 100644 --- a/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java +++ b/integrationtest/src/test/resources/moduleInfoTest/src/test/java/org/mapstruct/itest/modules/ModulesTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.modules.CustomerDto; import org.mapstruct.itest.modules.CustomerEntity; import org.mapstruct.itest.modules.CustomerMapper; -public class ModulesTest { +class ModulesTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerDto dto = new CustomerDto(); dto.setName( "Kermit" ); dto.setEmail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java index d3a45db26b..adc7aac7cb 100644 --- a/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java +++ b/integrationtest/src/test/resources/namingStrategyTest/usage/src/test/java/org/mapstruct/itest/naming/NamingTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.naming.GolfPlayer; import org.mapstruct.itest.naming.GolfPlayerDto; import org.mapstruct.itest.naming.GolfPlayerMapper; @@ -17,10 +17,10 @@ * * @author Gunnar Morling */ -public class NamingTest { +class NamingTest { @Test - public void shouldApplyCustomNamingStrategy() { + void shouldApplyCustomNamingStrategy() { GolfPlayer player = new GolfPlayer() .withName( "Jared" ) .withHandicap( 9.2D ); diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index 3053b211e1..f4cd0b51de 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -26,6 +26,7 @@ ${mapstruct.version} + 6.0.3 @@ -135,8 +136,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java index ae3740848f..ac97eba8b4 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java +++ b/integrationtest/src/test/resources/protobufBuilderTest/src/test/java/org/mapstruct/itest/protobuf/ProtobufMapperTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.protobuf; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Christian Bandowski */ -public class ProtobufMapperTest { +class ProtobufMapperTest { @Test - public void testSimpleImmutableBuilderHappyPath() { + void testSimpleImmutableBuilderHappyPath() { PersonDto personDto = PersonMapper.INSTANCE.toDto( PersonProtos.Person.newBuilder() .setAge( 33 ) .setName( "Bob" ) @@ -33,7 +33,7 @@ public void testSimpleImmutableBuilderHappyPath() { } @Test - public void testLombokToImmutable() { + void testLombokToImmutable() { PersonProtos.Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); assertThat( person.getAge() ).isEqualTo( 33 ); diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java index 5f7a99273a..4064e0fe3f 100644 --- a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java @@ -7,14 +7,14 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.module1.SourceRootRecord; import org.mapstruct.itest.records.module1.SourceNestedRecord; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMap() { + void shouldMap() { SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) ); TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source ); diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java index 2f274792b8..218fd82c6a 100644 --- a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.api.CustomerDto; import org.mapstruct.itest.records.mapper.CustomerEntity; import org.mapstruct.itest.records.mapper.CustomerMapper; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -24,7 +24,7 @@ public void shouldMapRecord() { } @Test - public void shouldMapIntoRecord() { + void shouldMapIntoRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java index 2f77e8d49f..2a510edafd 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -9,15 +9,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.records.CustomerDto; import org.mapstruct.itest.records.CustomerEntity; import org.mapstruct.itest.records.CustomerMapper; -public class RecordsTest { +class RecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) ); assertThat( customer ).isNotNull(); @@ -26,7 +26,7 @@ public void shouldMapRecord() { } @Test - public void shouldMapIntoRecord() { + void shouldMapIntoRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); @@ -39,7 +39,7 @@ public void shouldMapIntoRecord() { } @Test - public void shouldMapIntoGenericRecord() { + void shouldMapIntoGenericRecord() { CustomerEntity entity = new CustomerEntity(); entity.setName( "Kermit" ); entity.setMail( "kermit@test.com" ); @@ -51,7 +51,7 @@ public void shouldMapIntoGenericRecord() { } @Test - public void shouldMapIntoRecordWithList() { + void shouldMapIntoRecordWithList() { Car car = new Car(); car.setWheelPositions( Arrays.asList( new WheelPosition( "left" ) ) ); @@ -63,7 +63,7 @@ public void shouldMapIntoRecordWithList() { } @Test - public void shouldMapMemberRecord() { + void shouldMapMemberRecord() { MemberEntity member = MemberMapper.INSTANCE.fromRecord( new MemberDto( true, false ) ); assertThat( member ).isNotNull(); @@ -72,7 +72,7 @@ public void shouldMapMemberRecord() { } @Test - public void shouldMapIntoMemberRecord() { + void shouldMapIntoMemberRecord() { MemberEntity entity = new MemberEntity(); entity.setIsActive( false ); entity.setPremium( true ); @@ -80,12 +80,12 @@ public void shouldMapIntoMemberRecord() { MemberDto value = MemberMapper.INSTANCE.toRecord( entity ); assertThat( value ).isNotNull(); - assertThat( value.isActive() ).isEqualTo( false ); - assertThat( value.premium() ).isEqualTo( true ); + assertThat( value.isActive() ).isFalse(); + assertThat( value.premium() ).isTrue(); } @Test - public void shouldUseDefaultConstructor() { + void shouldUseDefaultConstructor() { Task entity = new Task( "some-id", 1000L ); TaskDto value = TaskMapper.INSTANCE.toRecord( entity ); diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java index c8ccaf1a65..c70937fa84 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java @@ -9,12 +9,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class NestedRecordsTest { +class NestedRecordsTest { @Test - public void shouldMapRecord() { + void shouldMapRecord() { CareProvider source = new CareProvider( "kermit", new Address( "Sesame Street", "New York" ) ); CareProviderDto target = CareProviderMapper.INSTANCE.map( source ); diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java index 379341ff66..171b6519c6 100644 --- a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java @@ -7,12 +7,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class SealedSubclassTest { +class SealedSubclassTest { @Test - public void mappingIsDoneUsingSubclassMapping() { + void mappingIsDoneUsingSubclassMapping() { VehicleCollection vehicles = new VehicleCollection(); vehicles.getVehicles().add( new Car() ); vehicles.getVehicles().add( new Bike() ); @@ -28,7 +28,7 @@ public void mappingIsDoneUsingSubclassMapping() { } @Test - public void inverseMappingIsDoneUsingSubclassMapping() { + void inverseMappingIsDoneUsingSubclassMapping() { VehicleCollectionDto vehicles = new VehicleCollectionDto(); vehicles.getVehicles().add( new CarDto() ); vehicles.getVehicles().add( new BikeDto() ); @@ -44,7 +44,7 @@ public void inverseMappingIsDoneUsingSubclassMapping() { } @Test - public void subclassMappingInheritsInverseMapping() { + void subclassMappingInheritsInverseMapping() { VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); CarDto carDto = new CarDto(); carDto.setMaker( "BenZ" ); diff --git a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java index 2b6a0bc93b..69c94322bd 100644 --- a/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java +++ b/integrationtest/src/test/resources/simpleTest/src/test/java/org/mapstruct/itest/simple/ConversionTest.java @@ -7,15 +7,15 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.simple.Source; import org.mapstruct.itest.simple.SourceTargetMapper; import org.mapstruct.itest.simple.Target; -public class ConversionTest { +class ConversionTest { @Test - public void shouldApplyConversions() { + void shouldApplyConversions() { Source source = new Source(); source.setFoo( 42 ); source.setBar( 23L ); @@ -30,18 +30,18 @@ public void shouldApplyConversions() { } @Test - public void shouldHandleNulls() { + void shouldHandleNulls() { Source source = new Source(); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 1 ) ); - assertThat( target.getBar() ).isEqualTo( 0 ); + assertThat( target.getBar() ).isZero(); assertThat( target.getZip() ).isEqualTo( "0" ); } @Test - public void shouldApplyConversionsToMappedProperties() { + void shouldApplyConversionsToMappedProperties() { Source source = new Source(); source.setQax( 42 ); source.setBaz( 23L ); @@ -54,7 +54,7 @@ public void shouldApplyConversionsToMappedProperties() { } @Test - public void shouldApplyConversionsForReverseMapping() { + void shouldApplyConversionsForReverseMapping() { Target target = new Target(); target.setFoo( 42L ); target.setBar( 23 ); @@ -69,7 +69,7 @@ public void shouldApplyConversionsForReverseMapping() { } @Test - public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { + void shouldApplyConversionsToMappedPropertiesForReverseMapping() { Target target = new Target(); target.setQax( 42 ); target.setBaz( 23L ); @@ -82,7 +82,7 @@ public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { } @Test - public void shouldWorkWithAbstractClass() { + void shouldWorkWithAbstractClass() { Source source = new Source(); source.setFoo( 42 ); source.setBar( 23L ); diff --git a/integrationtest/src/test/resources/springTest/pom.xml b/integrationtest/src/test/resources/springTest/pom.xml index 3c1fb54552..e4210c0cba 100644 --- a/integrationtest/src/test/resources/springTest/pom.xml +++ b/integrationtest/src/test/resources/springTest/pom.xml @@ -18,6 +18,10 @@ springTest jar + + + ${org.junit6.jupiter.version} + diff --git a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java index a5b3bb0e65..c0f3de4ce2 100644 --- a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java +++ b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java @@ -5,14 +5,14 @@ */ package org.mapstruct.itest.spring; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.itest.spring.SpringBasedMapperTest.SpringTestConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; @@ -22,8 +22,8 @@ * @author Andreas Gudian */ @ContextConfiguration(classes = SpringTestConfig.class) -@RunWith(SpringJUnit4ClassRunner.class) -public class SpringBasedMapperTest { +@ExtendWith(SpringExtension.class) +class SpringBasedMapperTest { @Configuration @ComponentScan(basePackageClasses = SpringBasedMapperTest.class) @@ -40,7 +40,7 @@ public static class SpringTestConfig { private SecondDecoratedSourceTargetMapper secondDecoratedMapper; @Test - public void shouldInjectSpringBasedMapper() { + void shouldInjectSpringBasedMapper() { Source source = new Source(); Target target = mapper.sourceToTarget( source ); @@ -51,7 +51,7 @@ public void shouldInjectSpringBasedMapper() { } @Test - public void shouldInjectDecorator() { + void shouldInjectDecorator() { Source source = new Source(); Target target = decoratedMapper.sourceToTarget( source ); @@ -68,7 +68,7 @@ public void shouldInjectDecorator() { } @Test - public void shouldInjectSecondDecorator() { + void shouldInjectSecondDecorator() { Source source = new Source(); Target target = secondDecoratedMapper.sourceToTarget( source ); diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 5ab2d0d18f..f122047093 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index d1e1dd7dff..4c5e4611f0 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java index 4a012283d3..f34bebe36d 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.itest.supertypegeneration.usage; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.supertypegeneration.usage.Order; import org.mapstruct.itest.supertypegeneration.usage.OrderDto; import org.mapstruct.itest.supertypegeneration.usage.OrderMapper; @@ -18,10 +18,10 @@ * * @author Gunnar Morling */ -public class GeneratedBasesTest { +class GeneratedBasesTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); order.setBaseName2( "my base name 2" ); diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index 67df383a18..89bd9c2d2f 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index bd06b79a49..0726580592 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java index d9e3f8d789..d7fb6b00fa 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/targettypegeneration/usage/GeneratedTargetTypeTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.itest.targettypegeneration.usage; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Integration test for using MapStruct with another annotation processor that generates the target type of a mapping @@ -15,10 +15,10 @@ * * @author Gunnar Morling */ -public class GeneratedTargetTypeTest { +class GeneratedTargetTypeTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml index 6ac3a01297..24d3436533 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml index 79696df47d..d7154c6246 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/pom.xml @@ -21,8 +21,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java index e715e66620..55ea60a0ad 100644 --- a/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/usestypegeneration/usage/GeneratedUsesTypeTest.java @@ -5,7 +5,7 @@ */ package org.mapstruct.itest.usestypegeneration.usage; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -14,10 +14,10 @@ * * @author Filip Hrisafov */ -public class GeneratedUsesTypeTest { +class GeneratedUsesTypeTest { @Test - public void considersPropertiesOnGeneratedSourceAndTargetTypes() { + void considersPropertiesOnGeneratedSourceAndTargetTypes() { Order order = new Order(); order.setItem( "my item" ); diff --git a/parent/pom.xml b/parent/pom.xml index c4ba63948d..f3b0fe25f9 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -141,11 +141,6 @@ gem-processor ${org.mapstruct.gem.version} - - junit - junit - 4.13.1 - com.puppycrawl.tools checkstyle @@ -201,7 +196,7 @@ org.jboss.arquillian arquillian-bom - 1.6.0.Final + 1.7.2.Final import pom From f76f11c08defcc8bb507c9247e7c217087397634 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Mar 2026 15:20:33 +0100 Subject: [PATCH 086/125] Enforce spaces inside parentheses for control flow statements via checkstyle --- .../src/main/resources/build-config/checkstyle.xml | 2 +- core/src/main/java/org/mapstruct/factory/Mappers.java | 4 ++-- .../main/java/org/mapstruct/ap/MappingProcessor.java | 2 +- .../mapstruct/ap/internal/conversion/Conversions.java | 2 +- .../ap/internal/model/AbstractBaseBuilder.java | 4 ++-- .../mapstruct/ap/internal/model/BeanMappingMethod.java | 2 +- .../ap/internal/model/DefaultMapperReference.java | 2 +- .../java/org/mapstruct/ap/internal/model/Mapper.java | 6 +++--- .../ap/internal/model/MappingBuilderContext.java | 2 +- .../ap/internal/model/NestedPropertyMappingMethod.java | 2 +- .../ap/internal/model/ObjectFactoryMethodResolver.java | 2 +- .../mapstruct/ap/internal/model/PropertyMapping.java | 10 +++++----- ...etterWrapperForCollectionsAndMapsWithNullCheck.java | 2 +- .../internal/model/beanmapping/MappingReference.java | 8 ++++---- .../internal/model/beanmapping/MappingReferences.java | 2 +- .../ap/internal/model/common/BuilderType.java | 2 +- .../org/mapstruct/ap/internal/model/common/Type.java | 8 ++++---- .../ap/internal/model/common/TypeFactory.java | 6 +++--- .../ap/internal/model/source/MappingMethodOptions.java | 4 ++-- .../ap/internal/model/source/MethodMatcher.java | 2 +- .../source/selector/JakartaXmlElementDeclSelector.java | 4 ++-- .../source/selector/JavaxXmlElementDeclSelector.java | 4 ++-- .../selector/MostSpecificResultTypeSelector.java | 2 +- .../internal/model/source/selector/TypeSelector.java | 2 +- .../processor/creation/MappingResolverImpl.java | 6 +++--- .../java/org/mapstruct/ap/internal/util/Strings.java | 4 ++-- .../ap/test/bugs/_1482/TargetSourceMapper.java | 2 +- .../ap/test/bugs/_1801/Issue1801BuilderProvider.java | 2 +- .../org/mapstruct/ap/test/bugs/_2663/JsonNullable.java | 2 +- .../org/mapstruct/ap/test/bugs/_2663/Nullable.java | 2 +- .../ap/test/bugs/_3089/Issue3089BuilderProvider.java | 2 +- ...nalMethodWithSourcePropertyNameInContextMapper.java | 2 +- ...nalMethodWithTargetPropertyNameInContextMapper.java | 2 +- .../mapstruct/ap/test/nestedbeans/RecursionTest.java | 6 +++--- .../selection/ExternalHandWrittenMapper.java | 2 +- 35 files changed, 59 insertions(+), 59 deletions(-) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index 71a7d35899..14567bf067 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -141,7 +141,7 @@ - + diff --git a/core/src/main/java/org/mapstruct/factory/Mappers.java b/core/src/main/java/org/mapstruct/factory/Mappers.java index 05b616fe9a..d20bd59fa3 100644 --- a/core/src/main/java/org/mapstruct/factory/Mappers.java +++ b/core/src/main/java/org/mapstruct/factory/Mappers.java @@ -84,10 +84,10 @@ private static T doGetMapper(Class clazz, ClassLoader classLoader) throws return constructor.newInstance(); } - catch (ClassNotFoundException e) { + catch ( ClassNotFoundException e ) { return getMapperFromServiceLoader( clazz, classLoader ); } - catch ( InstantiationException | InvocationTargetException | IllegalAccessException e) { + catch ( InstantiationException | InvocationTargetException | IllegalAccessException e ) { throw new RuntimeException( e ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 32e65c7047..01a2a35955 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -264,7 +264,7 @@ private Set getMappers(final Set annotations try { Set annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation ); - for (Element mapperElement : annotatedMappers) { + for ( Element mapperElement : annotatedMappers ) { TypeElement mapperTypeElement = asTypeElement( mapperElement ); // on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 7b2f8702b1..c29ad5141e 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -355,7 +355,7 @@ public ConversionProvider getConversion(Type sourceType, Type targetType) { else if ( targetType.isOptionalType() ) { // Type -> Optional Type targetBaseType = targetType.getOptionalBaseType(); - if ( targetBaseType.equals( sourceType )) { + if ( targetBaseType.equals( sourceType ) ) { return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; } ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 584e0260bc..8859e542b8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -115,8 +115,8 @@ Assignment createForgedAssignment(SourceRHS source, ForgedMethod methodRef, Mapp if ( mappingMethod == null ) { return null; } - if (methodRef.getMappingReferences().isRestrictToDefinedMappings() || - !ctx.getMappingsToGenerate().contains( mappingMethod )) { + if ( methodRef.getMappingReferences().isRestrictToDefinedMappings() || + !ctx.getMappingsToGenerate().contains( mappingMethod ) ) { // If the mapping options are restricted only to the defined mappings, then use the mapping method. // See https://github.com/mapstruct/mapstruct/issues/1148 ctx.getMappingsToGenerate().add( mappingMethod ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index d67b550ad4..a73bce56e3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -629,7 +629,7 @@ private boolean isCorrectlySealed(Type mappingSourceType) { List unusedPermittedSubclasses = new ArrayList<>( mappingSourceType.getPermittedSubclasses() ); method.getOptions().getSubclassMappings().forEach( subClassOption -> { - for (Iterator iterator = unusedPermittedSubclasses.iterator(); + for ( Iterator iterator = unusedPermittedSubclasses.iterator(); iterator.hasNext(); ) { if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) { iterator.remove(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java index 6ac0c1c74d..2728f99323 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java @@ -36,7 +36,7 @@ private DefaultMapperReference(Type type, boolean isAnnotatedMapper, boolean isS public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedMapper, boolean isSingleton, TypeFactory typeFactory, List otherMapperReferences) { Set importTypes = Collections.asSet( type ); - if ( isAnnotatedMapper && !isSingleton) { + if ( isAnnotatedMapper && !isSingleton ) { importTypes.add( typeFactory.getType( "org.mapstruct.factory.Mappers" ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index cd092ca4f4..bcf7e840e5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -199,12 +199,12 @@ protected String getTemplateName() { * @return the flat name for the type element */ public static String getFlatName(TypeElement element) { - if (!(element.getEnclosingElement() instanceof TypeElement)) { + if ( !(element.getEnclosingElement() instanceof TypeElement) ) { return element.getSimpleName().toString(); } StringBuilder nameBuilder = new StringBuilder( element.getSimpleName().toString() ); - for (Element enclosing = element.getEnclosingElement(); enclosing instanceof TypeElement; enclosing = - enclosing.getEnclosingElement()) { + for ( Element enclosing = element.getEnclosingElement(); enclosing instanceof TypeElement; enclosing = + enclosing.getEnclosingElement() ) { nameBuilder.insert( 0, '$' ); nameBuilder.insert( 0, enclosing.getSimpleName().toString() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index 1ebf99ffeb..59c3efd141 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -228,7 +228,7 @@ public List getReservedNames() { nameSet.add( method.getName() ); } // add existing names - for ( SourceMethod method : sourceModel) { + for ( SourceMethod method : sourceModel ) { if ( method.isAbstract() ) { nameSet.add( method.getName() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index ed841ba5bc..6fbd5e6f82 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -179,7 +179,7 @@ public List getPropertyEntries() { @Override public Set getImportTypes() { Set types = super.getImportTypes(); - for ( SafePropertyEntry propertyEntry : safePropertyEntries) { + for ( SafePropertyEntry propertyEntry : safePropertyEntries ) { types.addAll( propertyEntry.getType().getImportTypes() ); if ( propertyEntry.getPresenceChecker() != null ) { types.addAll( propertyEntry.getPresenceChecker().getImportTypes() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index 8b526b9177..88965dedca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -74,7 +74,7 @@ public static MethodReference getFactoryMethod( Method method, ctx ); - if (matchingFactoryMethods.isEmpty()) { + if ( matchingFactoryMethods.isEmpty() ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index c120f035b2..5defa65bf5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -314,7 +314,7 @@ private Assignment forge( ) { else if ( sourceType.isMapType() && targetType.isMapType() ) { assignment = forgeMapMapping( sourceType, targetType, rightHandSide ); } - else if ( sourceType.isMapType() && !targetType.isMapType()) { + else if ( sourceType.isMapType() && !targetType.isMapType() ) { assignment = forgeMapping( sourceType, targetType.withoutBounds(), rightHandSide ); } else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) @@ -427,7 +427,7 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { Assignment factory = ObjectFactoryMethodResolver .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx ); - if ( factory == null && targetBuilderType != null) { + if ( factory == null && targetBuilderType != null ) { // If there is no dedicated factory method and the target has a builder we will try to use that MethodReference builderFactoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( targetType, @@ -732,9 +732,9 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc String variableName = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); Type variableType = propertyEntry.getType(); - for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { + for ( int i = 1; i < sourceReference.getPropertyEntries().size(); i++ ) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); - if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { + if ( entry.getPresenceChecker() != null && entry.getReadAccessor() != null ) { if ( variableType.isOptionalType() ) { presenceChecks.add( new OptionalPresenceCheck( variableName, @@ -852,7 +852,7 @@ private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sour // because we are forging a Mapping for a method with multiple source parameters. // If the target type is enum, then we can't create an update method if ( !targetType.isEnumType() && ( method.isUpdateMethod() || forceUpdateMethod ) - && targetWriteAccessorType != AccessorType.ADDER) { + && targetWriteAccessorType != AccessorType.ADDER ) { parameters.add( Parameter.forForgedMappingTarget( targetType ) ); returnType = ctx.getTypeFactory().createVoidType(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java index a0c9f799e9..b9f1d2acb0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java @@ -57,7 +57,7 @@ public Set getImportTypes() { imported.add( typeFactory.getType( EnumSet.class ) ); } } - if (isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { + if ( isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { imported.addAll( getNullCheckLocalVarType().getImportTypes() ); } return imported; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index d013a77a7a..9baa84dc77 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -45,7 +45,7 @@ public TargetReference getTargetReference() { public MappingReference popTargetReference() { if ( targetReference != null ) { TargetReference newTargetReference = targetReference.pop(); - if (newTargetReference != null ) { + if ( newTargetReference != null ) { return new MappingReference(mapping, newTargetReference, sourceReference ); } } @@ -55,7 +55,7 @@ public MappingReference popTargetReference() { public MappingReference popSourceReference() { if ( sourceReference != null ) { SourceReference newSourceReference = sourceReference.pop(); - if (newSourceReference != null ) { + if ( newSourceReference != null ) { return new MappingReference(mapping, targetReference, newSourceReference ); } } @@ -76,7 +76,7 @@ public boolean equals(Object o) { return false; } - if (!Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { + if ( !Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { return false; } @@ -97,7 +97,7 @@ public boolean equals(Object o) { } - if (!Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { + if ( !Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { return false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java index ced945bb90..edc5b3157e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -109,7 +109,7 @@ public boolean hasNestedTargetReferences() { for ( MappingReference mappingRef : mappingReferences ) { TargetReference targetReference = mappingRef.getTargetReference(); - if ( targetReference.isNested()) { + if ( targetReference.isNested() ) { return true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index 9300f683ce..f399c46dc9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -108,7 +108,7 @@ else if ( typeUtils.isSameType( builder.getTypeMirror(), builderCreationOwner ) // When the builderCreationMethod is constructor, its return type is Void. In this case the // builder type should be the owner type. - if (builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR) { + if ( builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR ) { builder = owner; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6287403ba3..8f512c3ace 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -285,7 +285,7 @@ public String createReferenceName() { return name; } - if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null) { + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { return nameWithTopLevelTypeName; } @@ -1346,7 +1346,7 @@ public String describe() { private String getNameKeepingInnerClasses() { String packageNamePrefix = getPackageName() + "."; String fullyQualifiedName = getFullyQualifiedName(); - if (fullyQualifiedName.startsWith( packageNamePrefix ) ) { + if ( fullyQualifiedName.startsWith( packageNamePrefix ) ) { return fullyQualifiedName.substring( packageNamePrefix.length() ); } return fullyQualifiedName; @@ -1665,7 +1665,7 @@ else if ( typeToMatch.hasSuperBound() && parameterized.getSuperBound() != null return new ResolvedPair( typeFactory.getType( parameterized ), declared ); } } - else if (parameterized.getSuperBound() != null ) { + else if ( parameterized.getSuperBound() != null ) { ResolvedPair match = visit( parameterized.getSuperBound(), declared ); if ( match.match != null ) { return new ResolvedPair( typeFactory.getType( parameterized ), declared ); @@ -1897,7 +1897,7 @@ public List getPermittedSubclasses() { if ( kotlinMetadata != null ) { return kotlinMetadata.getPermittedSubclasses(); } - if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { + if ( SEALED_PERMITTED_SUBCLASSES_METHOD == null ) { return emptyList(); } try { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index 606bbce69d..37c38ab590 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -294,7 +294,7 @@ else if ( mirror.getKind() == TypeKind.ARRAY ) { packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); qualifiedName = componentTypeElement.getQualifiedName().toString() + arraySuffix; } - else if (componentTypeMirror.getKind().isPrimitive()) { + else if ( componentTypeMirror.getKind().isPrimitive() ) { // When the component type is primitive and is annotated with ElementType.TYPE_USE then // the typeMirror#toString returns (@CustomAnnotation :: byte) for the javac compiler name = NativeTypes.getName( componentTypeMirror.getKind() ) + builder.toString(); @@ -497,7 +497,7 @@ public List getThrownTypes(ExecutableType method) { } public List getThrownTypes(Accessor accessor) { - if (accessor.getAccessorType().isFieldAssignment()) { + if ( accessor.getAccessorType().isFieldAssignment() ) { return new ArrayList<>(); } Element element = accessor.getElement(); @@ -526,7 +526,7 @@ private List getTypeParameters(TypeMirror mirror, boolean isImplementation List typeArguments = declaredType.getTypeArguments(); List typeParameters = new ArrayList<>( typeArguments.size() ); - for ( TypeMirror typeParameter : typeArguments) { + for ( TypeMirror typeParameter : typeArguments ) { Type type = getType( typeParameter ); typeParameters.add( isImplementationType ? type.getTypeBound() : type ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 08ac1a683a..ea62ee3657 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -246,11 +246,11 @@ private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, Sourc } private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) { - if (templateMethod.getParameters().size() != sourceMethod.getParameters().size()) { + if ( templateMethod.getParameters().size() != sourceMethod.getParameters().size() ) { return false; } for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) { - if (!templateMethod.getParameters().get( i ).getType().equals( + if ( !templateMethod.getParameters().get( i ).getType().equals( sourceMethod.getParameters().get( i ).getType() ) ) { return false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 3d8bfea135..c62012472c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -165,7 +165,7 @@ private boolean lineUp() { // Represent result as map. Map resolvedPairs = new HashMap<>(); for ( TypeVarCandidate candidate : methodParCandidates.values() ) { - for ( Type.ResolvedPair pair : candidate.pairs) { + for ( Type.ResolvedPair pair : candidate.pairs ) { resolvedPairs.put( pair.getParameter(), pair.getMatch() ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java index df5cd848a5..d5595ef6b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java @@ -28,7 +28,7 @@ class JakartaXmlElementDeclSelector extends XmlElementDeclSelector { XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } @@ -39,7 +39,7 @@ XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementRefInfo getXmlElementRefInfo(Element element) { XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java index 1d02e97e90..912da3e696 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java @@ -28,7 +28,7 @@ class JavaxXmlElementDeclSelector extends XmlElementDeclSelector { XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } @@ -39,7 +39,7 @@ XmlElementDeclInfo getXmlElementDeclInfo(Element element) { XmlElementRefInfo getXmlElementRefInfo(Element element) { XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); - if (gem == null) { + if ( gem == null ) { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 87ec3b4dc1..88e302eba0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -23,7 +23,7 @@ public List> getMatchingMethods(List