mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#2553 Support source property paths for maps
This commit is contained in:
parent
29008e12bf
commit
13bc0c023c
@ -504,7 +504,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
.build();
|
||||
|
||||
Accessor targetPropertyReadAccessor =
|
||||
method.getResultType().getPropertyReadAccessors().get( propertyName );
|
||||
method.getResultType().getReadAccessor( propertyName );
|
||||
MappingReferences mappingRefs = extractMappingReferences( propertyName, true );
|
||||
PropertyMapping propertyMapping = new PropertyMappingBuilder()
|
||||
.mappingContext( ctx )
|
||||
@ -1047,7 +1047,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
}
|
||||
|
||||
Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName );
|
||||
Accessor targetReadAccessor = resultTypeToMap.getPropertyReadAccessors().get( targetPropertyName );
|
||||
Accessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName );
|
||||
|
||||
if ( targetWriteAccessor == null ) {
|
||||
if ( targetReadAccessor == null ) {
|
||||
@ -1389,7 +1389,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
}
|
||||
|
||||
Accessor targetPropertyReadAccessor =
|
||||
method.getResultType().getPropertyReadAccessors().get( targetPropertyName );
|
||||
method.getResultType().getReadAccessor( targetPropertyName );
|
||||
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
|
||||
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
|
||||
.sourceMethod( method )
|
||||
@ -1432,7 +1432,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
.build();
|
||||
|
||||
Accessor targetPropertyReadAccessor =
|
||||
method.getResultType().getPropertyReadAccessors().get( targetProperty.getKey() );
|
||||
method.getResultType().getReadAccessor( targetProperty.getKey() );
|
||||
MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false );
|
||||
PropertyMapping propertyMapping = new PropertyMappingBuilder()
|
||||
.mappingContext( ctx )
|
||||
@ -1473,22 +1473,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
return sourceRef;
|
||||
}
|
||||
|
||||
if ( sourceParameter.getType().isMapType() ) {
|
||||
List<Type> typeParameters = sourceParameter.getType().getTypeParameters();
|
||||
if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) {
|
||||
return SourceReference.fromMapSource(
|
||||
new String[] { targetPropertyName },
|
||||
sourceParameter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Accessor sourceReadAccessor =
|
||||
sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName );
|
||||
Accessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName );
|
||||
if ( sourceReadAccessor != null ) {
|
||||
// property mapping
|
||||
Accessor sourcePresenceChecker =
|
||||
sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName );
|
||||
sourceParameter.getType().getPresenceChecker( targetPropertyName );
|
||||
|
||||
DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror();
|
||||
Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor );
|
||||
|
@ -14,9 +14,12 @@ import org.mapstruct.ap.internal.model.common.Parameter;
|
||||
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry;
|
||||
import org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck;
|
||||
import org.mapstruct.ap.internal.util.Strings;
|
||||
import org.mapstruct.ap.internal.util.ValueProvider;
|
||||
import org.mapstruct.ap.internal.util.accessor.Accessor;
|
||||
import org.mapstruct.ap.internal.util.accessor.AccessorType;
|
||||
|
||||
/**
|
||||
* This method is used to convert the nested properties as listed in propertyEntries into a method
|
||||
@ -165,12 +168,21 @@ public class NestedPropertyMappingMethod extends MappingMethod {
|
||||
public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) {
|
||||
this.safeName = safeName;
|
||||
this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue();
|
||||
if ( entry.getPresenceChecker() != null ) {
|
||||
Accessor presenceChecker = entry.getPresenceChecker();
|
||||
if ( presenceChecker != null ) {
|
||||
if ( presenceChecker.getAccessorType() == AccessorType.MAP_CONTAINS ) {
|
||||
this.presenceChecker = new SourceReferenceContainsKeyPresenceCheck(
|
||||
previousPropertyName,
|
||||
presenceChecker.getSimpleName()
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.presenceChecker = new SourceReferenceMethodPresenceCheck(
|
||||
previousPropertyName,
|
||||
entry.getPresenceChecker().getSimpleName()
|
||||
presenceChecker.getSimpleName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.presenceChecker = null;
|
||||
}
|
||||
|
@ -642,7 +642,7 @@ public class NestedTargetPropertyMappingHolder {
|
||||
boolean forceUpdateMethod) {
|
||||
|
||||
Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName );
|
||||
Accessor targetReadAccessor = targetType.getPropertyReadAccessors().get( targetPropertyName );
|
||||
Accessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName );
|
||||
if ( targetWriteAccessor == null ) {
|
||||
Set<String> readAccessors = targetType.getPropertyReadAccessors().keySet();
|
||||
String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors );
|
||||
|
@ -7,16 +7,13 @@ package org.mapstruct.ap.internal.model.beanmapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.Parameter;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
@ -27,8 +24,6 @@ import org.mapstruct.ap.internal.util.FormattingMessager;
|
||||
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.MapValueAccessor;
|
||||
import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker;
|
||||
|
||||
import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference;
|
||||
import static org.mapstruct.ap.internal.util.Collections.last;
|
||||
@ -51,32 +46,13 @@ import static org.mapstruct.ap.internal.util.Collections.last;
|
||||
* <li>{@code propertyEntries[1]} will describe {@code propB}</li>
|
||||
* </ul>
|
||||
*
|
||||
* After building, {@link #isValid()} will return true when when no problems are detected during building.
|
||||
* After building, {@link #isValid()} will return true when no problems are detected during building.
|
||||
*
|
||||
* @author Sjaak Derksen
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class SourceReference extends AbstractReference {
|
||||
|
||||
public static SourceReference fromMapSource(String[] segments, Parameter parameter) {
|
||||
Type parameterType = parameter.getType();
|
||||
Type valueType = parameterType.getTypeParameters().get( 1 );
|
||||
|
||||
TypeElement typeElement = parameterType.getTypeElement();
|
||||
TypeMirror typeMirror = valueType.getTypeMirror();
|
||||
String simpleName = String.join( ".", segments );
|
||||
|
||||
MapValueAccessor mapValueAccessor = new MapValueAccessor( typeElement, typeMirror, simpleName );
|
||||
MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker(
|
||||
typeElement,
|
||||
typeMirror,
|
||||
simpleName
|
||||
);
|
||||
List<PropertyEntry> entries = Collections.singletonList(
|
||||
PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType )
|
||||
);
|
||||
return new SourceReference( parameter, entries, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link SourceReference} from an {@code @Mappping}.
|
||||
*/
|
||||
@ -173,11 +149,6 @@ public class SourceReference extends AbstractReference {
|
||||
* @return the source reference
|
||||
*/
|
||||
private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) {
|
||||
|
||||
if ( canBeTreatedAsMapSourceType( parameter.getType() ) ) {
|
||||
return fromMapSource( segments, parameter );
|
||||
}
|
||||
|
||||
boolean foundEntryMatch;
|
||||
|
||||
String[] propertyNames = segments;
|
||||
@ -214,14 +185,6 @@ public class SourceReference extends AbstractReference {
|
||||
*/
|
||||
private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) {
|
||||
|
||||
if (parameter != null && canBeTreatedAsMapSourceType( parameter.getType() )) {
|
||||
String[] propertyNames = new String[0];
|
||||
if ( segments.length > 1 ) {
|
||||
propertyNames = Arrays.copyOfRange( segments, 1, segments.length );
|
||||
}
|
||||
return fromMapSource( propertyNames, parameter );
|
||||
}
|
||||
|
||||
boolean foundEntryMatch;
|
||||
|
||||
String[] propertyNames = new String[0];
|
||||
@ -244,17 +207,8 @@ public class SourceReference extends AbstractReference {
|
||||
return new SourceReference( parameter, entries, foundEntryMatch );
|
||||
}
|
||||
|
||||
private boolean canBeTreatedAsMapSourceType(Type type) {
|
||||
if ( !type.isMapType() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Type> typeParameters = type.getTypeParameters();
|
||||
return typeParameters.size() == 2 && typeParameters.get( 0 ).isString();
|
||||
}
|
||||
|
||||
/**
|
||||
* When there are more than one source parameters, the first segment name of the propery
|
||||
* When there are more than one source parameters, the first segment name of the property
|
||||
* needs to match the parameter name to avoid ambiguity
|
||||
*
|
||||
* consider: {@code Target map( Source1 source1, Source2 source2 )}
|
||||
@ -356,24 +310,20 @@ public class SourceReference extends AbstractReference {
|
||||
Type newType = type;
|
||||
for ( int i = 0; i < entryNames.length; i++ ) {
|
||||
boolean matchFound = false;
|
||||
Map<String, Accessor> sourceReadAccessors = newType.getPropertyReadAccessors();
|
||||
Map<String, Accessor> sourcePresenceCheckers = newType.getPropertyPresenceCheckers();
|
||||
|
||||
for ( Map.Entry<String, Accessor> getter : sourceReadAccessors.entrySet() ) {
|
||||
if ( getter.getKey().equals( entryNames[i] ) ) {
|
||||
Accessor readAccessor = newType.getReadAccessor( entryNames[i] );
|
||||
if ( readAccessor != null ) {
|
||||
Accessor presenceChecker = newType.getPresenceChecker( entryNames[i] );
|
||||
newType = typeFactory.getReturnType(
|
||||
(DeclaredType) newType.getTypeMirror(),
|
||||
getter.getValue()
|
||||
readAccessor
|
||||
);
|
||||
sourceEntries.add( forSourceReference(
|
||||
Arrays.copyOf( entryNames, i + 1 ),
|
||||
getter.getValue(),
|
||||
sourcePresenceCheckers.get( entryNames[i] ),
|
||||
readAccessor,
|
||||
presenceChecker,
|
||||
newType
|
||||
) );
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !matchFound ) {
|
||||
break;
|
||||
|
@ -48,6 +48,8 @@ import org.mapstruct.ap.internal.util.Nouns;
|
||||
import org.mapstruct.ap.internal.util.TypeUtils;
|
||||
import org.mapstruct.ap.internal.util.accessor.Accessor;
|
||||
import org.mapstruct.ap.internal.util.accessor.AccessorType;
|
||||
import org.mapstruct.ap.internal.util.accessor.MapValueAccessor;
|
||||
import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker;
|
||||
|
||||
import static org.mapstruct.ap.internal.util.Collections.first;
|
||||
|
||||
@ -60,6 +62,7 @@ import static org.mapstruct.ap.internal.util.Collections.first;
|
||||
* through {@link TypeFactory}.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class Type extends ModelElement implements Comparable<Type> {
|
||||
|
||||
@ -308,6 +311,17 @@ public class Type extends ModelElement implements Comparable<Type> {
|
||||
return isMapType;
|
||||
}
|
||||
|
||||
private boolean hasStringMapSignature() {
|
||||
if ( isMapType() ) {
|
||||
List<Type> typeParameters = getTypeParameters();
|
||||
if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCollectionOrMapType() {
|
||||
return isCollectionType || isMapType;
|
||||
}
|
||||
@ -597,6 +611,42 @@ public class Type extends ModelElement implements Comparable<Type> {
|
||||
}
|
||||
}
|
||||
|
||||
public Accessor getReadAccessor(String propertyName) {
|
||||
if ( hasStringMapSignature() ) {
|
||||
ExecutableElement getMethod = getAllMethods()
|
||||
.stream()
|
||||
.filter( m -> m.getSimpleName().contentEquals( "get" ) )
|
||||
.filter( m -> m.getParameters().size() == 1 )
|
||||
.findAny()
|
||||
.orElse( null );
|
||||
return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName );
|
||||
}
|
||||
|
||||
Map<String, Accessor> readAccessors = getPropertyReadAccessors();
|
||||
|
||||
return readAccessors.get( propertyName );
|
||||
}
|
||||
|
||||
public Accessor getPresenceChecker(String propertyName) {
|
||||
if ( hasStringMapSignature() ) {
|
||||
ExecutableElement containsKeyMethod = getAllMethods()
|
||||
.stream()
|
||||
.filter( m -> m.getSimpleName().contentEquals( "containsKey" ) )
|
||||
.filter( m -> m.getParameters().size() == 1 )
|
||||
.findAny()
|
||||
.orElse( null );
|
||||
|
||||
return new MapValuePresenceChecker(
|
||||
containsKeyMethod,
|
||||
typeParameters.get( 1 ).getTypeMirror(),
|
||||
propertyName
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Accessor> presenceCheckers = getPropertyPresenceCheckers();
|
||||
return presenceCheckers.get( propertyName );
|
||||
}
|
||||
|
||||
/**
|
||||
* getPropertyReadAccessors
|
||||
*
|
||||
|
@ -261,6 +261,20 @@ class FromMapMappingTest {
|
||||
assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" );
|
||||
}
|
||||
|
||||
@IssueKey("2553")
|
||||
@ProcessorTest
|
||||
@WithClasses(MapToBeanFromMapAndNestedMapWithDefinedMapping.class)
|
||||
void shouldMapFromNestedMapWithDefinedMapping() {
|
||||
|
||||
MapToBeanFromMapAndNestedMapWithDefinedMapping.Source source =
|
||||
new MapToBeanFromMapAndNestedMapWithDefinedMapping.Source();
|
||||
MapToBeanFromMapAndNestedMapWithDefinedMapping.Target target =
|
||||
MapToBeanFromMapAndNestedMapWithDefinedMapping.INSTANCE.toTarget( source );
|
||||
|
||||
assertThat( target ).isNotNull();
|
||||
assertThat( target.getNested() ).isEqualTo( "valueFromNestedMap" );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses(ObjectMapToBeanWithQualifierMapper.class)
|
||||
void shouldUseObjectQualifiedMethod() {
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.frommap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface MapToBeanFromMapAndNestedMapWithDefinedMapping {
|
||||
|
||||
MapToBeanFromMapAndNestedMapWithDefinedMapping INSTANCE = Mappers.getMapper(
|
||||
MapToBeanFromMapAndNestedMapWithDefinedMapping.class );
|
||||
|
||||
@Mapping(target = "nested", source = "nestedTarget.nested")
|
||||
Target toTarget(Source source);
|
||||
|
||||
class Source {
|
||||
|
||||
private Map<String, String> nestedTarget = new HashMap<>();
|
||||
|
||||
public Map<String, String> getNestedTarget() {
|
||||
return nestedTarget;
|
||||
}
|
||||
|
||||
public void setNestedTarget(Map<String, String> nestedTarget) {
|
||||
this.nestedTarget = nestedTarget;
|
||||
}
|
||||
|
||||
public Source() {
|
||||
nestedTarget.put( "nested", "valueFromNestedMap" );
|
||||
}
|
||||
}
|
||||
|
||||
class Target {
|
||||
|
||||
private String nested;
|
||||
|
||||
public String getNested() {
|
||||
return nested;
|
||||
}
|
||||
|
||||
public void setNested(String nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user