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

View File

@ -12,7 +12,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.BiFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
@ -23,7 +23,7 @@ import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.util.accessor.Accessor; 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 org.mapstruct.ap.internal.util.accessor.ReadAccessor;
import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.first;
@ -64,7 +64,7 @@ public class Filters {
public List<ReadAccessor> getterMethodsIn(List<ExecutableElement> elements) { public List<ReadAccessor> getterMethodsIn(List<ExecutableElement> elements) {
return elements.stream() return elements.stream()
.filter( accessorNaming::isGetterMethod ) .filter( accessorNaming::isGetterMethod )
.map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) )
.collect( Collectors.toCollection( LinkedList::new ) ); .collect( Collectors.toCollection( LinkedList::new ) );
} }
@ -90,7 +90,10 @@ public class Filters {
for ( Element recordComponent : recordComponents ) { for ( Element recordComponent : recordComponents ) {
recordAccessors.put( recordAccessors.put(
recordComponent.getSimpleName().toString(), 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(); 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() return accessors.stream()
.filter( Fields::isFieldAccessor ) .filter( Fields::isFieldAccessor )
.map( creator ) .map( variableElement -> creator.apply( variableElement, getWithinContext( variableElement ) ) )
.collect( Collectors.toCollection( LinkedList::new ) ); .collect( Collectors.toCollection( LinkedList::new ) );
} }
@ -117,7 +120,7 @@ public class Filters {
public List<Accessor> setterMethodsIn(List<ExecutableElement> elements) { public List<Accessor> setterMethodsIn(List<ExecutableElement> elements) {
return elements.stream() return elements.stream()
.filter( accessorNaming::isSetterMethod ) .filter( accessorNaming::isSetterMethod )
.map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), SETTER ) ) .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) )
.collect( Collectors.toCollection( LinkedList::new ) ); .collect( Collectors.toCollection( LinkedList::new ) );
} }
@ -129,10 +132,14 @@ public class Filters {
return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); 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) { public List<Accessor> adderMethodsIn(List<ExecutableElement> elements) {
return elements.stream() return elements.stream()
.filter( accessorNaming::isAdderMethod ) .filter( accessorNaming::isAdderMethod )
.map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), ADDER ) ) .map( method -> new ElementAccessor( method, getFirstParameter( method ), ADDER ) )
.collect( Collectors.toCollection( LinkedList::new ) ); .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; package org.mapstruct.ap.internal.util.accessor;
import java.util.Set;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; 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 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 AccessorType accessorType;
private final TypeMirror accessedType;
public ElementAccessor(VariableElement variableElement) { public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) {
this( variableElement, AccessorType.FIELD ); this( variableElement, accessedType, AccessorType.FIELD );
} }
public ElementAccessor(Element element, AccessorType accessorType) { public ElementAccessor(Element element, TypeMirror accessedType, String name) {
super( element ); 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.accessorType = accessorType;
this.name = null;
} }
@Override @Override
public TypeMirror getAccessedType() { 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 @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(); String getReadValueSource();
static ReadAccessor fromField(VariableElement variableElement) { static ReadAccessor fromField(VariableElement variableElement, TypeMirror accessedType) {
return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { return new ReadDelegateAccessor( new ElementAccessor( variableElement, accessedType ) ) {
@Override @Override
public String getReadValueSource() { public String getReadValueSource() {
return getSimpleName(); return getSimpleName();
@ -26,8 +26,8 @@ public interface ReadAccessor extends Accessor {
}; };
} }
static ReadAccessor fromRecordComponent(Element element) { static ReadAccessor fromRecordComponent(Element element, TypeMirror accessedType) {
return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) {
@Override @Override
public String getReadValueSource() { public String getReadValueSource() {
return getSimpleName() + "()"; return getSimpleName() + "()";
@ -36,7 +36,7 @@ public interface ReadAccessor extends Accessor {
} }
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 ElementAccessor( element, accessedType, AccessorType.GETTER ) ) {
@Override @Override
public String getReadValueSource() { public String getReadValueSource() {
return getSimpleName() + "()"; 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" );
}
}