Skip to content
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

DelegatingConverter and CycleAvoidingMappingContext #101

Closed
twallmey opened this issue Dec 6, 2023 · 4 comments
Closed

DelegatingConverter and CycleAvoidingMappingContext #101

twallmey opened this issue Dec 6, 2023 · 4 comments

Comments

@twallmey
Copy link

twallmey commented Dec 6, 2023

Hi,

I've defined a Mapper that should avoid cycles. For this reason I've just followed Mapstructs suggestion and invented the CycleAvoidingMappingContext. Doing so my current mapper looks like:

@Mapper(
    config = MapstructConfig.class
)
public interface DataModificationMapperV1Physical extends Converter<DataModificationV1, DataModification> {
  DataModification convert(DataModificationV1 from, @Context CycleAvoidingMappingContext context);

  @InheritInverseConfiguration
  @DelegatingConverter
  DataModificationV1 inverseConvert(DataModification from, @Context CycleAvoidingMappingContext context);
}

My problem obviously results from the additionally defined @DelegatingConverter because when compiling I get the following error:
Can only generate a delegating converter for methods with exactly one parameter and non-void return type.

Does anyone have an idea how to combine the CycleAvoidingMappingContext with @DelegatingConverter successfully?

Many thanks in advance,

Thorben

@Chessray
Copy link
Collaborator

Chessray commented Dec 6, 2023

In a broader sense, the question is asking about @DelegatingConverter with additional @Context parameters. You're right, this is a simple omission. I'll include it in the next release.

@twallmey
Copy link
Author

twallmey commented Dec 6, 2023

Great to hear! Do you already have an idea when the next release will be available that contains this feature?

I'm asking because we are currently working on a bigger innovation project. Does ist make sense to wait oder would you think that it will take a longer time? In this case we would have to look for a workaround.

@Chessray
Copy link
Collaborator

Chessray commented Dec 21, 2023

Sorry, looking at this again I just realised that I have to retract my previous response. Your base convert method doesn't conform to Spring's standard, so the @DelegatingConverter cannot work in this scenario anyway. If you want to use @Context objects, you can't have the Mapper extend the Converter interface.

This is a known limitation resulting from Spring's narrower definition of a Converter. MapStruct's Mappers are more liberal here.

@petitgros
Copy link

petitgros commented Feb 9, 2024

Hello,

I am trying to use spring ConversionService with a generic custom Converter including CycleAvoidingMappingContext like below.

  1. If i use DelegatingConverter in this custom Converter TToSConverter is generated even if there is no Mapper annotation.
  2. If i use DelegatingConverter in subclasses, as described twallmey, it work but only without using Context annotation. Maybe it will by using BaseMapper instead Converter as interface will generating delegate.
public interface BaseMapper<S, T> extends Converter<S, T> {

    @Nullable
    T convert(@NonNull S source, @Context CycleAvoidingMappingContext context);

    @Nullable
    @Override
    @Named("baseConvert")
    default T convert(@NonNull S source) {
        return convert(source, new CycleAvoidingMappingContext());
    }

    @DelegatingConverter // <--- This generate code below
    @InheritInverseConfiguration
    S invertConvert(T source);
}

Unwanted TToSConverter is generated

@Component
public class TToSConverter implements Converter<T, S> {
  private BaseMapper delegateMapper;

  public TToSConverter(@Autowired final BaseMapper delegateMapper) {
    this.delegateMapper = delegateMapper;
  }

  @Override
  public S convert(final T source) {
    return delegateMapper.invertConvert(source);
  }
}

EDIT:

I resolved the first point by adding a filter before the DelegatingConverterDescriptor creation in processor:

delegatingConverterDescriptors =
        annotations.stream()
            .filter(ConverterMapperProcessor::isDelegatingConverterAnnotation)
            .map(roundEnv::getElementsAnnotatedWith)
            .flatMap(Set::stream)
            .map(ExecutableElement.class::cast)
            // Do not generate delegate without Mapper annotation on class
            .filter(annotatedMethod -> annotatedMethod.getEnclosingElement().getAnnotationMirrors().stream().anyMatch(x -> x.getAnnotationType().toString().equals(MAPPER)))
            .map(annotatedMethod -> new DelegatingConverterDescriptor(annotatedMethod, processingEnv))
            .collect(toList());

I am creating a new issue for the second part (I can't process the DelegatingConverter by inheritance)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

3 participants