void generateMapStructTypeConverters()

in extensions/mapstruct/deployment/src/main/java/org/apache/camel/quarkus/component/mapstruct/deployment/MapStructProcessor.java [125:314]


    void generateMapStructTypeConverters(
            BuildProducer<GeneratedBeanBuildItem> generatedBean,
            BuildProducer<GeneratedClassBuildItem> generatedClass,
            BuildProducer<UnremovableBeanBuildItem> unremovableBean,
            BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
            BuildProducer<ConversionMethodInfoRuntimeValuesBuildItem> conversionMethodInfos,
            CombinedIndexBuildItem combinedIndex,
            MapStructMapperPackagesBuildItem mapperPackages,
            MapStructRecorder recorder) {

        // The logic that follows mimics dynamic TypeConverter logic in DefaultMapStructFinder.discoverMappings
        Set<String> packages = mapperPackages.getMapperPackages();
        AtomicInteger methodCount = new AtomicInteger();
        Map<String, RuntimeValue<ConversionMethodInfo>> conversionMethods = new HashMap<>();
        IndexView index = combinedIndex.getIndex();

        // Find implementations of Mapper annotated interfaces or abstract classes
        index.getAnnotations(Mapper.class)
                .stream()
                .map(AnnotationInstance::target)
                .map(AnnotationTarget::asClass)
                .filter(classInfo -> packages.contains(classInfo.name().packagePrefix()))
                .filter(classInfo -> classInfo.isInterface() || Modifier.isAbstract(classInfo.flags()))
                .flatMap(classInfo -> Stream.concat(index.getAllKnownImplementors(classInfo.name()).stream(),
                        index.getAllKnownSubclasses(classInfo.name()).stream()))
                .forEach(classInfo -> {
                    AtomicReference<RuntimeValue<?>> mapperRuntimeValue = new AtomicReference<>();
                    String mapperClassName = classInfo.name().toString();
                    String mapperDefinitionClassName = getMapperDefinitionClassName(classInfo);
                    if (ObjectHelper.isEmpty(mapperDefinitionClassName)) {
                        return;
                    }

                    // Check if there's a static instance field defined for the Mapper
                    ClassInfo mapperDefinitionClassInfo = index.getClassByName(mapperDefinitionClassName);
                    Optional<FieldInfo> mapperInstanceField = mapperDefinitionClassInfo
                            .fields()
                            .stream()
                            .filter(fieldInfo -> Modifier.isStatic(fieldInfo.flags()))
                            .filter(fieldInfo -> fieldInfo.type().name().toString().equals(mapperDefinitionClassName))
                            .findFirst();

                    // Check of the Mapper is a CDI bean with one of the supported MapStruct annotations
                    boolean mapperBeanExists = classInfo.hasDeclaredAnnotation(ApplicationScoped.class)
                            || classInfo.hasDeclaredAnnotation(Named.class);
                    if (mapperInstanceField.isEmpty()) {
                        if (mapperBeanExists) {
                            unremovableBean
                                    .produce(new UnremovableBeanBuildItem(beanInfo -> beanInfo.hasType(classInfo.name())));
                        } else {
                            // Create the Mapper ourselves
                            mapperRuntimeValue.set(recorder.createMapper(mapperClassName));
                        }
                    }

                    /*
                     * Generate SimpleTypeConverter.ConversionMethod implementations for each candidate Mapper method.
                     *
                     * ReflectionHelper is used to resolve the mapper methods for simplicity, compared to Jandex where
                     * we potentially have to iterate over the type hierarchy (E.g for multiple interfaces,
                     * interface / class inheritance etc).
                     *
                     * public final class FooConversionMethod implements ConversionMethod {
                     *    private final FooMapperImpl mapper;
                     *
                     *    // Generated only if a Mapper instance is a CDI bean
                     *    public FooConversionMethod() {
                     *    }
                     *
                     *    // Generated only if a Mapper instance was declared on the Mapper interface
                     *    public FooConversionMethod() {
                     *        this(CarMapper.INSTANCE);
                     *    }
                     *
                     *    public FooConversionMethod(FooMapperImpl mapper) {
                     *        this.mapper = mapper;
                     *    }
                     *
                     *    @Override
                     *    public Object doConvert(Class<?> type, Exchange exchange, Object value) throws Exception {
                     *        return mapper.stringToInt(value);
                     *    }
                     * }
                     */
                    ReflectionHelper.doWithMethods(resolveClass(mapperDefinitionClassName), method -> {
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (method.getParameterCount() != 1) {
                            return;
                        }

                        Class<?> fromType = parameterTypes[0];
                        Class<?> toType = method.getReturnType();
                        if (toType.isPrimitive()) {
                            return;
                        }

                        String conversionMethodClassName = String.format("%s.%s%dConversionMethod",
                                classInfo.name().packagePrefixName().toString(),
                                StringHelper.capitalize(method.getName()),
                                methodCount.incrementAndGet());

                        ClassOutput output = mapperBeanExists ? new GeneratedBeanGizmoAdaptor(generatedBean)
                                : new GeneratedClassGizmoAdaptor(generatedClass, true);

                        try (ClassCreator classCreator = ClassCreator.builder()
                                .className(conversionMethodClassName)
                                .classOutput(output)
                                .setFinal(true)
                                .interfaces(ConversionMethod.class)
                                .superClass(Object.class.getName())
                                .build()) {

                            // Take advantage of CDI and use injection to get the Mapper instance
                            if (mapperBeanExists) {
                                classCreator.addAnnotation(Singleton.class);
                                unremovableBean.produce(new UnremovableBeanBuildItem(
                                        beanInfo -> beanInfo.hasType(DotName.createSimple(conversionMethodClassName))));
                            }

                            FieldCreator mapperField = classCreator.getFieldCreator("mapper", mapperClassName)
                                    .setModifiers(Modifier.PRIVATE | Modifier.FINAL);

                            if (mapperInstanceField.isPresent() || mapperBeanExists) {
                                // Create a no-args constructor
                                try (MethodCreator initMethod = classCreator.getMethodCreator("<init>", void.class)) {
                                    initMethod.setModifiers(Modifier.PUBLIC);
                                    if (mapperBeanExists) {
                                        initMethod.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class),
                                                initMethod.getThis());
                                    } else {
                                        // If we don't have CDI injection, get the Mapper instance from the Mapper interface
                                        FieldInfo fieldInfo = mapperInstanceField.get();
                                        initMethod.invokeSpecialMethod(
                                                MethodDescriptor.ofConstructor(conversionMethodClassName,
                                                        mapperClassName),
                                                initMethod.getThis(),
                                                initMethod.readStaticField(FieldDescriptor.of(mapperClassName,
                                                        fieldInfo.name(), fieldInfo.type().toString())));
                                    }
                                    initMethod.returnNull();
                                }
                            }

                            try (MethodCreator initMethod = classCreator.getMethodCreator("<init>", void.class,
                                    mapperClassName)) {
                                initMethod.setModifiers(Modifier.PUBLIC);
                                if (mapperBeanExists) {
                                    initMethod.addAnnotation(Inject.class);
                                }
                                initMethod.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class),
                                        initMethod.getThis());
                                initMethod.writeInstanceField(mapperField.getFieldDescriptor(), initMethod.getThis(),
                                        initMethod.getMethodParam(0));
                                initMethod.returnNull();
                            }

                            // doConvert implementation
                            try (MethodCreator doConvertMethod = classCreator.getMethodCreator("doConvert",
                                    Object.class, Class.class, Exchange.class, Object.class)) {
                                doConvertMethod.setModifiers(Modifier.PUBLIC);

                                MethodDescriptor mapperMethod = MethodDescriptor.ofMethod(mapperClassName,
                                        method.getName(), toType.getName(), fromType.getName());

                                ResultHandle mapper = doConvertMethod
                                        .readInstanceField(mapperField.getFieldDescriptor(), doConvertMethod.getThis());

                                // Invoke the target method on the Mapper with the 'value' method arg from convertTo
                                ResultHandle mapperResult = doConvertMethod.invokeVirtualMethod(mapperMethod,
                                        mapper, doConvertMethod.getMethodParam(2));

                                doConvertMethod.returnValue(mapperResult);
                            }
                        }

                        // Register the 'to' type for reflection (See MapstructEndpoint.doBuild())
                        reflectiveClass.produce(ReflectiveClassBuildItem.builder(toType).build());

                        // Instantiate the generated ConversionMethod
                        String key = String.format("%s:%s", fromType.getName(), toType.getName());
                        conversionMethods.computeIfAbsent(key,
                                conversionMethodsKey -> recorder.createConversionMethodInfo(fromType, toType,
                                        mapperBeanExists,
                                        mapperRuntimeValue.get(), conversionMethodClassName));
                    });
                });

        conversionMethodInfos
                .produce(new ConversionMethodInfoRuntimeValuesBuildItem(new HashSet<>(conversionMethods.values())));
    }