#3661 Use correct type for the Record component read accessors

This commit is contained in:
Filip Hrisafov 2024-09-14 01:17:45 +02:00 committed by GitHub
parent 12c9c6c1f0
commit 2686e852b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 265 additions and 29 deletions

View File

@ -131,6 +131,13 @@ public class MavenIntegrationTest {
void recordsCrossModuleTest() { void recordsCrossModuleTest() {
} }
@ProcessorTest(baseDir = "recordsCrossModuleInterfaceTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC
})
@EnabledForJreRange(min = JRE.JAVA_17)
void recordsCrossModuleInterfaceTest() {
}
@ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = { @ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = {
ProcessorTest.ProcessorType.JAVAC ProcessorTest.ProcessorType.JAVAC
}) })

View File

@ -0,0 +1,22 @@
<?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>
<artifactId>recordsCrossModuleInterfaceTest</artifactId>
<groupId>org.mapstruct</groupId>
<version>1.0.0</version>
</parent>
<artifactId>records-cross-module-1</artifactId>
</project>

View File

@ -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.itest.records.module1;
public interface NestedInterface {
String field();
}

View File

@ -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.itest.records.module1;
public interface RootInterface {
NestedInterface nested();
}

View File

@ -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.itest.records.module1;
public record SourceNestedRecord(
String field
) implements NestedInterface {
}

View File

@ -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.itest.records.module1;
public record SourceRootRecord(
SourceNestedRecord nested
) implements RootInterface {
}

View File

@ -0,0 +1,30 @@
<?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>
<artifactId>recordsCrossModuleInterfaceTest</artifactId>
<groupId>org.mapstruct</groupId>
<version>1.0.0</version>
</parent>
<artifactId>records-cross-module-2</artifactId>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>records-cross-module-1</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>

View File

@ -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.itest.records.module2;
import org.mapstruct.itest.records.module1.SourceRootRecord;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface RecordInterfaceIssueMapper {
RecordInterfaceIssueMapper INSTANCE = Mappers.getMapper(RecordInterfaceIssueMapper.class);
TargetRootRecord map(SourceRootRecord source);
}

View File

@ -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.itest.records.module2;
public record TargetNestedRecord(
String field
) {
}

View File

@ -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.itest.records.module2;
public record TargetRootRecord(
TargetNestedRecord nested
) {
}

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.itest.records.module2;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.mapstruct.itest.records.module1.SourceRootRecord;
import org.mapstruct.itest.records.module1.SourceNestedRecord;
public class RecordsTest {
@Test
public void shouldMap() {
SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) );
TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source );
assertThat( target ).isNotNull();
assertThat( target.nested() ).isNotNull();
assertThat( target.nested().field() ).isEqualTo( "test" );
}
}

View File

@ -0,0 +1,26 @@
<?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>recordsCrossModuleInterfaceTest</artifactId>
<packaging>pom</packaging>
<modules>
<module>module-1</module>
<module>module-2</module>
</modules>
</project>

View File

@ -50,7 +50,7 @@ import org.mapstruct.ap.internal.util.Nouns;
import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.TypeUtils;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.AccessorType; import org.mapstruct.ap.internal.util.accessor.AccessorType;
import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; import org.mapstruct.ap.internal.util.accessor.ElementAccessor;
import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.MapValueAccessor;
import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor;
import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor;
@ -1047,7 +1047,7 @@ public class Type extends ModelElement implements Comparable<Type> {
List<Accessor> setterMethods = getSetters(); List<Accessor> setterMethods = getSetters();
List<Accessor> readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); List<Accessor> readAccessors = new ArrayList<>( getPropertyReadAccessors().values() );
// All the fields are also alternative accessors // All the fields are also alternative accessors
readAccessors.addAll( filters.fieldsIn( getAllFields(), FieldElementAccessor::new ) ); readAccessors.addAll( filters.fieldsIn( getAllFields(), ElementAccessor::new ) );
// there could be a read accessor (field or method) for a list/map that is not present as setter. // there could be a read accessor (field or method) for a list/map that is not present as setter.
// an accessor could substitute the setter in that case and act as setter. // an accessor could substitute the setter in that case and act as setter.

View File

@ -39,22 +39,16 @@ import static org.mapstruct.ap.internal.util.accessor.AccessorType.SETTER;
public class Filters { public class Filters {
private static final Method RECORD_COMPONENTS_METHOD; private static final Method RECORD_COMPONENTS_METHOD;
private static final Method RECORD_COMPONENT_ACCESSOR_METHOD;
static { static {
Method recordComponentsMethod; Method recordComponentsMethod;
Method recordComponentAccessorMethod;
try { try {
recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" ); recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" );
recordComponentAccessorMethod = Class.forName( "javax.lang.model.element.RecordComponentElement" )
.getMethod( "getAccessor" );
} }
catch ( NoSuchMethodException | ClassNotFoundException e ) { catch ( NoSuchMethodException e ) {
recordComponentsMethod = null; recordComponentsMethod = null;
recordComponentAccessorMethod = null;
} }
RECORD_COMPONENTS_METHOD = recordComponentsMethod; RECORD_COMPONENTS_METHOD = recordComponentsMethod;
RECORD_COMPONENT_ACCESSOR_METHOD = recordComponentAccessorMethod;
} }
private final AccessorNamingUtils accessorNaming; private final AccessorNamingUtils accessorNaming;
@ -89,26 +83,19 @@ public class Filters {
} }
public Map<String, ReadAccessor> recordAccessorsIn(Collection<Element> recordComponents) { public Map<String, ReadAccessor> recordAccessorsIn(Collection<Element> recordComponents) {
if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) { if ( recordComponents.isEmpty() ) {
return java.util.Collections.emptyMap(); return java.util.Collections.emptyMap();
} }
try {
Map<String, ReadAccessor> recordAccessors = new LinkedHashMap<>(); Map<String, ReadAccessor> recordAccessors = new LinkedHashMap<>();
for ( Element recordComponent : recordComponents ) { for ( Element recordComponent : recordComponents ) {
ExecutableElement recordExecutableElement =
(ExecutableElement) RECORD_COMPONENT_ACCESSOR_METHOD.invoke( recordComponent );
recordAccessors.put( recordAccessors.put(
recordComponent.getSimpleName().toString(), recordComponent.getSimpleName().toString(),
ReadAccessor.fromGetter( recordExecutableElement, getReturnType( recordExecutableElement ) ) ReadAccessor.fromRecordComponent( recordComponent )
); );
} }
return recordAccessors; return recordAccessors;
} }
catch ( IllegalAccessException | InvocationTargetException e ) {
return java.util.Collections.emptyMap();
}
}
private TypeMirror getReturnType(ExecutableElement executableElement) { private TypeMirror getReturnType(ExecutableElement executableElement) {
return getWithinContext( executableElement ).getReturnType(); return getWithinContext( executableElement ).getReturnType();

View File

@ -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.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 ElementAccessor extends AbstractAccessor<Element> {
private final AccessorType accessorType;
public ElementAccessor(VariableElement variableElement) {
this( variableElement, AccessorType.FIELD );
}
public ElementAccessor(Element element, AccessorType accessorType) {
super( element );
this.accessorType = accessorType;
}
@Override
public TypeMirror getAccessedType() {
return element.asType();
}
@Override
public String toString() {
return element.toString();
}
@Override
public AccessorType getAccessorType() {
return accessorType;
}
}

View File

@ -5,6 +5,7 @@
*/ */
package org.mapstruct.ap.internal.util.accessor; package org.mapstruct.ap.internal.util.accessor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
@ -17,7 +18,7 @@ public interface ReadAccessor extends Accessor {
String getReadValueSource(); String getReadValueSource();
static ReadAccessor fromField(VariableElement variableElement) { static ReadAccessor fromField(VariableElement variableElement) {
return new ReadDelegateAccessor( new FieldElementAccessor( variableElement ) ) { return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) {
@Override @Override
public String getReadValueSource() { public String getReadValueSource() {
return getSimpleName(); return getSimpleName();
@ -25,6 +26,15 @@ public interface ReadAccessor extends Accessor {
}; };
} }
static ReadAccessor fromRecordComponent(Element element) {
return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) {
@Override
public String getReadValueSource() {
return getSimpleName() + "()";
}
};
}
static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) {
return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) {
@Override @Override

View File

@ -5,6 +5,7 @@
*/ */
package org.mapstruct.ap.internal.util.accessor; package org.mapstruct.ap.internal.util.accessor;
import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
@ -13,9 +14,9 @@ import javax.lang.model.type.TypeMirror;
* *
* @author Filip Hrisafov * @author Filip Hrisafov
*/ */
public class FieldElementAccessor extends AbstractAccessor<VariableElement> { public class RecordElementAccessor extends AbstractAccessor<Element> {
public FieldElementAccessor(VariableElement element) { public RecordElementAccessor(Element element) {
super( element ); super( element );
} }
@ -31,7 +32,7 @@ public class FieldElementAccessor extends AbstractAccessor<VariableElement> {
@Override @Override
public AccessorType getAccessorType() { public AccessorType getAccessorType() {
return AccessorType.FIELD; return AccessorType.GETTER;
} }
} }