mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#1153 Do not use invalid TargetReferences when creating nested target mappings
* Move the error generation for the invalid TargetReference into the BuilderFromTargetMapping * TargetReference will strip the first entry name in the following cases only if the first entry name matches the MappingTarget parameter, or for reverse mappings it matches the source parameter name * Pass the reverse source parameter when initializing a reverse mapping
This commit is contained in:
parent
aa6cda1177
commit
459354b6b8
@ -322,7 +322,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
|
|
||||||
// first we have to handle nested target mappings
|
// first we have to handle nested target mappings
|
||||||
if ( method.getMappingOptions().hasNestedTargetReferences() ) {
|
if ( method.getMappingOptions().hasNestedTargetReferences() ) {
|
||||||
handleDefinedNestedTargetMapping( handledTargets );
|
errorOccurred = handleDefinedNestedTargetMapping( handledTargets );
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( Map.Entry<String, List<Mapping>> entry : methodMappings.entrySet() ) {
|
for ( Map.Entry<String, List<Mapping>> entry : methodMappings.entrySet() ) {
|
||||||
@ -335,7 +335,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( reportErrorOnTargetObject( mapping ) ) {
|
else {
|
||||||
errorOccurred = true;
|
errorOccurred = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,7 +350,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
return errorOccurred;
|
return errorOccurred;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDefinedNestedTargetMapping(Set<String> handledTargets) {
|
private boolean handleDefinedNestedTargetMapping(Set<String> handledTargets) {
|
||||||
|
|
||||||
NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder()
|
NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder()
|
||||||
.mappingContext( ctx )
|
.mappingContext( ctx )
|
||||||
@ -368,6 +368,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
}
|
}
|
||||||
unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() );
|
unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() );
|
||||||
}
|
}
|
||||||
|
return holder.hasErrorOccurred();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleDefinedMapping(Mapping mapping, Set<String> handledTargets) {
|
private boolean handleDefinedMapping(Mapping mapping, Set<String> handledTargets) {
|
||||||
@ -474,36 +475,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
return errorOccured;
|
return errorOccured;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean reportErrorOnTargetObject(Mapping mapping) {
|
|
||||||
|
|
||||||
boolean errorOccurred = false;
|
|
||||||
|
|
||||||
boolean hasReadAccessor
|
|
||||||
= method.getResultType().getPropertyReadAccessors().containsKey( mapping.getTargetName() );
|
|
||||||
|
|
||||||
if ( hasReadAccessor ) {
|
|
||||||
if ( !mapping.isIgnored() ) {
|
|
||||||
ctx.getMessager().printMessage(
|
|
||||||
method.getExecutable(),
|
|
||||||
mapping.getMirror(),
|
|
||||||
mapping.getSourceAnnotationValue(),
|
|
||||||
Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE,
|
|
||||||
mapping.getTargetName() );
|
|
||||||
errorOccurred = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ctx.getMessager().printMessage(
|
|
||||||
method.getExecutable(),
|
|
||||||
mapping.getMirror(),
|
|
||||||
mapping.getSourceAnnotationValue(),
|
|
||||||
Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE,
|
|
||||||
mapping.getTargetName() );
|
|
||||||
errorOccurred = true;
|
|
||||||
}
|
|
||||||
return errorOccurred;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates over all target properties and all source parameters.
|
* Iterates over all target properties and all source parameters.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.model.source.MappingOptions;
|
|||||||
import org.mapstruct.ap.internal.model.source.Method;
|
import org.mapstruct.ap.internal.model.source.Method;
|
||||||
import org.mapstruct.ap.internal.model.source.PropertyEntry;
|
import org.mapstruct.ap.internal.model.source.PropertyEntry;
|
||||||
import org.mapstruct.ap.internal.model.source.SourceReference;
|
import org.mapstruct.ap.internal.model.source.SourceReference;
|
||||||
|
import org.mapstruct.ap.internal.model.source.TargetReference;
|
||||||
import org.mapstruct.ap.internal.util.Extractor;
|
import org.mapstruct.ap.internal.util.Extractor;
|
||||||
|
|
||||||
import static org.mapstruct.ap.internal.util.Collections.first;
|
import static org.mapstruct.ap.internal.util.Collections.first;
|
||||||
@ -63,15 +64,17 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
private final Set<String> handledTargets;
|
private final Set<String> handledTargets;
|
||||||
private final List<PropertyMapping> propertyMappings;
|
private final List<PropertyMapping> propertyMappings;
|
||||||
private final Map<PropertyEntry, List<Mapping>> unprocessedDefinedTarget;
|
private final Map<PropertyEntry, List<Mapping>> unprocessedDefinedTarget;
|
||||||
|
private final boolean errorOccurred;
|
||||||
|
|
||||||
public NestedTargetPropertyMappingHolder(
|
public NestedTargetPropertyMappingHolder(
|
||||||
List<Parameter> processedSourceParameters, Set<String> handledTargets,
|
List<Parameter> processedSourceParameters, Set<String> handledTargets,
|
||||||
List<PropertyMapping> propertyMappings,
|
List<PropertyMapping> propertyMappings,
|
||||||
Map<PropertyEntry, List<Mapping>> unprocessedDefinedTarget) {
|
Map<PropertyEntry, List<Mapping>> unprocessedDefinedTarget, boolean errorOccurred) {
|
||||||
this.processedSourceParameters = processedSourceParameters;
|
this.processedSourceParameters = processedSourceParameters;
|
||||||
this.handledTargets = handledTargets;
|
this.handledTargets = handledTargets;
|
||||||
this.propertyMappings = propertyMappings;
|
this.propertyMappings = propertyMappings;
|
||||||
this.unprocessedDefinedTarget = unprocessedDefinedTarget;
|
this.unprocessedDefinedTarget = unprocessedDefinedTarget;
|
||||||
|
this.errorOccurred = errorOccurred;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,6 +105,13 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
return unprocessedDefinedTarget;
|
return unprocessedDefinedTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if an error occurred during the creation of the nested mappings
|
||||||
|
*/
|
||||||
|
public boolean hasErrorOccurred() {
|
||||||
|
return errorOccurred;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private Method method;
|
private Method method;
|
||||||
@ -227,7 +237,8 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
processedSourceParameters,
|
processedSourceParameters,
|
||||||
handledTargets,
|
handledTargets,
|
||||||
propertyMappings,
|
propertyMappings,
|
||||||
unprocessedDefinedTarget
|
unprocessedDefinedTarget,
|
||||||
|
groupedByTP.errorOccurred
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,9 +295,16 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
= new LinkedHashMap<PropertyEntry, List<Mapping>>();
|
= new LinkedHashMap<PropertyEntry, List<Mapping>>();
|
||||||
Map<PropertyEntry, List<Mapping>> singleTargetReferences
|
Map<PropertyEntry, List<Mapping>> singleTargetReferences
|
||||||
= new LinkedHashMap<PropertyEntry, List<Mapping>>();
|
= new LinkedHashMap<PropertyEntry, List<Mapping>>();
|
||||||
|
boolean errorOccurred = false;
|
||||||
for ( List<Mapping> mapping : mappings.values() ) {
|
for ( List<Mapping> mapping : mappings.values() ) {
|
||||||
PropertyEntry property = first( first( mapping ).getTargetReference().getPropertyEntries() );
|
Mapping firstMapping = first( mapping );
|
||||||
Mapping newMapping = first( mapping ).popTargetReference();
|
TargetReference targetReference = firstMapping.getTargetReference();
|
||||||
|
if ( !targetReference.isValid() ) {
|
||||||
|
errorOccurred = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PropertyEntry property = first( targetReference.getPropertyEntries() );
|
||||||
|
Mapping newMapping = firstMapping.popTargetReference();
|
||||||
if ( newMapping != null ) {
|
if ( newMapping != null ) {
|
||||||
// group properties on current name.
|
// group properties on current name.
|
||||||
if ( !mappingsKeyedByProperty.containsKey( property ) ) {
|
if ( !mappingsKeyedByProperty.containsKey( property ) ) {
|
||||||
@ -298,11 +316,11 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
if ( !singleTargetReferences.containsKey( property ) ) {
|
if ( !singleTargetReferences.containsKey( property ) ) {
|
||||||
singleTargetReferences.put( property, new ArrayList<Mapping>() );
|
singleTargetReferences.put( property, new ArrayList<Mapping>() );
|
||||||
}
|
}
|
||||||
singleTargetReferences.get( property ).add( first( mapping ) );
|
singleTargetReferences.get( property ).add( firstMapping );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences );
|
return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences, errorOccurred );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -576,11 +594,13 @@ public class NestedTargetPropertyMappingHolder {
|
|||||||
private static class GroupedTargetReferences {
|
private static class GroupedTargetReferences {
|
||||||
private final Map<PropertyEntry, List<Mapping>> poppedTargetReferences;
|
private final Map<PropertyEntry, List<Mapping>> poppedTargetReferences;
|
||||||
private final Map<PropertyEntry, List<Mapping>> singleTargetReferences;
|
private final Map<PropertyEntry, List<Mapping>> singleTargetReferences;
|
||||||
|
private final boolean errorOccurred;
|
||||||
|
|
||||||
private GroupedTargetReferences(Map<PropertyEntry, List<Mapping>> poppedTargetReferences,
|
private GroupedTargetReferences(Map<PropertyEntry, List<Mapping>> poppedTargetReferences,
|
||||||
Map<PropertyEntry, List<Mapping>> singleTargetReferences) {
|
Map<PropertyEntry, List<Mapping>> singleTargetReferences, boolean errorOccurred) {
|
||||||
this.poppedTargetReferences = poppedTargetReferences;
|
this.poppedTargetReferences = poppedTargetReferences;
|
||||||
this.singleTargetReferences = singleTargetReferences;
|
this.singleTargetReferences = singleTargetReferences;
|
||||||
|
this.errorOccurred = errorOccurred;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +252,21 @@ public class Mapping {
|
|||||||
( (DeclaredType) mirror ).asElement().getKind() == ElementKind.ENUM;
|
( (DeclaredType) mirror ).asElement().getKind() == ElementKind.ENUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory, boolean isReverse) {
|
public void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory) {
|
||||||
|
init( method, messager, typeFactory, false, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Mapping.
|
||||||
|
*
|
||||||
|
* @param method the source method that the mapping belongs to
|
||||||
|
* @param messager the messager that can be used for outputting messages
|
||||||
|
* @param typeFactory the type factory
|
||||||
|
* @param isReverse whether the init is for a reverse mapping
|
||||||
|
* @param reverseSourceParameter the source parameter from the revers mapping
|
||||||
|
*/
|
||||||
|
private void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory, boolean isReverse,
|
||||||
|
Parameter reverseSourceParameter) {
|
||||||
|
|
||||||
if ( !method.isEnumMapping() ) {
|
if ( !method.isEnumMapping() ) {
|
||||||
sourceReference = new SourceReference.BuilderFromMapping()
|
sourceReference = new SourceReference.BuilderFromMapping()
|
||||||
@ -268,6 +282,7 @@ public class Mapping {
|
|||||||
.method( method )
|
.method( method )
|
||||||
.messager( messager )
|
.messager( messager )
|
||||||
.typeFactory( typeFactory )
|
.typeFactory( typeFactory )
|
||||||
|
.reverseSourceParameter( reverseSourceParameter )
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,7 +423,13 @@ public class Mapping {
|
|||||||
Collections.<String>emptyList()
|
Collections.<String>emptyList()
|
||||||
);
|
);
|
||||||
|
|
||||||
reverse.init( method, messager, typeFactory, true );
|
reverse.init(
|
||||||
|
method,
|
||||||
|
messager,
|
||||||
|
typeFactory,
|
||||||
|
true,
|
||||||
|
sourceReference != null ? sourceReference.getParameter() : null
|
||||||
|
);
|
||||||
return reverse;
|
return reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ public class SourceMethod implements Method {
|
|||||||
if ( mappings != null ) {
|
if ( mappings != null ) {
|
||||||
for ( Map.Entry<String, List<Mapping>> entry : mappings.entrySet() ) {
|
for ( Map.Entry<String, List<Mapping>> entry : mappings.entrySet() ) {
|
||||||
for ( Mapping mapping : entry.getValue() ) {
|
for ( Mapping mapping : entry.getValue() ) {
|
||||||
mapping.init( sourceMethod, messager, typeFactory, false );
|
mapping.init( sourceMethod, messager, typeFactory );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,30 @@ public class TargetReference {
|
|||||||
private SourceMethod method;
|
private SourceMethod method;
|
||||||
private FormattingMessager messager;
|
private FormattingMessager messager;
|
||||||
private TypeFactory typeFactory;
|
private TypeFactory typeFactory;
|
||||||
|
private boolean isReverse;
|
||||||
|
/**
|
||||||
|
* Needed when we are building from reverse mapping. It is needed, so we can remove the first level if it is
|
||||||
|
* needed.
|
||||||
|
* E.g. If we have a mapping like:
|
||||||
|
* <code>
|
||||||
|
* {@literal @}Mapping( target = "letterSignature", source = "dto.signature" )
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* When it is reversed it will look like:
|
||||||
|
* <code>
|
||||||
|
* {@literal @}Mapping( target = "dto.signature", source = "letterSignature" )
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* The {@code dto} needs to be considered as a possibility for a target name only if a Target Reference for
|
||||||
|
* a reverse is created.
|
||||||
|
*/
|
||||||
|
private Parameter reverseSourceParameter;
|
||||||
|
/**
|
||||||
|
* During {@link #getTargetEntries(Type, String[])} an error can occur. However, we are invoking
|
||||||
|
* that multiple times because the first entry can also be the name of the parameter. Therefore we keep
|
||||||
|
* the error message until the end when we can report it.
|
||||||
|
*/
|
||||||
|
private Message errorMessage;
|
||||||
|
|
||||||
public BuilderFromTargetMapping messager(FormattingMessager messager) {
|
public BuilderFromTargetMapping messager(FormattingMessager messager) {
|
||||||
this.messager = messager;
|
this.messager = messager;
|
||||||
@ -94,6 +118,12 @@ public class TargetReference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BuilderFromTargetMapping isReverse(boolean isReverse) {
|
public BuilderFromTargetMapping isReverse(boolean isReverse) {
|
||||||
|
this.isReverse = isReverse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BuilderFromTargetMapping reverseSourceParameter(Parameter reverseSourceParameter) {
|
||||||
|
this.reverseSourceParameter = reverseSourceParameter;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,20 +142,28 @@ public class TargetReference {
|
|||||||
boolean foundEntryMatch;
|
boolean foundEntryMatch;
|
||||||
Type resultType = method.getResultType();
|
Type resultType = method.getResultType();
|
||||||
|
|
||||||
// there can be 3 situations
|
// there can be 4 situations
|
||||||
// 1. Return type
|
// 1. Return type
|
||||||
// 2. @MappingTarget, with
|
// 2. A reverse target reference where the source parameter name is used in the original mapping
|
||||||
// 3. or without parameter name.
|
// 3. @MappingTarget, with
|
||||||
|
// 4. or without parameter name.
|
||||||
String[] targetPropertyNames = segments;
|
String[] targetPropertyNames = segments;
|
||||||
List<PropertyEntry> entries = getTargetEntries( resultType, targetPropertyNames );
|
List<PropertyEntry> entries = getTargetEntries( resultType, targetPropertyNames );
|
||||||
foundEntryMatch = (entries.size() == targetPropertyNames.length);
|
foundEntryMatch = (entries.size() == targetPropertyNames.length);
|
||||||
if ( !foundEntryMatch && segments.length > 1 ) {
|
if ( !foundEntryMatch && segments.length > 1
|
||||||
|
&& matchesSourceOrTargetParameter( segments[0], parameter, reverseSourceParameter, isReverse ) ) {
|
||||||
targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length );
|
targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length );
|
||||||
entries = getTargetEntries( resultType, targetPropertyNames );
|
entries = getTargetEntries( resultType, targetPropertyNames );
|
||||||
foundEntryMatch = (entries.size() == targetPropertyNames.length);
|
foundEntryMatch = (entries.size() == targetPropertyNames.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// foundEntryMatch = isValid, errors are handled in the BeanMapping, where the error context is known
|
if ( !foundEntryMatch && errorMessage != null) {
|
||||||
|
// This is called only for reporting errors
|
||||||
|
reportMappingError( errorMessage, mapping.getTargetName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// foundEntryMatch = isValid, errors are handled here, and the BeanMapping uses that to ignore
|
||||||
|
// the creation of mapping for invalid TargetReferences
|
||||||
return new TargetReference( parameter, entries, foundEntryMatch );
|
return new TargetReference( parameter, entries, foundEntryMatch );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,29 +180,23 @@ public class TargetReference {
|
|||||||
|
|
||||||
Accessor targetReadAccessor = nextType.getPropertyReadAccessors().get( entryNames[i] );
|
Accessor targetReadAccessor = nextType.getPropertyReadAccessors().get( entryNames[i] );
|
||||||
Accessor targetWriteAccessor = nextType.getPropertyWriteAccessors( cms ).get( entryNames[i] );
|
Accessor targetWriteAccessor = nextType.getPropertyWriteAccessors( cms ).get( entryNames[i] );
|
||||||
if ( targetWriteAccessor == null || ( i < entryNames.length - 1 && targetReadAccessor == null) ) {
|
boolean isLast = i == entryNames.length - 1;
|
||||||
// there should always be a write accessor and there should be read accessor mandatory for all
|
boolean isNotLast = i < entryNames.length - 1;
|
||||||
// but the last
|
if ( isWriteAccessorNotValidWhenNotLast( targetWriteAccessor, isNotLast )
|
||||||
// TODO error handling when full fledged target mapping is in place.
|
|| isWriteAccessorNotValidWhenLast( targetWriteAccessor, targetReadAccessor, mapping, isLast )
|
||||||
|
|| ( isNotLast && targetReadAccessor == null ) ) {
|
||||||
|
// there should always be a write accessor (except for the last when the mapping is ignored and
|
||||||
|
// there is a read accessor) and there should be read accessor mandatory for all but the last
|
||||||
|
setErrorMessage( targetWriteAccessor, targetReadAccessor );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ( i == entryNames.length - 1 ) || ( Executables.isSetterMethod( targetWriteAccessor )
|
if ( isLast || ( Executables.isSetterMethod( targetWriteAccessor )
|
||||||
|| Executables.isFieldAccessor( targetWriteAccessor ) ) ) {
|
|| Executables.isFieldAccessor( targetWriteAccessor ) ) ) {
|
||||||
// only intermediate nested properties when they are a true setter or field accessor
|
// only intermediate nested properties when they are a true setter or field accessor
|
||||||
// the last may be other readAccessor (setter / getter / adder).
|
// the last may be other readAccessor (setter / getter / adder).
|
||||||
|
|
||||||
if ( Executables.isGetterMethod( targetWriteAccessor ) ||
|
nextType = findNextType( nextType, targetWriteAccessor, targetReadAccessor );
|
||||||
Executables.isFieldAccessor( targetWriteAccessor ) ) {
|
|
||||||
nextType = typeFactory.getReturnType(
|
|
||||||
(DeclaredType) nextType.getTypeMirror(),
|
|
||||||
targetWriteAccessor );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nextType = typeFactory.getSingleParameter(
|
|
||||||
(DeclaredType) nextType.getTypeMirror(),
|
|
||||||
targetWriteAccessor ).getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if an entry alread exists, otherwise create
|
// check if an entry alread exists, otherwise create
|
||||||
String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 );
|
String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 );
|
||||||
@ -178,10 +210,103 @@ public class TargetReference {
|
|||||||
return targetEntries;
|
return targetEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the next type based on the initial type.
|
||||||
|
*
|
||||||
|
* @param initial for which a next type should be found
|
||||||
|
* @param targetWriteAccessor the write accessor
|
||||||
|
* @param targetReadAccessor the read accessor
|
||||||
|
* @return the next type that should be used for finding a property entry
|
||||||
|
*/
|
||||||
|
private Type findNextType(Type initial, Accessor targetWriteAccessor, Accessor targetReadAccessor) {
|
||||||
|
Type nextType;
|
||||||
|
Accessor toUse = targetWriteAccessor != null ? targetWriteAccessor : targetReadAccessor;
|
||||||
|
if ( Executables.isGetterMethod( toUse ) ||
|
||||||
|
Executables.isFieldAccessor( toUse ) ) {
|
||||||
|
nextType = typeFactory.getReturnType(
|
||||||
|
(DeclaredType) initial.getTypeMirror(),
|
||||||
|
toUse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextType = typeFactory.getSingleParameter(
|
||||||
|
(DeclaredType) initial.getTypeMirror(),
|
||||||
|
toUse
|
||||||
|
).getType();
|
||||||
|
}
|
||||||
|
return nextType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setErrorMessage(Accessor targetWriteAccessor, Accessor targetReadAccessor) {
|
||||||
|
if ( targetWriteAccessor == null && targetReadAccessor == null ) {
|
||||||
|
errorMessage = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE;
|
||||||
|
}
|
||||||
|
else if ( targetWriteAccessor == null ) {
|
||||||
|
errorMessage = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void reportMappingError(Message msg, Object... objects) {
|
private void reportMappingError(Message msg, Object... objects) {
|
||||||
messager.printMessage( method.getExecutable(), mapping.getMirror(), mapping.getSourceAnnotationValue(),
|
messager.printMessage( method.getExecutable(), mapping.getMirror(), mapping.getSourceAnnotationValue(),
|
||||||
msg, objects );
|
msg, objects );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A write accessor is not valid if it is {@code null} and it is not last. i.e. for nested target mappings
|
||||||
|
* there must be a write accessor for all entries except the last one.
|
||||||
|
*
|
||||||
|
* @param accessor that needs to be checked
|
||||||
|
* @param isNotLast whether or not this is the last write accessor in the entry chain
|
||||||
|
*
|
||||||
|
* @return {@code true} if the accessor is not valid, {@code false} otherwise
|
||||||
|
*/
|
||||||
|
private static boolean isWriteAccessorNotValidWhenNotLast(Accessor accessor, boolean isNotLast) {
|
||||||
|
return accessor == null && isNotLast;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a last accessor to be valid, a read accessor should exist and the mapping should be ignored. All other
|
||||||
|
* cases represent an invalid write accessor. This method will evaluate to {@code true} if the following is
|
||||||
|
* {@code true}:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code writeAccessor} is {@code null}</li>
|
||||||
|
* <li>It is for the last entry</li>
|
||||||
|
* <li>A read accessor does not exist, or the mapping is not ignored</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param writeAccessor that needs to be checked
|
||||||
|
* @param readAccessor that is used
|
||||||
|
* @param mapping that is used
|
||||||
|
* @param isLast whether or not this is the last write accessor in the entry chain
|
||||||
|
*
|
||||||
|
* @return {@code true} if the write accessor is not valid, {@code false} otherwise. See description for more
|
||||||
|
* information
|
||||||
|
*/
|
||||||
|
private static boolean isWriteAccessorNotValidWhenLast(Accessor writeAccessor, Accessor readAccessor,
|
||||||
|
Mapping mapping, boolean isLast) {
|
||||||
|
return writeAccessor == null && isLast && ( readAccessor == null || !mapping.isIgnored() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the {@code segment} is the same as the {@code targetParameter} or the {@code
|
||||||
|
* reverseSourceParameter} names
|
||||||
|
*
|
||||||
|
* @param segment that needs to be checked
|
||||||
|
* @param targetParameter the target parameter if it exists
|
||||||
|
* @param reverseSourceParameter the reverse source parameter if it exists
|
||||||
|
* @param isReverse whether a reverse {@link TargetReference} is being built
|
||||||
|
*
|
||||||
|
* @return {@code true} if the segment matches the name of the {@code targetParameter} or the name of the
|
||||||
|
* {@code reverseSourceParameter} when this is a reverse {@link TargetReference} is being built, {@code
|
||||||
|
* false} otherwise
|
||||||
|
*/
|
||||||
|
private static boolean matchesSourceOrTargetParameter(String segment, Parameter targetParameter,
|
||||||
|
Parameter reverseSourceParameter, boolean isReverse) {
|
||||||
|
boolean matchesTargetParameter =
|
||||||
|
targetParameter != null && targetParameter.getName().equals( segment );
|
||||||
|
return matchesTargetParameter
|
||||||
|
|| isReverse && reverseSourceParameter != null && reverseSourceParameter.getName().equals( segment );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TargetReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
|
private TargetReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._1153;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ErroneousIssue1153Mapper {
|
||||||
|
|
||||||
|
@Mappings( {
|
||||||
|
@Mapping( target = "readOnly", source = "nonNested"),
|
||||||
|
@Mapping( target = "nestedTarget.readOnly", source = "nestedSource.nested"),
|
||||||
|
@Mapping( target = "nestedTarget.writable", source = "nestedSource.writable"),
|
||||||
|
@Mapping( target = "nestedTarget2.readOnly", ignore = true),
|
||||||
|
@Mapping( target = "nestedTarget2.writable2", source = "nestedSource.writable"),
|
||||||
|
} )
|
||||||
|
Target map(Source source);
|
||||||
|
|
||||||
|
class Source {
|
||||||
|
|
||||||
|
public static class NestedSource {
|
||||||
|
//CHECKSTYLE:OFF
|
||||||
|
public String nested;
|
||||||
|
public String writable;
|
||||||
|
//CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
|
||||||
|
//CHECKSTYLE:OFF
|
||||||
|
public String nonNested;
|
||||||
|
public NestedSource nestedSource;
|
||||||
|
public NestedSource nestedSource2;
|
||||||
|
//CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
|
||||||
|
class Target {
|
||||||
|
|
||||||
|
public static class NestedTarget {
|
||||||
|
private String readOnly;
|
||||||
|
private String writable;
|
||||||
|
|
||||||
|
public String getReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWritable() {
|
||||||
|
return writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWritable(String writable) {
|
||||||
|
this.writable = writable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readOnly;
|
||||||
|
private NestedTarget nestedTarget;
|
||||||
|
private NestedTarget nestedTarget2;
|
||||||
|
|
||||||
|
public String getReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestedTarget getNestedTarget() {
|
||||||
|
return nestedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNestedTarget(NestedTarget nestedTarget) {
|
||||||
|
this.nestedTarget = nestedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestedTarget getNestedTarget2() {
|
||||||
|
return nestedTarget2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNestedTarget2(NestedTarget nestedTarget2) {
|
||||||
|
this.nestedTarget2 = nestedTarget2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._1153;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mapstruct.ap.testutil.IssueKey;
|
||||||
|
import org.mapstruct.ap.testutil.WithClasses;
|
||||||
|
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
|
||||||
|
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
|
||||||
|
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
|
||||||
|
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
@RunWith(AnnotationProcessorTestRunner.class)
|
||||||
|
@WithClasses(ErroneousIssue1153Mapper.class)
|
||||||
|
@IssueKey("1153")
|
||||||
|
public class Issue1153Test {
|
||||||
|
|
||||||
|
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
|
||||||
|
diagnostics = {
|
||||||
|
@Diagnostic(type = ErroneousIssue1153Mapper.class,
|
||||||
|
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||||
|
line = 32,
|
||||||
|
messageRegExp = "Property \"readOnly\" has no write accessor\\."),
|
||||||
|
@Diagnostic(type = ErroneousIssue1153Mapper.class,
|
||||||
|
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||||
|
line = 33,
|
||||||
|
messageRegExp = "Property \"nestedTarget.readOnly\" has no write accessor\\."),
|
||||||
|
@Diagnostic(type = ErroneousIssue1153Mapper.class,
|
||||||
|
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||||
|
line = 36,
|
||||||
|
messageRegExp = "Unknown property \"nestedTarget2.writable2\" in return type\\.")
|
||||||
|
})
|
||||||
|
@Test
|
||||||
|
public void shouldReportErrorsCorrectly() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user