diff --git a/.travis.yml b/.travis.yml
index 6a370d1b4..b86d33d31 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,9 @@ matrix:
- jdk: openjdk13
# There is an issue with the documentation so skip it
script: mvn -B -V clean install -DskipDistribution=true
+ - jdk: openjdk14
+ # There is an issue with the documentation so skip it
+ script: mvn -B -V clean install -DskipDistribution=true
# There is an issue with the documentation so skip it
- jdk: openjdk-ea
script: mvn -B -V clean install -DskipDistribution=true
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 16298179a..e579f031f 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,8 @@
*/
package org.mapstruct.itest.tests;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.mapstruct.itest.testutil.extension.ProcessorTest;
@@ -99,6 +101,13 @@ public class MavenIntegrationTest {
void protobufBuilderTest() {
}
+ @ProcessorTest(baseDir = "recordsTest", processorTypes = {
+ ProcessorTest.ProcessorType.JAVAC
+ })
+ @EnabledForJreRange(min = JRE.JAVA_14)
+ void recordsTest() {
+ }
+
@ProcessorTest(baseDir = "simpleTest")
void simpleTest() {
}
diff --git a/integrationtest/src/test/resources/recordsTest/pom.xml b/integrationtest/src/test/resources/recordsTest/pom.xml
new file mode 100644
index 000000000..c74469a7b
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+ 4.0.0
+
+
+ org.mapstruct
+ mapstruct-it-parent
+ 1.0.0
+ ../pom.xml
+
+
+ recordsTest
+ jar
+
+
+
+ generate-via-compiler-plugin
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ \${compiler-id}
+ --enable-preview
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-jdt
+ ${org.eclipse.tycho.compiler-jdt.version}
+
+
+
+
+
+
+
+ ${project.groupId}
+ mapstruct-processor
+ ${mapstruct.version}
+ provided
+
+
+
+
+ debug-forked-javac
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+
+ true
+
+ --enable-preview
+ -J-Xdebug
+ -J-Xnoagent
+ -J-Djava.compiler=NONE
+ -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 1
+ --enable-preview
+
+
+
+
+
+
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerDto.java
new file mode 100644
index 000000000..69b2547bc
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerDto.java
@@ -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.itest.records;
+
+/**
+ * @author Filip Hrisafov
+ */
+public record CustomerDto(String name, String email) {
+
+}
diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerEntity.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerEntity.java
new file mode 100644
index 000000000..1663bbac5
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/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.itest.records;
+
+/**
+ * @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/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.java
new file mode 100644
index 000000000..0e8a0d256
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CustomerMapper.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.itest.records;
+
+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 fromRecord(CustomerDto record);
+
+}
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
new file mode 100644
index 000000000..5a7a62f78
--- /dev/null
+++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.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.itest.records;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.mapstruct.itest.records.CustomerDto;
+import org.mapstruct.itest.records.CustomerEntity;
+import org.mapstruct.itest.records.CustomerMapper;
+
+public class RecordsTest {
+
+ @Test
+ public void shouldMapRecord() {
+ CustomerEntity customer = CustomerMapper.INSTANCE.fromRecord( new CustomerDto( "Kermit", "kermit@test.com" ) );
+
+ assertThat( customer ).isNotNull();
+ assertThat( customer.getName() ).isEqualTo( "Kermit" );
+ assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" );
+ }
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index 9b920c212..83a59bf2c 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -510,7 +510,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.3
+ 0.8.5
org.jvnet.jaxb2.maven2
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 fe9d5e7e9..6f3a7b79c 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
@@ -498,6 +498,11 @@ public class Type extends ModelElement implements Comparable {
}
}
+ Map recordAccessors = filters.recordsIn( typeElement );
+ for ( Map.Entry recordEntry : recordAccessors.entrySet() ) {
+ modifiableGetters.putIfAbsent( recordEntry.getKey(), recordEntry.getValue() );
+ }
+
List fieldsList = filters.fieldsIn( getAllFields() );
for ( Accessor field : fieldsList ) {
String propertyName = getPropertyName( field );
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 b673b3f16..6981a1ebb 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
@@ -5,11 +5,16 @@
*/
package org.mapstruct.ap.internal.util;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
@@ -30,9 +35,29 @@ import static org.mapstruct.ap.internal.util.accessor.AccessorType.SETTER;
* Filter methods for working with {@link Element} collections.
*
* @author Gunnar Morling
+ * @author Filip Hrisafov
*/
public class Filters {
+ private static final Method RECORD_COMPONENTS_METHOD;
+ private static final Method RECORD_COMPONENT_ACCESSOR_METHOD;
+
+ static {
+ Method recordComponentsMethod;
+ Method recordComponentAccessorMethod;
+ try {
+ recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" );
+ recordComponentAccessorMethod = Class.forName( "javax.lang.model.element.RecordComponentElement" )
+ .getMethod( "getAccessor" );
+ }
+ catch ( NoSuchMethodException | ClassNotFoundException e ) {
+ recordComponentsMethod = null;
+ recordComponentAccessorMethod = null;
+ }
+ RECORD_COMPONENTS_METHOD = recordComponentsMethod;
+ RECORD_COMPONENT_ACCESSOR_METHOD = recordComponentAccessorMethod;
+ }
+
private final AccessorNamingUtils accessorNaming;
private final Types typeUtils;
private final TypeMirror typeMirror;
@@ -50,6 +75,33 @@ public class Filters {
.collect( Collectors.toCollection( LinkedList::new ) );
}
+ public Map recordsIn(TypeElement typeElement) {
+ if ( RECORD_COMPONENTS_METHOD == null || RECORD_COMPONENT_ACCESSOR_METHOD == null ) {
+ return java.util.Collections.emptyMap();
+ }
+ try {
+ @SuppressWarnings("unchecked")
+ List recordComponents = (List) RECORD_COMPONENTS_METHOD.invoke( typeElement );
+ Map recordAccessors = new LinkedHashMap<>();
+ for ( Element recordComponent : recordComponents ) {
+ ExecutableElement recordExecutableElement =
+ (ExecutableElement) RECORD_COMPONENT_ACCESSOR_METHOD.invoke( recordComponent );
+ recordAccessors.put(
+ recordComponent.getSimpleName().toString(),
+ new ExecutableElementAccessor( recordExecutableElement,
+ getReturnType( recordExecutableElement ),
+ GETTER
+ )
+ );
+ }
+
+ return recordAccessors;
+ }
+ catch ( IllegalAccessException | InvocationTargetException e ) {
+ return java.util.Collections.emptyMap();
+ }
+ }
+
private TypeMirror getReturnType(ExecutableElement executableElement) {
return getWithinContext( executableElement ).getReturnType();
}