mapstruct / mapstruct-spring-extensions

Helpful additions to MapStruct when using the Spring Framework.
Apache License 2.0
144 stars 33 forks source link

Allow inherited DelegatingConverter to be processed #104

Open petitgros opened 7 months ago

petitgros commented 7 months ago

Related to https://github.com/mapstruct/mapstruct-spring-extensions/issues/101

Hi,

I am trying to modify the annotation processor to support custom Converter interface but I don't know how to process DelegatingConverter by inheritance.

New code in ConverterMapperProcessor:

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());

Custom converter:

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());
    }

    // Annotation is not processed
    @DelegatingConverter
    @InheritInverseConfiguration
    default S invertConvert(T source) {
        return invertConvert(source, new CycleAvoidingMappingContext());
    }

    S invertConvert(T source, @Context CycleAvoidingMappingContext context);
}

Mapper:

@Mapper(config = MapperConfig.class)
public abstract class CarMapper implements BaseMapper<Car, CarDto> {

    // Required annotation to have delegate generated by processor
    @DelegatingConverter
    @Override
    public Car invertConvert(CarDto source) {
        return BaseMapper.super.invertConvert(source);
    }
}

Delegate:

@Component
public class  CarDtoToCarConverter implements Converter<CarDto, Car> {
  private  CarMapper delegateMapper;

  public CarDtoToCarConverter(@Autowired final CarMapper delegateMapper) {
    this.delegateMapper = delegateMapper;
  }

  @Override
  public Car convert(final CarDto source) {
    return delegateMapper.invertConvert(source);
  }
}

Wanted mapper:

@Mapper(config = MapperConfig.class)
public abstract class CarMapper implements BaseMapper<Car, CarDto> {
    // Nothing to override
}

Thanks

Chessray commented 7 months ago

Logically speaking, the second portion (generating the Delegating Converter) would need to happen inside the Processor for the @Mapper annotation. This goes a bit against how we're doing things at the moment. Not sure whether this is feasible.