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