#2730 Add support for Jakarta XML Binding

This commit is contained in:
Iaroslav Bogdanchikov 2022-09-02 22:04:01 +02:00 committed by GitHub
parent 21069e5a2e
commit bbf63ae177
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1062 additions and 46 deletions

View File

@ -39,6 +39,13 @@
<groupId>org.mapstruct.tools.gem</groupId>
<artifactId>gem-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
@ -187,7 +194,6 @@
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

View File

@ -133,8 +133,8 @@
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</profile>

View File

@ -75,6 +75,10 @@ public class MavenIntegrationTest {
void jaxbTest() {
}
@ProcessorTest(baseDir = "jakartaJaxbTest")
void jakartaJaxbTest() {
}
@ProcessorTest(baseDir = "jsr330Test")
void jsr330Test() {
}

View File

@ -83,6 +83,13 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<profiles>
@ -93,14 +100,16 @@
</activation>
<dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</profile>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-it-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>jakartaJaxbTest</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>xjc</id>
<phase>initialize</phase>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<xjbSources>
<xjbSource>${project.build.resources[0].directory}/binding</xjbSource>
</xjbSources>
<sources>
<source>${project.build.resources[0].directory}/schema</source>
</sources>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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.itest.jakarta.jaxb;
import java.util.List;
/**
* @author Sjaak Derksen
*/
public class OrderDetailsDto {
private String name;
private List<String> description;
private OrderStatusDto status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getDescription() {
return description;
}
public void setDescription(List<String> description) {
this.description = description;
}
public OrderStatusDto getStatus() {
return status;
}
public void setStatus(OrderStatusDto status) {
this.status = status;
}
}

View File

@ -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.itest.jakarta.jaxb;
import java.util.Date;
/**
* @author Sjaak Derksen
*/
public class OrderDto {
private Long orderNumber;
private Date orderDate;
private OrderDetailsDto orderDetails;
private ShippingAddressDto shippingAddress;
public Long getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(Long orderNumber) {
this.orderNumber = orderNumber;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
public OrderDetailsDto getOrderDetails() {
return orderDetails;
}
public void setOrderDetails(OrderDetailsDto orderDetails) {
this.orderDetails = orderDetails;
}
public ShippingAddressDto getShippingAddress() {
return shippingAddress;
}
public void setShippingAddress(ShippingAddressDto shippingAddress) {
this.shippingAddress = shippingAddress;
}
}

View File

@ -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.itest.jakarta.jaxb;
/**
* @author Sjaak Derksen
*/
public enum OrderStatusDto {
ORDERED( "small" ),
PROCESSED( "medium" ),
DELIVERED( "large" );
private final String value;
OrderStatusDto(String v) {
value = v;
}
public String value() {
return value;
}
public static OrderStatusDto fromValue(String v) {
for ( OrderStatusDto c : OrderStatusDto.values() ) {
if ( c.value.equals( v ) ) {
return c;
}
}
throw new IllegalArgumentException( v );
}
}

View File

@ -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.itest.jakarta.jaxb;
/**
* @author Sjaak Derksen
*/
public class ShippingAddressDto {
private String street;
private String houseNumber;
private String city;
private String country;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getHouseNumber() {
return houseNumber;
}
public void setHouseNumber(String houseNumber) {
this.houseNumber = houseNumber;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}

View File

@ -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.itest.jakarta.jaxb;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderDetailsType;
import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType;
import org.mapstruct.itest.jakarta.jaxb.xsd.test2.OrderStatusType;
import org.mapstruct.itest.jakarta.jaxb.xsd.test2.ShippingAddressType;
import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType;
/**
* @author Sjaak Derksen
*/
@Mapper(uses = {
org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory.class,
org.mapstruct.itest.jakarta.jaxb.xsd.test2.ObjectFactory.class,
org.mapstruct.itest.jakarta.jaxb.xsd.underscores.ObjectFactory.class
})
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
// source 2 target methods
OrderDto sourceToTarget(OrderType source);
OrderDetailsDto detailsToDto(OrderDetailsType source);
OrderStatusDto statusToDto(OrderStatusType source);
ShippingAddressDto shippingAddressToDto(ShippingAddressType source);
SubTypeDto subTypeToDto(SubType source);
// target 2 source methods
OrderType targetToSource(OrderDto target);
OrderDetailsType dtoToDetails(OrderDetailsDto target);
OrderStatusType dtoToStatus(OrderStatusDto target);
ShippingAddressType dtoToShippingAddress(ShippingAddressDto source);
SubType dtoToSubType(SubTypeDto source);
}

View File

@ -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.itest.jakarta.jaxb;
public class SubTypeDto extends SuperTypeDto {
private String declaredCamelCase;
private String declaredUnderscore;
public String getDeclaredCamelCase() {
return declaredCamelCase;
}
public void setDeclaredCamelCase(String declaredCamelCase) {
this.declaredCamelCase = declaredCamelCase;
}
public String getDeclaredUnderscore() {
return declaredUnderscore;
}
public void setDeclaredUnderscore(String declaredUnderscore) {
this.declaredUnderscore = declaredUnderscore;
}
}

View File

@ -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.itest.jakarta.jaxb;
public class SuperTypeDto {
private String inheritedCamelCase;
private String inheritedUnderscore;
public String getInheritedCamelCase() {
return inheritedCamelCase;
}
public void setInheritedCamelCase(String inheritedCamelCase) {
this.inheritedCamelCase = inheritedCamelCase;
}
public String getInheritedUnderscore() {
return inheritedUnderscore;
}
public void setInheritedUnderscore(String inheritedUnderscore) {
this.inheritedUnderscore = inheritedUnderscore;
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<jaxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc"
version="3.0" >
<jaxb:globalBindings
fixedAttributeAsConstantProperty="true"
typesafeEnumBase="xs:string"
typesafeEnumMemberName="generateName"
generateIsSetMethod="true"
generateElementProperty="true" >
<xjc:noValidator/>
<xjc:noValidatingUnmarshaller/>
</jaxb:globalBindings>
</jaxb:bindings>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:test1="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test1"
xmlns:test2="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test2"
targetNamespace="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test1"
elementFormDefault="qualified" version="1.0.0">
<import namespace="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test2" schemaLocation="test2.xsd"/>
<element name="Order" type="test1:OrderType" />
<complexType name="OrderType">
<sequence>
<element name="orderNumber" type="long"/>
<element name="orderDate" type="dateTime"/>
<element name="orderDetails" type="test1:OrderDetailsType"/>
<element name="shippingAddress" type="test2:ShippingAddressType"/>
</sequence>
</complexType>
<element name="OrderDetails" type="test1:OrderDetailsType" />
<complexType name="OrderDetailsType">
<sequence>
<element name="name" type="string"/>
<element name="description" minOccurs="1" maxOccurs="5" type="string"/>
<element name="status" type="test2:OrderStatusType"/>
</sequence>
</complexType>
</schema>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:test2="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test2"
targetNamespace="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/test2"
elementFormDefault="qualified" version="1.0.0">
<element name="OrderStatus" type="test2:OrderStatusType" />
<simpleType name="OrderStatusType">
<restriction base="string">
<enumeration value="ordered" />
<enumeration value="processed" />
<enumeration value="delivered" />
</restriction>
</simpleType>
<element name="ShippingAddress" type="test2:ShippingAddressType" />
<complexType name="ShippingAddressType">
<sequence>
<element name="street" type="string"/>
<element name="houseNumber" type="string"/>
<element name="city" type="string"/>
<element name="country" type="string"/>
</sequence>
</complexType>
</schema>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:underscores="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/underscores"
targetNamespace="http://www.mapstruct.org/itest/jakarta/jaxb/xsd/underscores"
elementFormDefault="qualified" version="1.0.0">
<element name="Super" type="underscores:SuperType" />
<complexType name="SuperType">
<sequence>
<element name="inheritedCamelCase" type="string"/>
<element name="inherited_underscore" type="string"/>
</sequence>
</complexType>
<element name="Sub" type="underscores:SubType" />
<complexType name="SubType">
<complexContent>
<extension base="underscores:SuperType">
<sequence>
<element name="declaredCamelCase" type="string"/>
<element name="declared_underscore" type="string"/>
</sequence>
</extension>
</complexContent>
</complexType>
</schema>

View File

@ -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.itest.jakarta.jaxb;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import org.junit.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;
/**
* Test for generation of Jakarta JAXB based mapper implementations.
*
* @author Iaroslav Bogdanchikov
*/
public class JakartaJaxbBasedMapperTest {
@Test
public void shouldMapJakartaJaxb() throws ParseException, JAXBException {
SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
OrderDto source1 = new OrderDto();
source1.setOrderDetails( new OrderDetailsDto() );
source1.setOrderNumber( 11L );
source1.setOrderDate( createDate( "31-08-1982 10:20:56" ) );
source1.setShippingAddress( new ShippingAddressDto() );
source1.getShippingAddress().setCity( "SmallTown" );
source1.getShippingAddress().setHouseNumber( "11a" );
source1.getShippingAddress().setStreet( "Awesome rd" );
source1.getShippingAddress().setCountry( "USA" );
source1.getOrderDetails().setDescription( new ArrayList<String>() );
source1.getOrderDetails().setName( "Shopping list for a Mapper" );
source1.getOrderDetails().getDescription().add( "1 MapStruct" );
source1.getOrderDetails().getDescription().add( "3 Lines of Code" );
source1.getOrderDetails().getDescription().add( "1 Dose of Luck" );
source1.getOrderDetails().setStatus( OrderStatusDto.ORDERED );
// map to JAXB
OrderType target = mapper.targetToSource( source1 );
// do a pretty print
ObjectFactory of = new ObjectFactory();
System.out.println( toXml( of.createOrder( target ) ) );
// map back from JAXB
OrderDto source2 = mapper.sourceToTarget( target );
// verify that source1 and source 2 are equal
assertThat( source2.getOrderNumber() ).isEqualTo( source1.getOrderNumber() );
assertThat( source2.getOrderDate() ).isEqualTo( source1.getOrderDate() );
assertThat( source2.getOrderDetails().getDescription().size() ).isEqualTo(
source1.getOrderDetails().getDescription().size()
);
assertThat( source2.getOrderDetails().getDescription().get( 0 ) ).isEqualTo(
source1.getOrderDetails().getDescription().get( 0 )
);
assertThat( source2.getOrderDetails().getDescription().get( 1 ) ).isEqualTo(
source1.getOrderDetails().getDescription().get( 1 )
);
assertThat( source2.getOrderDetails().getDescription().get( 2 ) ).isEqualTo(
source1.getOrderDetails().getDescription().get( 2 )
);
assertThat( source2.getOrderDetails().getName() ).isEqualTo( source1.getOrderDetails().getName() );
assertThat( source2.getOrderDetails().getStatus() ).isEqualTo( source1.getOrderDetails().getStatus() );
}
@Test
public void underscores() throws ParseException, JAXBException {
SourceTargetMapper mapper = SourceTargetMapper.INSTANCE;
SubTypeDto source1 = new SubTypeDto();
source1.setInheritedCamelCase("InheritedCamelCase");
source1.setInheritedUnderscore("InheritedUnderscore");
source1.setDeclaredCamelCase("DeclaredCamelCase");
source1.setDeclaredUnderscore("DeclaredUnderscore");
SubType target = mapper.dtoToSubType( source1 );
SubTypeDto source2 = mapper.subTypeToDto( target );
assertThat( source2.getInheritedCamelCase() ).isEqualTo( source1.getInheritedCamelCase() );
assertThat( source2.getInheritedUnderscore() ).isEqualTo( source1.getInheritedUnderscore() );
assertThat( source2.getDeclaredCamelCase() ).isEqualTo( source1.getDeclaredCamelCase() );
assertThat( source2.getDeclaredUnderscore() ).isEqualTo( source1.getDeclaredUnderscore() );
}
private Date createDate(String date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" );
return sdf.parse( date );
}
private String toXml(JAXBElement<?> element) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance( element.getValue().getClass() );
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
marshaller.marshal( element, baos );
return baos.toString();
}
}

View File

@ -51,7 +51,7 @@
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
<version>${jaxb-runtime.version}</version>
</dependency>
</dependencies>
</plugin>
@ -66,14 +66,16 @@
</activation>
<dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</profile>

View File

@ -42,6 +42,7 @@
-->
<minimum.java.version>1.8</minimum.java.version>
<protobuf.version>3.21.2</protobuf.version>
<jaxb-runtime.version>2.3.2</jaxb-runtime.version>
</properties>
<licenses>
@ -255,6 +256,30 @@
<version>2.9</version>
</dependency>
<!-- XML Binding -->
<!-- Old (javax) -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>${jaxb-runtime.version}</version>
</dependency>
<!-- New (jakarta) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Plexus Eclipse Compiler -->
<dependency>
<groupId>org.eclipse.tycho</groupId>
@ -488,12 +513,12 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.17</version>
<version>1.20</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2.1</version>
<version>7.0</version>
</dependency>
</dependencies>
</plugin>

View File

@ -137,6 +137,13 @@
<artifactId>joda-time</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
@ -375,7 +382,6 @@
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.gem.jakarta;
import jakarta.xml.bind.annotation.XmlElementDecl;
import jakarta.xml.bind.annotation.XmlElementRef;
import org.mapstruct.tools.gem.GemDefinition;
/**
* This class is a temporary solution to an issue in the Gem Tools library.
*
* <p>
* This class can be merged with {@link org.mapstruct.ap.internal.gem.GemGenerator}
* after the mentioned issue is resolved.
* </p>
*
* @see <a href="https://github.com/mapstruct/tools-gem/issues/10">Gem Tools issue #10</a>
* @author Iaroslav Bogdanchikov
*/
@GemDefinition(XmlElementDecl.class)
@GemDefinition(XmlElementRef.class)
class JakartaGemGenerator {
}

View File

@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.model.source.builtin;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.JaxbConstants;
import org.mapstruct.ap.internal.util.JodaTimeConstants;
@ -24,7 +25,7 @@ public class BuiltInMappingMethods {
public BuiltInMappingMethods(TypeFactory typeFactory) {
boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory );
builtInMethods = new ArrayList<>( 20 );
builtInMethods = new ArrayList<>( 21 );
if ( isXmlGregorianCalendarPresent ) {
builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) );
builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) );
@ -39,8 +40,14 @@ public class BuiltInMappingMethods {
builtInMethods.add( new XmlGregorianCalendarToLocalDateTime( typeFactory ) );
}
if ( isJaxbAvailable( typeFactory ) ) {
builtInMethods.add( new JaxbElemToValue( typeFactory ) );
if ( isJavaxJaxbAvailable( typeFactory ) ) {
Type type = typeFactory.getType( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN );
builtInMethods.add( new JaxbElemToValue( type ) );
}
if ( isJakartaJaxbAvailable( typeFactory ) ) {
Type type = typeFactory.getType( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN );
builtInMethods.add( new JaxbElemToValue( type ) );
}
builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) );
@ -58,8 +65,12 @@ public class BuiltInMappingMethods {
}
}
private static boolean isJaxbAvailable(TypeFactory typeFactory) {
return typeFactory.isTypeAvailable( JaxbConstants.JAXB_ELEMENT_FQN );
private static boolean isJavaxJaxbAvailable(TypeFactory typeFactory) {
return typeFactory.isTypeAvailable( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN );
}
private static boolean isJakartaJaxbAvailable(TypeFactory typeFactory) {
return typeFactory.isTypeAvailable( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN );
}
private static boolean isXmlGregorianCalendarAvailable(TypeFactory typeFactory) {

View File

@ -5,26 +5,23 @@
*/
package org.mapstruct.ap.internal.model.source.builtin;
import static org.mapstruct.ap.internal.util.Collections.asSet;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.JaxbConstants;
import static org.mapstruct.ap.internal.util.Collections.asSet;
/**
* @author Sjaak Derksen
*/
public class JaxbElemToValue extends BuiltInMethod {
class JaxbElemToValue extends BuiltInMethod {
private final Parameter parameter;
private final Type returnType;
private final Set<Type> importTypes;
public JaxbElemToValue(TypeFactory typeFactory) {
Type type = typeFactory.getType( JaxbConstants.JAXB_ELEMENT_FQN );
JaxbElemToValue(Type type) {
this.parameter = new Parameter( "element", type );
this.returnType = type.getTypeParameters().get( 0 );
this.importTypes = asSet( parameter.getType() );

View File

@ -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.internal.model.source.selector;
import javax.lang.model.element.Element;
import org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem;
import org.mapstruct.ap.internal.gem.jakarta.XmlElementRefGem;
import org.mapstruct.ap.internal.util.TypeUtils;
/**
* The concrete implementation of the {@link XmlElementDeclSelector} that
* works with {@link jakarta.xml.bind.annotation.XmlElementRef} and
* {@link jakarta.xml.bind.annotation.XmlElementDecl}.
*
* @author Iaroslav Bogdanchikov
*/
class JakartaXmlElementDeclSelector extends XmlElementDeclSelector {
JakartaXmlElementDeclSelector(TypeUtils typeUtils) {
super( typeUtils );
}
@Override
XmlElementDeclInfo getXmlElementDeclInfo(Element element) {
XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element );
if (gem == null) {
return null;
}
return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() );
}
@Override
XmlElementRefInfo getXmlElementRefInfo(Element element) {
XmlElementRefGem gem = XmlElementRefGem.instanceOn( element );
if (gem == null) {
return null;
}
return new XmlElementRefInfo( gem.name().get(), gem.type().get() );
}
}

View File

@ -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.internal.model.source.selector;
import javax.lang.model.element.Element;
import org.mapstruct.ap.internal.gem.XmlElementDeclGem;
import org.mapstruct.ap.internal.gem.XmlElementRefGem;
import org.mapstruct.ap.internal.util.TypeUtils;
/**
* The concrete implementation of the {@link XmlElementDeclSelector} that
* works with {@link javax.xml.bind.annotation.XmlElementRef} and
* {@link javax.xml.bind.annotation.XmlElementDecl}.
*
* @author Iaroslav Bogdanchikov
*/
class JavaxXmlElementDeclSelector extends XmlElementDeclSelector {
JavaxXmlElementDeclSelector(TypeUtils typeUtils) {
super( typeUtils );
}
@Override
XmlElementDeclInfo getXmlElementDeclInfo(Element element) {
XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element );
if (gem == null) {
return null;
}
return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() );
}
@Override
XmlElementRefInfo getXmlElementRefInfo(Element element) {
XmlElementRefGem gem = XmlElementRefGem.instanceOn( element );
if (gem == null) {
return null;
}
return new XmlElementRefInfo( gem.name().get(), gem.type().get() );
}
}

View File

@ -32,7 +32,8 @@ public class MethodSelectors {
new TypeSelector( typeFactory, messager ),
new QualifierSelector( typeUtils, elementUtils ),
new TargetTypeSelector( typeUtils ),
new XmlElementDeclSelector( typeUtils ),
new JavaxXmlElementDeclSelector( typeUtils ),
new JakartaXmlElementDeclSelector( typeUtils ),
new InheritanceSelector(),
new CreateOrUpdateSelector(),
new SourceRhsSelector(),

View File

@ -11,19 +11,17 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.util.TypeUtils;
import org.mapstruct.ap.internal.gem.XmlElementRefGem;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.gem.XmlElementDeclGem;
import org.mapstruct.ap.internal.util.TypeUtils;
/**
* Finds the {@link javax.xml.bind.annotation.XmlElementRef} annotation on a field (of the mapping result type or its
* Finds the {@code XmlElementRef} annotation on a field (of the mapping result type or its
* super types) matching the
* target property name. Then selects those methods with matching {@code name} and {@code scope} attributes of the
* {@link javax.xml.bind.annotation.XmlElementDecl} annotation, if that is present. Matching happens in the following
* {@code XmlElementDecl} annotation, if that is present. Matching happens in the following
* order:
* <ol>
* <li>Name and Scope matches</li>
@ -34,12 +32,15 @@ import org.mapstruct.ap.internal.gem.XmlElementDeclGem;
* the given method is not annotated with {@code XmlElementDecl} it will be considered as matching.
*
* @author Sjaak Derksen
*
* @see JavaxXmlElementDeclSelector
* @see JakartaXmlElementDeclSelector
*/
public class XmlElementDeclSelector implements MethodSelector {
abstract class XmlElementDeclSelector implements MethodSelector {
private final TypeUtils typeUtils;
public XmlElementDeclSelector(TypeUtils typeUtils) {
XmlElementDeclSelector(TypeUtils typeUtils) {
this.typeUtils = typeUtils;
}
@ -63,15 +64,14 @@ public class XmlElementDeclSelector implements MethodSelector {
}
SourceMethod candidateMethod = (SourceMethod) candidate.getMethod();
XmlElementDeclGem xmlElementDecl =
XmlElementDeclGem.instanceOn( candidateMethod.getExecutable() );
XmlElementDeclInfo xmlElementDeclInfo = getXmlElementDeclInfo( candidateMethod.getExecutable() );
if ( xmlElementDecl == null ) {
if ( xmlElementDeclInfo == null ) {
continue;
}
String name = xmlElementDecl.name().get();
TypeMirror scope = xmlElementDecl.scope().getValue();
String name = xmlElementDeclInfo.nameValue();
TypeMirror scope = xmlElementDeclInfo.scopeType();
boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() );
boolean scopeIsSetAndMatches =
@ -142,9 +142,9 @@ public class XmlElementDeclSelector implements MethodSelector {
for ( Element enclosed : currentElement.getEnclosedElements() ) {
if ( enclosed.getKind().equals( ElementKind.FIELD )
&& enclosed.getSimpleName().contentEquals( targetPropertyName ) ) {
XmlElementRefGem xmlElementRef = XmlElementRefGem.instanceOn( enclosed );
if ( xmlElementRef != null ) {
return new XmlElementRefInfo( xmlElementRef.name().get(), currentMirror );
XmlElementRefInfo xmlElementRefInfo = getXmlElementRefInfo( enclosed );
if ( xmlElementRefInfo != null ) {
return new XmlElementRefInfo( xmlElementRefInfo.nameValue(), currentMirror );
}
}
}
@ -154,7 +154,11 @@ public class XmlElementDeclSelector implements MethodSelector {
return defaultInfo;
}
private static class XmlElementRefInfo {
abstract XmlElementDeclInfo getXmlElementDeclInfo(Element element);
abstract XmlElementRefInfo getXmlElementRefInfo(Element element);
static class XmlElementRefInfo {
private final String nameValue;
private final TypeMirror sourceType;
@ -163,12 +167,37 @@ public class XmlElementDeclSelector implements MethodSelector {
this.sourceType = sourceType;
}
public String nameValue() {
String nameValue() {
return nameValue;
}
public TypeMirror sourceType() {
TypeMirror sourceType() {
return sourceType;
}
}
/**
* A class, whose purpose is to combine the use of
* {@link org.mapstruct.ap.internal.gem.XmlElementDeclGem}
* and
* {@link org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem}.
*/
static class XmlElementDeclInfo {
private final String nameValue;
private final TypeMirror scopeType;
XmlElementDeclInfo(String nameValue, TypeMirror scopeType) {
this.nameValue = nameValue;
this.scopeType = scopeType;
}
String nameValue() {
return nameValue;
}
TypeMirror scopeType() {
return scopeType;
}
}
}

View File

@ -10,7 +10,8 @@ package org.mapstruct.ap.internal.util;
*/
public final class JaxbConstants {
public static final String JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement";
public static final String JAVAX_JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement";
public static final String JAKARTA_JAXB_ELEMENT_FQN = "jakarta.xml.bind.JAXBElement";
private JaxbConstants() {
}

View File

@ -29,6 +29,8 @@ import org.mapstruct.ap.test.builtin._target.MapTarget;
import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty;
import org.mapstruct.ap.test.builtin.bean.CalendarProperty;
import org.mapstruct.ap.test.builtin.bean.DateProperty;
import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty;
import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty;
import org.mapstruct.ap.test.builtin.bean.JaxbElementListProperty;
import org.mapstruct.ap.test.builtin.bean.JaxbElementProperty;
import org.mapstruct.ap.test.builtin.bean.SomeType;
@ -45,6 +47,8 @@ import org.mapstruct.ap.test.builtin.mapper.CalendarToXmlGregCalMapper;
import org.mapstruct.ap.test.builtin.mapper.DateToCalendarMapper;
import org.mapstruct.ap.test.builtin.mapper.DateToXmlGregCalMapper;
import org.mapstruct.ap.test.builtin.mapper.IterableSourceTargetMapper;
import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbListMapper;
import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbMapper;
import org.mapstruct.ap.test.builtin.mapper.JaxbListMapper;
import org.mapstruct.ap.test.builtin.mapper.JaxbMapper;
import org.mapstruct.ap.test.builtin.mapper.MapSourceTargetMapper;
@ -58,6 +62,7 @@ import org.mapstruct.ap.test.builtin.source.MapSource;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithJakartaJaxb;
import org.mapstruct.ap.testutil.WithJavaxJaxb;
import static org.assertj.core.api.Assertions.assertThat;
@ -101,6 +106,23 @@ public class BuiltInTest {
assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" );
}
@ProcessorTest
@WithClasses( {
JakartaJaxbMapper.class,
JakartaJaxbElementProperty.class,
} )
@WithJakartaJaxb
public void shouldApplyBuiltInOnJakartaJaxbElement() {
JakartaJaxbElementProperty source = new JakartaJaxbElementProperty();
source.setProp( createJakartaJaxb( "TEST" ) );
source.publicProp = createJakartaJaxb( "PUBLIC TEST" );
StringProperty target = JakartaJaxbMapper.INSTANCE.map( source );
assertThat( target ).isNotNull();
assertThat( target.getProp() ).isEqualTo( "TEST" );
assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" );
}
@ProcessorTest
@WithClasses( {
JaxbMapper.class,
@ -128,6 +150,33 @@ public class BuiltInTest {
assertThat( target2.getProp() ).isNotNull();
}
@ProcessorTest
@WithClasses( {
JakartaJaxbMapper.class,
JakartaJaxbElementProperty.class,
} )
@WithJakartaJaxb
@IssueKey( "1698" )
public void shouldApplyBuiltInOnJakartaJAXBElementExtra() {
JakartaJaxbElementProperty source = new JakartaJaxbElementProperty();
source.setProp( createJakartaJaxb( "5" ) );
source.publicProp = createJakartaJaxb( "5" );
BigDecimalProperty target = JakartaJaxbMapper.INSTANCE.mapBD( source );
assertThat( target ).isNotNull();
assertThat( target.getProp() ).isEqualTo( new BigDecimal( "5" ) );
assertThat( target.publicProp ).isEqualTo( new BigDecimal( "5" ) );
JakartaJaxbElementProperty source2 = new JakartaJaxbElementProperty();
source2.setProp( createJakartaJaxb( "5" ) );
source2.publicProp = createJakartaJaxb( "5" );
SomeTypeProperty target2 = JakartaJaxbMapper.INSTANCE.mapSomeType( source2 );
assertThat( target2 ).isNotNull();
assertThat( target2.publicProp ).isNotNull();
assertThat( target2.getProp() ).isNotNull();
}
@ProcessorTest
@WithClasses( {
JaxbListMapper.class,
@ -147,6 +196,24 @@ public class BuiltInTest {
assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" );
}
@ProcessorTest
@WithClasses( {
JakartaJaxbListMapper.class,
JakartaJaxbElementListProperty.class,
} )
@WithJakartaJaxb
@IssueKey( "141" )
public void shouldApplyBuiltInOnJakartaJAXBElementList() {
JakartaJaxbElementListProperty source = new JakartaJaxbElementListProperty();
source.setProp( createJakartaJaxbList( "TEST2" ) );
source.publicProp = createJakartaJaxbList( "PUBLIC TEST2" );
StringListProperty target = JakartaJaxbListMapper.INSTANCE.map( source );
assertThat( target ).isNotNull();
assertThat( target.getProp().get( 0 ) ).isEqualTo( "TEST2" );
assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" );
}
@ProcessorTest
@WithClasses( DateToXmlGregCalMapper.class )
public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException {
@ -414,12 +481,22 @@ public class BuiltInTest {
return new JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test );
}
private jakarta.xml.bind.JAXBElement<String> createJakartaJaxb(String test) {
return new jakarta.xml.bind.JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test );
}
private List<JAXBElement<String>> createJaxbList(String test) {
List<JAXBElement<String>> result = new ArrayList<>();
result.add( createJaxb( test ) );
return result;
}
private List<jakarta.xml.bind.JAXBElement<String>> createJakartaJaxbList(String test) {
List<jakarta.xml.bind.JAXBElement<String>> result = new ArrayList<>();
result.add( createJakartaJaxb( test ) );
return result;
}
private Date createDate(String date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" );
return sdf.parse( date );

View File

@ -0,0 +1,28 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.builtin.bean;
import java.util.List;
import jakarta.xml.bind.JAXBElement;
public class JakartaJaxbElementListProperty {
// CHECKSTYLE:OFF
public List<JAXBElement<String>> publicProp;
// CHECKSTYLE:ON
private List<JAXBElement<String>> prop;
public List<JAXBElement<String>> getProp() {
return prop;
}
public void setProp( List<JAXBElement<String>> prop ) {
this.prop = prop;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.builtin.bean;
import jakarta.xml.bind.JAXBElement;
public class JakartaJaxbElementProperty {
// CHECKSTYLE:OFF
public JAXBElement<String> publicProp;
// CHECKSTYLE:ON
private JAXBElement<String> prop;
public JAXBElement<String> getProp() {
return prop;
}
public void setProp( JAXBElement<String> prop ) {
this.prop = prop;
}
}

View File

@ -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.builtin.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty;
import org.mapstruct.ap.test.builtin.bean.StringListProperty;
import org.mapstruct.factory.Mappers;
@Mapper
public interface JakartaJaxbListMapper {
JakartaJaxbListMapper INSTANCE = Mappers.getMapper( JakartaJaxbListMapper.class );
StringListProperty map(JakartaJaxbElementListProperty source);
}

View File

@ -0,0 +1,31 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.builtin.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty;
import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty;
import org.mapstruct.ap.test.builtin.bean.SomeType;
import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty;
import org.mapstruct.ap.test.builtin.bean.StringProperty;
import org.mapstruct.factory.Mappers;
@Mapper
public interface JakartaJaxbMapper {
JakartaJaxbMapper INSTANCE = Mappers.getMapper( JakartaJaxbMapper.class );
StringProperty map(JakartaJaxbElementProperty source);
BigDecimalProperty mapBD(JakartaJaxbElementProperty source);
SomeTypeProperty mapSomeType(JakartaJaxbElementProperty source);
@SuppressWarnings( "unused" )
default SomeType map( String in ) {
return new SomeType();
}
}

View File

@ -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.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 Jakarta XML Binding dependencies.
*
* @author Iaroslav Bogdanchikov
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@WithTestDependency({
"jakarta.xml.bind",
})
public @interface WithJakartaJaxb {
}