#3807: Properly recognize the type of public generic fields

Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
Yang Tang 2025-05-31 19:29:39 +08:00 committed by GitHub
parent 8fc97f5f62
commit bff88297e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 140 additions and 191 deletions

View File

@ -65,7 +65,7 @@ import org.mapstruct.ap.internal.util.Message;
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 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
existingVariableNames
);
existingVariableNames.add( safeParameterName );
return new ParameterElementAccessor( element, accessedType, safeParameterName );
return new ElementAccessor( element, accessedType, safeParameterName );
}
private boolean hasDefaultAnnotationFromAnyPackage(Element element) {

View File

@ -12,7 +12,7 @@ import java.util.LinkedHashMap;
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.ExecutableType;
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;
@ -90,7 +90,10 @@ public class Filters {
for ( Element recordComponent : recordComponents ) {
recordAccessors.put(
recordComponent.getSimpleName().toString(),
ReadAccessor.fromRecordComponent( recordComponent )
ReadAccessor.fromRecordComponent(
recordComponent,
typeUtils.asMemberOf( (DeclaredType) typeMirror, recordComponent )
)
);
}
@ -101,10 +104,10 @@ public class Filters {
return getWithinContext( executableElement ).getReturnType();
}
public <T> List<T> fieldsIn(List<VariableElement> accessors, Function<VariableElement, T> creator) {
public <T> List<T> fieldsIn(List<VariableElement> accessors, BiFunction<VariableElement, TypeMirror, T> 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 class Filters {
public List<Accessor> setterMethodsIn(List<ExecutableElement> 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 @@ public class Filters {
return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement );
}
private TypeMirror getWithinContext( VariableElement variableElement ) {
return typeUtils.asMemberOf( (DeclaredType) typeMirror, variableElement );
}
public List<Accessor> adderMethodsIn(List<ExecutableElement> 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 ) );
}
}

View File

@ -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<T extends Element> implements Accessor {
protected final T element;
AbstractAccessor(T element) {
this.element = element;
}
@Override
public String getSimpleName() {
return element.getSimpleName().toString();
}
@Override
public Set<Modifier> getModifiers() {
return element.getModifiers();
}
@Override
public T getElement() {
return element;
}
}

View File

@ -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<Element> {
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) {
this( variableElement, AccessorType.FIELD );
public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) {
this( variableElement, accessedType, AccessorType.FIELD );
}
public ElementAccessor(Element element, AccessorType accessorType) {
super( element );
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, 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<Modifier> getModifiers() {
return element.getModifiers();
}
@Override
public Element getElement() {
return element;
}
@Override

View File

@ -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<ExecutableElement> {
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;
}
}

View File

@ -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<Element> {
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;
}
}

View File

@ -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 interface ReadAccessor extends Accessor {
};
}
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 interface ReadAccessor extends Accessor {
}
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() + "()";

View File

@ -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<Element> {
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;
}
}

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.test.bugs._3807;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface Issue3807Mapper {
Issue3807Mapper INSTANCE = Mappers.getMapper( Issue3807Mapper.class );
TargetWithoutSetter<String> mapNoSetter(Source target);
NormalTarget<String> 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<T> {
public T value;
}
//CHECKSTYLE:ON
class NormalTarget<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
}

View File

@ -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<String> targetWithoutSetter =
Issue3807Mapper.INSTANCE.mapNoSetter( source );
assertThat( targetWithoutSetter ).isNotNull();
assertThat( targetWithoutSetter.value ).isEqualTo( "value" );
Issue3807Mapper.NormalTarget<String> normalTarget = Issue3807Mapper.INSTANCE.mapNormalSource( source );
assertThat( normalTarget ).isNotNull();
assertThat( normalTarget.getValue() ).isEqualTo( "value" );
}
}