#2983 Add @AnnotateWith support to non bean mapping methods

This commit is contained in:
Orange Add 2022-08-28 18:33:46 +08:00 committed by GitHub
parent 4708f4b2aa
commit ac356cab25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 264 additions and 32 deletions

View File

@ -13,6 +13,9 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.util.Strings;
import java.util.ArrayList;
import java.util.List;
/**
* An abstract builder that can be reused for building {@link MappingMethod}(s).
*
@ -117,4 +120,15 @@ public abstract class AbstractMappingMethodBuilder<B extends AbstractMappingMeth
return description;
}
public List<Annotation> getMethodAnnotations() {
AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
ctx.getElementUtils(),
ctx.getTypeFactory(),
ctx.getMessager() );
List<Annotation> annotations = new ArrayList<>();
annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
return annotations;
}
}

View File

@ -85,7 +85,6 @@ import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETE
*/
public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List<Annotation> annotations;
private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final Map<String, List<PropertyMapping>> constructorMappingsByParameter;
@ -113,7 +112,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Set<Parameter> unprocessedSourceParameters = new HashSet<>();
private final Set<String> existingVariableNames = new HashSet<>();
private final Map<String, Set<MappingReference>> unprocessedDefinedTargets = new LinkedHashMap<>();
private final List<Annotation> annotations = new ArrayList<>();
private MappingReferences mappingReferences;
private MethodReference factoryMethod;
@ -216,12 +214,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// If the return type cannot be constructed then no need to try to create mappings
return null;
}
AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
ctx.getElementUtils(),
ctx.getTypeFactory(),
ctx.getMessager() );
annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
/* the type that needs to be used in the mapping process as target */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
@ -370,7 +362,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return new BeanMappingMethod(
method,
annotations,
getMethodAnnotations(),
existingVariableNames,
propertyMappings,
factoryMethod,
@ -1724,6 +1716,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
List<SubclassMapping> subclassMappings) {
super(
method,
annotations,
existingVariableNames,
factoryMethod,
mapNullToDefault,
@ -1732,7 +1725,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
//CHECKSTYLE:ON
this.annotations = annotations;
this.propertyMappings = propertyMappings;
this.returnTypeBuilder = returnTypeBuilder;
this.finalizerMethod = finalizerMethod;
@ -1771,10 +1763,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
this.subclassMappings = subclassMappings;
}
public List<Annotation> getAnnotations() {
return annotations;
}
public List<PropertyMapping> getConstantMappings() {
return constantMappings;
}

View File

@ -32,12 +32,13 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod {
private final String index2Name;
private IterableCreation iterableCreation;
ContainerMappingMethod(Method method, Collection<String> existingVariables, Assignment parameterAssignment,
ContainerMappingMethod(Method method, List<Annotation> annotations,
Collection<String> existingVariables, Assignment parameterAssignment,
MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences,
SelectionParameters selectionParameters) {
super( method, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences,
super( method, annotations, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences,
afterMappingReferences );
this.elementAssignment = parameterAssignment;
this.loopVariableName = loopVariableName;

View File

@ -57,6 +57,7 @@ public class IterableMappingMethod extends ContainerMappingMethod {
List<LifecycleCallbackMethodReference> afterMappingMethods, SelectionParameters selectionParameters) {
return new IterableMappingMethod(
method,
getMethodAnnotations(),
existingVariables,
assignment,
factoryMethod,
@ -69,13 +70,15 @@ public class IterableMappingMethod extends ContainerMappingMethod {
}
}
private IterableMappingMethod(Method method, Collection<String> existingVariables, Assignment parameterAssignment,
private IterableMappingMethod(Method method, List<Annotation> annotations,
Collection<String> existingVariables, Assignment parameterAssignment,
MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences,
SelectionParameters selectionParameters) {
super(
method,
annotations,
existingVariables,
parameterAssignment,
factoryMethod,

View File

@ -199,6 +199,7 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
return new MapMappingMethod(
method,
getMethodAnnotations(),
existingVariables,
keyAssignment,
valueAssignment,
@ -224,11 +225,12 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
}
private MapMappingMethod(Method method, Collection<String> existingVariableNames, Assignment keyAssignment,
private MapMappingMethod(Method method, List<Annotation> annotations,
Collection<String> existingVariableNames, Assignment keyAssignment,
Assignment valueAssignment, MethodReference factoryMethod, boolean mapNullToDefault,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences) {
super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences,
super( method, annotations, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences,
afterMappingReferences );
this.keyAssignment = keyAssignment;

View File

@ -24,7 +24,10 @@ public abstract class NormalTypeMappingMethod extends MappingMethod {
private final boolean overridden;
private final boolean mapNullToDefault;
NormalTypeMappingMethod(Method method, Collection<String> existingVariableNames, MethodReference factoryMethod,
private final List<Annotation> annotations;
NormalTypeMappingMethod(Method method, List<Annotation> annotations,
Collection<String> existingVariableNames, MethodReference factoryMethod,
boolean mapNullToDefault,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences) {
@ -32,6 +35,7 @@ public abstract class NormalTypeMappingMethod extends MappingMethod {
this.factoryMethod = factoryMethod;
this.overridden = method.overridesMethod();
this.mapNullToDefault = mapNullToDefault;
this.annotations = annotations;
}
@Override
@ -45,6 +49,9 @@ public abstract class NormalTypeMappingMethod extends MappingMethod {
else if ( factoryMethod != null ) {
types.addAll( factoryMethod.getImportTypes() );
}
for ( Annotation annotation : annotations ) {
types.addAll( annotation.getImportTypes() );
}
return types;
}
@ -60,6 +67,10 @@ public abstract class NormalTypeMappingMethod extends MappingMethod {
return this.factoryMethod;
}
public List<Annotation> getAnnotations() {
return annotations;
}
@Override
public int hashCode() {
final int prime = 31;

View File

@ -5,6 +5,12 @@
*/
package org.mapstruct.ap.internal.model;
import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@ -13,12 +19,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
@ -63,9 +63,9 @@ public class StreamMappingMethod extends ContainerMappingMethod {
sourceParameterType.isIterableType() ) {
helperImports.add( ctx.getTypeFactory().getType( StreamSupport.class ) );
}
return new StreamMappingMethod(
method,
getMethodAnnotations(),
existingVariables,
assignment,
factoryMethod,
@ -78,14 +78,16 @@ public class StreamMappingMethod extends ContainerMappingMethod {
);
}
}
private StreamMappingMethod(Method method, Collection<String> existingVariables, Assignment parameterAssignment,
//CHECKSTYLE:OFF
private StreamMappingMethod(Method method, List<Annotation> annotations,
Collection<String> existingVariables, Assignment parameterAssignment,
MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName,
List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences,
SelectionParameters selectionParameters, Set<Type> helperImports) {
super(
method,
annotations,
existingVariables,
parameterAssignment,
factoryMethod,
@ -95,6 +97,7 @@ public class StreamMappingMethod extends ContainerMappingMethod {
afterMappingReferences,
selectionParameters
);
//CHECKSTYLE:ON
this.helperImports = helperImports;
}

View File

@ -41,6 +41,7 @@ import static org.mapstruct.ap.internal.util.Collections.first;
*/
public class ValueMappingMethod extends MappingMethod {
private final List<Annotation> annotations;
private final List<MappingEntry> valueMappings;
private final MappingEntry defaultTarget;
private final MappingEntry nullTarget;
@ -116,9 +117,16 @@ public class ValueMappingMethod extends MappingMethod {
LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
AdditionalAnnotationsBuilder additionalAnnotationsBuilder =
new AdditionalAnnotationsBuilder(
ctx.getElementUtils(),
ctx.getTypeFactory(),
ctx.getMessager() );
List<Annotation> annotations = new ArrayList<>();
annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) );
// finally return a mapping
return new ValueMappingMethod( method,
annotations,
mappingEntries,
valueMappings.nullValueTarget,
valueMappings.defaultTargetValue,
@ -532,6 +540,7 @@ public class ValueMappingMethod extends MappingMethod {
}
private ValueMappingMethod(Method method,
List<Annotation> annotations,
List<MappingEntry> enumMappings,
String nullTarget,
String defaultTarget,
@ -544,6 +553,7 @@ public class ValueMappingMethod extends MappingMethod {
this.defaultTarget = new MappingEntry( null, defaultTarget != null ? defaultTarget : THROW_EXCEPTION);
this.unexpectedValueMappingException = unexpectedValueMappingException;
this.overridden = method.overridesMethod();
this.annotations = annotations;
}
@Override
@ -556,7 +566,9 @@ public class ValueMappingMethod extends MappingMethod {
importTypes.addAll( unexpectedValueMappingException.getImportTypes() );
}
}
for ( Annotation annotation : annotations ) {
importTypes.addAll( annotation.getImportTypes() );
}
return importTypes;
}
@ -590,6 +602,10 @@ public class ValueMappingMethod extends MappingMethod {
return overridden;
}
public List<Annotation> getAnnotations() {
return annotations;
}
public static class MappingEntry {
private final String source;
private final String target;

View File

@ -6,6 +6,9 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.IterableMappingMethod" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#if overridden>@Override</#if>
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#list beforeMappingReferencesWithoutMappingTarget as callback>

View File

@ -6,6 +6,9 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MapMappingMethod" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#if overridden>@Override</#if>
<#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#list beforeMappingReferencesWithoutMappingTarget as callback>

View File

@ -6,6 +6,9 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.StreamMappingMethod" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#if overridden>@Override</#if>
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>)<@throws/> {
<#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable-->

View File

@ -6,6 +6,9 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ValueMappingMethod" -->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
<#if overridden>@Override</#if>
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) {
<#list beforeMappingReferencesWithoutMappingTarget as callback>

View File

@ -0,0 +1,46 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
/**
* @author orange add
*/
@Mapper
public interface AnnotateBeanMappingMethodMapper {
@AnnotateWith(CustomMethodOnlyAnnotation.class)
Target map(Source source);
class Source {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
class Target {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import java.util.List;
/**
* @author orange add
*/
@Mapper
public interface AnnotateIterableMappingMethodMapper {
@AnnotateWith(CustomMethodOnlyAnnotation.class)
List<String> toStringList(List<Integer> integers);
}

View File

@ -0,0 +1,24 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;
import java.util.Date;
import java.util.Map;
/**
* @author orange add
*/
@Mapper
public interface AnnotateMapMappingMethodMapper {
@MapMapping(valueDateFormat = "dd.MM.yyyy")
@AnnotateWith(CustomMethodOnlyAnnotation.class)
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

View File

@ -0,0 +1,21 @@
/*
* 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.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import java.util.stream.Stream;
/**
* @author orange add
*/
@Mapper
public interface AnnotateStreamMappingMethodMapper {
@AnnotateWith(CustomMethodOnlyAnnotation.class)
Stream<String> toStringStream(Stream<Integer> integerStream);
}

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.ap.test.annotatewith;
import org.mapstruct.AnnotateWith;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
/**
* @author orange add
*/
@Mapper
public interface AnnotateValueMappingMethodMapper {
@ValueMappings({
@ValueMapping(target = "EXISTING", source = "EXISTING"),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "OTHER_EXISTING" )
})
@AnnotateWith(CustomMethodOnlyAnnotation.class)
AnnotateWithEnum map(String str);
}

View File

@ -6,7 +6,6 @@
package org.mapstruct.ap.test.annotatewith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.Mapper;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
@ -17,6 +16,11 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutco
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.mapstruct.factory.Mappers;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
/**
@ -545,4 +549,44 @@ public class AnnotateWithTest {
public void mapperWithIdenticalAnnotationRepeated() {
}
@ProcessorTest
@WithClasses( {AnnotateBeanMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} )
public void beanMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException {
AnnotateBeanMappingMethodMapper mapper = Mappers.getMapper( AnnotateBeanMappingMethodMapper.class );
Method method = mapper.getClass().getMethod( "map", AnnotateBeanMappingMethodMapper.Source.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
@ProcessorTest
@WithClasses( {AnnotateIterableMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} )
public void iterableMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException {
AnnotateIterableMappingMethodMapper mapper = Mappers.getMapper( AnnotateIterableMappingMethodMapper.class );
Method method = mapper.getClass().getMethod( "toStringList", List.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
@ProcessorTest
@WithClasses( {AnnotateMapMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} )
public void mapMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException {
AnnotateMapMappingMethodMapper mapper = Mappers.getMapper( AnnotateMapMappingMethodMapper.class );
Method method = mapper.getClass().getMethod( "longDateMapToStringStringMap", Map.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
@ProcessorTest
@WithClasses( {AnnotateStreamMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} )
public void streamMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException {
AnnotateStreamMappingMethodMapper mapper = Mappers.getMapper( AnnotateStreamMappingMethodMapper.class );
Method method = mapper.getClass().getMethod( "toStringStream", Stream.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
@ProcessorTest
@WithClasses( {AnnotateValueMappingMethodMapper.class, AnnotateWithEnum.class, CustomMethodOnlyAnnotation.class} )
public void valueMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException {
AnnotateValueMappingMethodMapper mapper = Mappers.getMapper( AnnotateValueMappingMethodMapper.class );
Method method = mapper.getClass().getMethod( "map", String.class );
assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull();
}
}