Skip to content

#2946 allow update methods with @SubclassMapping #3330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Assignment createForgedAssignment(SourceRHS source, ForgedMethod methodRef, Mapp
private Assignment createAssignment(SourceRHS source, ForgedMethod methodRef) {
Assignment assignment = MethodReference.forForgedMethod(
methodRef,
ParameterBinding.fromParameters( methodRef.getParameters() )
ParameterBinding.fromParameters( methodRef.getSourceParameters() )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed?

);
assignment.setAssignment( source );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
sourceType,
targetType,
mappingReferences ) );

String sourceArgument = null;
for ( Parameter parameter : method.getSourceParameters() ) {
if ( ctx
Expand All @@ -479,7 +480,19 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap
}
}
}
return new SubclassMapping( sourceType, sourceArgument, targetType, assignment );

String targetArgument = null;
Parameter targetParameter = method.getMappingTargetParameter();
if ( targetParameter != null ) {
targetArgument = targetParameter.getName();
if ( assignment != null ) {
assignment.setSourceLocalVarName(
"(" + sourceType.createReferenceName() + ") " + sourceArgument
+ ", (" + targetType.createReferenceName() + ") " + targetArgument );
}
}

return new SubclassMapping( sourceType, sourceArgument, targetType, targetArgument, assignment );
}

private boolean isAbstractReturnTypeAllowed() {
Expand Down Expand Up @@ -1942,6 +1955,10 @@ public boolean hasSubclassMappings() {
return !subclassMappings.isEmpty();
}

public boolean isUpdateMethod() {
return Parameter.getMappingTargetParameter( getParameters() ) != null;
}

Comment on lines +1958 to +1961
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this and use isExistingInstanceMapping instead

public boolean isAbstractReturnType() {
return getFactoryMethod() == null && returnTypeToConstruct != null
&& returnTypeToConstruct.isAbstract();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ private ForgedMethod(String name, Type sourceType, Type returnType, List<Paramet
Parameter sourceParameter = new Parameter( sourceParamSafeName, sourceType );
this.parameters.add( sourceParameter );
this.parameters.addAll( additionalParameters );
if ( basedOn.isUpdateMethod() ) {
this.parameters.add( Parameter.forForgedMappingTarget( returnType ) );
}
Comment on lines +174 to +176
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Why don't we pass everything in the additional parameters?

this.sourceParameters = Parameter.getSourceParameters( parameters );
this.contextParameters = Parameter.getContextParameters( parameters );
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,24 @@ public class SubclassMapping extends ModelElement {
private final Type targetType;
private final Assignment assignment;
private final String sourceArgument;
private final String targetArgument;

public SubclassMapping(Type sourceType, String sourceArgument, Type targetType, Assignment assignment) {
public SubclassMapping( Type sourceType, String sourceArgument, Type targetType, String targetArgument, Assignment assignment ) {
this.sourceType = sourceType;
this.sourceArgument = sourceArgument;
this.targetType = targetType;
this.targetArgument = targetArgument;
this.assignment = assignment;
}

public Type getSourceType() {
return sourceType;
}

public Type getTargetType() {
return targetType;
}

@Override
public Set<Type> getImportTypes() {
return Collections.singleton( sourceType );
Expand All @@ -53,6 +59,10 @@ public String getSourceArgument() {
return sourceArgument;
}

public String getTargetArgument() {
return targetArgument;
}

@Override
public boolean equals(final Object other) {
if ( !( other instanceof SubclassMapping ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_ILLEGAL_SUBCLASS;
import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_NO_VALID_SUPERCLASS;
import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED;

/**
* Represents a subclass mapping as configured via {@code @SubclassMapping}.
Expand Down Expand Up @@ -58,11 +57,6 @@ private static boolean isConsistent(SubclassMappingGem gem, ExecutableElement me
TypeUtils typeUtils, List<Parameter> sourceParameters, Type resultType,
SubclassValidator subclassValidator) {

if ( resultType == null ) {
messager.printMessage( method, gem.mirror(), SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED );
return false;
}

TypeMirror sourceSubclass = gem.source().getValue();
TypeMirror targetSubclass = gem.target().getValue();
TypeMirror targetParentType = resultType.getTypeMirror();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType,
SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils );
Set<SubclassMappingOptions> subclassMappingOptions = getSubclassMappings(
sourceParameters,
targetParameter != null ? null : resultType,
resultType,
method,
beanMappingOptions,
subclassValidator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ public enum Message {
SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS( "Subclass '%s' is already defined as a source." ),
SUBCLASSMAPPING_ILLEGAL_SUBCLASS( "Class '%s' is not a subclass of '%s'." ),
SUBCLASSMAPPING_NO_VALID_SUPERCLASS( "Could not find a parameter that is a superclass for '%s'." ),
SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED( "SubclassMapping annotation can not be used for update methods." ),
SUBCLASSMAPPING_ILLOGICAL_ORDER( "SubclassMapping annotation for '%s' found after '%s', but all '%s' objects are also instances of '%s'.", Diagnostic.Kind.WARNING ),

LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@

<#if hasSubclassMappings()>
<#list subclassMappings as subclass>
<#if subclass_index &gt; 0>else</#if> if (${subclass.sourceArgument} instanceof <@includeModel object=subclass.sourceType/>) {
<@includeModel object=subclass.assignment existingInstanceMapping=existingInstanceMapping/>
<#if isUpdateMethod()>
<#if subclass_index &gt; 0>else</#if> if (${subclass.sourceArgument} instanceof <@includeModel object=subclass.sourceType/> && ${subclass.targetArgument} instanceof <@includeModel object=subclass.targetType/>) {
<#else>
<#if subclass_index &gt; 0>else</#if> if (${subclass.sourceArgument} instanceof <@includeModel object=subclass.sourceType/>) {
</#if>
<@includeModel object=subclass.assignment existingInstanceMapping=existingInstanceMapping/>
}
</#list>
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,21 +227,11 @@ void subclassMappingInheritsInverseMappingWithCompositeMapping() {
void subclassOrderWarning() {
}

@IssueKey( "2946" )
@ProcessorTest
@WithClasses({ ErroneousSubclassUpdateMapper.class })
@ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = {
@Diagnostic(type = ErroneousSubclassUpdateMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 21,
message = "SubclassMapping annotation can not be used for update methods."
),
@Diagnostic(type = ErroneousSubclassUpdateMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 25,
message = "SubclassMapping annotation can not be used for update methods."
)
})
void unsupportedUpdateMethod() {
@WithClasses({ SubclassUpdateMapper.class })
@ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED)
void updateMethod() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this test and write more thorough tests

}

@ProcessorTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,15 @@
import org.mapstruct.SubclassMapping;
import org.mapstruct.ap.test.subclassmapping.mappables.Car;
import org.mapstruct.ap.test.subclassmapping.mappables.CarDto;
import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto;
import org.mapstruct.factory.Mappers;

@Mapper
public interface ErroneousSubclassUpdateMapper {
ErroneousSubclassUpdateMapper INSTANCE = Mappers.getMapper( ErroneousSubclassUpdateMapper.class );
public interface SubclassUpdateMapper {
SubclassUpdateMapper INSTANCE = Mappers.getMapper( SubclassUpdateMapper.class );

@SubclassMapping( source = Car.class, target = CarDto.class )
@Mapping( source = "vehicleManufacturingCompany", target = "maker" )
void map(@MappingTarget VehicleDto target, Car vehicle);

@SubclassMapping( source = Car.class, target = CarDto.class )
@Mapping( source = "vehicleManufacturingCompany", target = "maker" )
VehicleDto mapWithReturnType(@MappingTarget VehicleDto target, Car vehicle);
VehicleDto map(@MappingTarget VehicleDto target, Vehicle vehicle);
}